Compare commits
181 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 | |||
| 48600ae71d |
@@ -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
|
||||
|
||||
+2
-2
@@ -23,8 +23,8 @@ before_script:
|
||||
|
||||
script:
|
||||
- ./autogen.sh
|
||||
- ./configure --enable-all --disable-http --disable-odbc
|
||||
- make
|
||||
- ./configure --enable-all --disable-odbc
|
||||
- make xref
|
||||
- ERL_LIBS=$PWD make test
|
||||
- grep -q 'TEST COMPLETE, \([[:digit:]]*\) ok, .* of \1 ' logs/raw.log
|
||||
|
||||
|
||||
+12
-3
@@ -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
|
||||
|
||||
@@ -157,6 +161,7 @@ install: all
|
||||
# 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)
|
||||
@@ -180,12 +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)
|
||||
[ -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
|
||||
@@ -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
|
||||
|
||||
+4
-13
@@ -2,7 +2,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ(2.53)
|
||||
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo community` | sed 's/-g.*//' | tr -d '\012']), [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-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 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 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],[])
|
||||
|
||||
@@ -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
|
||||
@@ -250,7 +242,6 @@ 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
|
||||
|
||||
|
||||
+80
-9
@@ -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}}
|
||||
@@ -1020,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
|
||||
@@ -1028,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
|
||||
@@ -1062,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.
|
||||
@@ -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}
|
||||
@@ -2780,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 & \\
|
||||
@@ -2999,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}
|
||||
@@ -3117,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}
|
||||
|
||||
@@ -3415,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
|
||||
@@ -3755,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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+10
-2
@@ -108,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:
|
||||
@@ -553,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: {}
|
||||
@@ -638,7 +646,7 @@ modules:
|
||||
##
|
||||
## Enable modules with custom options in a specific virtual host
|
||||
##
|
||||
## append_host_config:
|
||||
## host_config:
|
||||
## "localhost":
|
||||
## modules:
|
||||
## mod_echo:
|
||||
|
||||
@@ -22,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
|
||||
@@ -142,14 +147,7 @@ fi
|
||||
[ -z "$date" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_date '$date'"
|
||||
[ -z "$EJABBERD_OPTS" ] || EJABBERD_OPTS="-ejabberd ${EJABBERD_OPTS}"
|
||||
|
||||
# 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"
|
||||
[ -d $SPOOL_DIR ] || $EXEC_CMD "mkdir -p $SPOOL_DIR"
|
||||
# then set SPOOL_DIR as ejabberd home directory by changing
|
||||
# to that directory readable by INSTALLUSER to prevent
|
||||
# "File operation error: eacces." messages
|
||||
cd $SPOOL_DIR
|
||||
|
||||
# export global variables
|
||||
|
||||
@@ -31,5 +31,6 @@
|
||||
host = <<"">> :: binary(),
|
||||
port = 5280 :: inet:port_number(),
|
||||
tp = http, % :: protocol(),
|
||||
opts = [] :: list(),
|
||||
headers = [] :: [{atom() | binary(), binary()}]}).
|
||||
|
||||
|
||||
@@ -147,5 +147,6 @@
|
||||
-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."}.
|
||||
|
||||
+33
-4
@@ -56,7 +56,7 @@ Deps = [{p1_cache_tab, ".*", {git, "git://github.com/processone/cache_tab"}},
|
||||
{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) ->
|
||||
@@ -97,9 +97,6 @@ CfgDeps = lists:flatmap(
|
||||
[{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,6 +116,33 @@ 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, Macros ++ HiPE ++ DebugInfo ++
|
||||
@@ -127,6 +151,11 @@ Config = [{erl_opts, Macros ++ HiPE ++ DebugInfo ++
|
||||
{keep_build_info, true},
|
||||
{ct_extra_params, "-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).
|
||||
|
||||
@@ -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 ->
|
||||
[];
|
||||
|
||||
+335
-186
@@ -45,6 +45,7 @@
|
||||
set_aux_field/3,
|
||||
del_aux_field/2,
|
||||
get_subscription/2,
|
||||
send_filtered/5,
|
||||
broadcast/4,
|
||||
get_subscribed/1,
|
||||
transform_listen_option/2]).
|
||||
@@ -94,20 +95,20 @@
|
||||
tls_options = [],
|
||||
authenticated = false,
|
||||
jid,
|
||||
user = "", server = <<"">>, resource = <<"">>,
|
||||
user = <<"">>, server = <<"">>, resource = <<"">>,
|
||||
sid,
|
||||
pres_t = ?SETS:new(),
|
||||
pres_f = ?SETS:new(),
|
||||
pres_a = ?SETS:new(),
|
||||
pres_i = ?SETS:new(),
|
||||
pres_last, pres_pri,
|
||||
pres_last,
|
||||
pres_timestamp,
|
||||
pres_invis = false,
|
||||
privacy_list = #userlist{},
|
||||
conn = unknown,
|
||||
auth_module = unknown,
|
||||
ip,
|
||||
aux_fields = [],
|
||||
csi_state = active,
|
||||
csi_queue = [],
|
||||
mgmt_state,
|
||||
mgmt_xmlns,
|
||||
mgmt_queue,
|
||||
@@ -247,6 +248,9 @@ get_subscription(LFrom, StateData) ->
|
||||
true -> none
|
||||
end.
|
||||
|
||||
send_filtered(FsmRef, Feature, From, To, Packet) ->
|
||||
FsmRef ! {send_filtered, Feature, From, To, Packet}.
|
||||
|
||||
broadcast(FsmRef, Type, From, Packet) ->
|
||||
FsmRef ! {broadcast, Type, From, Packet}.
|
||||
|
||||
@@ -307,41 +311,37 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
end,
|
||||
MaxAckQueue = case proplists:get_value(max_ack_queue, Opts) of
|
||||
Limit when is_integer(Limit), Limit > 0 -> Limit;
|
||||
infinity -> infinity;
|
||||
_ -> 500
|
||||
end,
|
||||
ResumeTimeout = case proplists:get_value(resume_timeout, Opts) of
|
||||
Timeout when is_integer(Timeout), Timeout >= 0 -> Timeout;
|
||||
_ -> 300
|
||||
end,
|
||||
ResendOnTimeout = proplists:get_bool(resend_on_timeout, Opts),
|
||||
ResendOnTimeout = case proplists:get_value(resend_on_timeout, Opts) of
|
||||
Resend when is_boolean(Resend) -> Resend;
|
||||
if_offline -> if_offline;
|
||||
_ -> false
|
||||
end,
|
||||
IP = peerip(SockMod, Socket),
|
||||
%% Check if IP is blacklisted:
|
||||
case is_ip_blacklisted(IP) of
|
||||
true ->
|
||||
?INFO_MSG("Connection attempt from blacklisted "
|
||||
"IP: ~s (~w)",
|
||||
[jlib:ip_to_list(IP), IP]),
|
||||
{stop, normal};
|
||||
false ->
|
||||
Socket1 = if TLSEnabled andalso
|
||||
SockMod /= ejabberd_frontend_socket ->
|
||||
SockMod:starttls(Socket, TLSOpts);
|
||||
true -> Socket
|
||||
end,
|
||||
SocketMonitor = SockMod:monitor(Socket1),
|
||||
StateData = #state{socket = Socket1, sockmod = SockMod,
|
||||
socket_monitor = SocketMonitor,
|
||||
xml_socket = XMLSocket, zlib = Zlib, tls = TLS,
|
||||
tls_required = StartTLSRequired,
|
||||
tls_enabled = TLSEnabled, tls_options = TLSOpts,
|
||||
sid = {now(), self()}, streamid = new_id(),
|
||||
access = Access, shaper = Shaper, ip = IP,
|
||||
mgmt_state = StreamMgmtState,
|
||||
mgmt_max_queue = MaxAckQueue,
|
||||
mgmt_timeout = ResumeTimeout,
|
||||
mgmt_resend = ResendOnTimeout},
|
||||
{ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT}
|
||||
end.
|
||||
Socket1 = if TLSEnabled andalso
|
||||
SockMod /= ejabberd_frontend_socket ->
|
||||
SockMod:starttls(Socket, TLSOpts);
|
||||
true -> Socket
|
||||
end,
|
||||
SocketMonitor = SockMod:monitor(Socket1),
|
||||
StateData = #state{socket = Socket1, sockmod = SockMod,
|
||||
socket_monitor = SocketMonitor,
|
||||
xml_socket = XMLSocket, zlib = Zlib, tls = TLS,
|
||||
tls_required = StartTLSRequired,
|
||||
tls_enabled = TLSEnabled, tls_options = TLSOpts,
|
||||
sid = {now(), self()}, streamid = new_id(),
|
||||
access = Access, shaper = Shaper, ip = IP,
|
||||
mgmt_state = StreamMgmtState,
|
||||
mgmt_max_queue = MaxAckQueue,
|
||||
mgmt_timeout = ResumeTimeout,
|
||||
mgmt_resend = ResendOnTimeout},
|
||||
{ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT}.
|
||||
|
||||
%% Return list of all available resources of contacts,
|
||||
get_subscribed(FsmRef) ->
|
||||
@@ -365,27 +365,31 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
jlib:nameprep(xml:get_attr_s(<<"to">>, Attrs));
|
||||
S -> S
|
||||
end,
|
||||
Lang = case xml:get_attr_s(<<"xml:lang">>, Attrs) of
|
||||
Lang1 when byte_size(Lang1) =< 35 ->
|
||||
%% As stated in BCP47, 4.4.1:
|
||||
%% Protocols or specifications that
|
||||
%% specify limited buffer sizes for
|
||||
%% language tags MUST allow for
|
||||
%% language tags of at least 35 characters.
|
||||
Lang1;
|
||||
_ ->
|
||||
%% Do not store long language tag to
|
||||
%% avoid possible DoS/flood attacks
|
||||
<<"">>
|
||||
end,
|
||||
IsBlacklistedIP = is_ip_blacklisted(StateData#state.ip, Lang),
|
||||
case lists:member(Server, ?MYHOSTS) of
|
||||
true ->
|
||||
Lang = case xml:get_attr_s(<<"xml:lang">>, Attrs) of
|
||||
Lang1 when size(Lang1) =< 35 ->
|
||||
%% As stated in BCP47, 4.4.1:
|
||||
%% Protocols or specifications that
|
||||
%% specify limited buffer sizes for
|
||||
%% language tags MUST allow for
|
||||
%% language tags of at least 35 characters.
|
||||
Lang1;
|
||||
_ ->
|
||||
%% Do not store long language tag to
|
||||
%% avoid possible DoS/flood attacks
|
||||
<<"">>
|
||||
end,
|
||||
true when IsBlacklistedIP == false ->
|
||||
change_shaper(StateData, jlib:make_jid(<<"">>, Server, <<"">>)),
|
||||
case xml:get_attr_s(<<"version">>, Attrs) of
|
||||
<<"1.0">> ->
|
||||
send_header(StateData, Server, <<"1.0">>, DefaultLang),
|
||||
case StateData#state.authenticated of
|
||||
false ->
|
||||
TLS = StateData#state.tls,
|
||||
TLSEnabled = StateData#state.tls_enabled,
|
||||
TLSRequired = StateData#state.tls_required,
|
||||
SASLState =
|
||||
cyrsasl:server_new(
|
||||
<<"jabber">>, Server, <<"">>, [],
|
||||
@@ -401,12 +405,21 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
ejabberd_auth:check_password_with_authmodule(
|
||||
U, Server, P, D, DG)
|
||||
end),
|
||||
Mechs = lists:map(fun (S) ->
|
||||
#xmlel{name = <<"mechanism">>,
|
||||
attrs = [],
|
||||
children = [{xmlcdata, S}]}
|
||||
end,
|
||||
cyrsasl:listmech(Server)),
|
||||
Mechs =
|
||||
case TLSEnabled or not TLSRequired of
|
||||
true ->
|
||||
Ms = lists:map(fun (S) ->
|
||||
#xmlel{name = <<"mechanism">>,
|
||||
attrs = [],
|
||||
children = [{xmlcdata, S}]}
|
||||
end,
|
||||
cyrsasl:listmech(Server)),
|
||||
[#xmlel{name = <<"mechanisms">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_SASL}],
|
||||
children = Ms}];
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
SockMod =
|
||||
(StateData#state.sockmod):get_sockmod(
|
||||
StateData#state.socket),
|
||||
@@ -424,9 +437,6 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
TLS = StateData#state.tls,
|
||||
TLSEnabled = StateData#state.tls_enabled,
|
||||
TLSRequired = StateData#state.tls_required,
|
||||
TLSFeature =
|
||||
case (TLS == true) andalso
|
||||
(TLSEnabled == false) andalso
|
||||
@@ -451,10 +461,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
#xmlel{name = <<"stream:features">>,
|
||||
attrs = [],
|
||||
children =
|
||||
TLSFeature ++ CompressFeature ++
|
||||
[#xmlel{name = <<"mechanisms">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_SASL}],
|
||||
children = Mechs}]
|
||||
TLSFeature ++ CompressFeature ++ Mechs
|
||||
++
|
||||
ejabberd_hooks:run_fold(c2s_stream_features,
|
||||
Server, [], [Server])}),
|
||||
@@ -491,6 +498,8 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
++
|
||||
RosterVersioningFeature ++
|
||||
StreamManagementFeature ++
|
||||
ejabberd_hooks:run_fold(c2s_post_auth_features,
|
||||
Server, [], [Server]) ++
|
||||
ejabberd_hooks:run_fold(c2s_stream_features,
|
||||
Server, [], [Server]),
|
||||
send_element(StateData,
|
||||
@@ -523,6 +532,15 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
lang = Lang})
|
||||
end
|
||||
end;
|
||||
true ->
|
||||
IP = StateData#state.ip,
|
||||
{true, LogReason, ReasonT} = IsBlacklistedIP,
|
||||
?INFO_MSG("Connection attempt from blacklisted IP ~s: ~s",
|
||||
[jlib:ip_to_list(IP), LogReason]),
|
||||
send_header(StateData, Server, <<"">>, DefaultLang),
|
||||
send_element(StateData, ?POLICY_VIOLATION_ERR(Lang, ReasonT)),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
_ ->
|
||||
send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
|
||||
send_element(StateData, ?HOST_UNKNOWN_ERR),
|
||||
@@ -622,9 +640,13 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
||||
P, D, DGen)
|
||||
of
|
||||
{true, AuthModule} ->
|
||||
?INFO_MSG("(~w) Accepted legacy authentication for ~s by ~p",
|
||||
[StateData#state.socket,
|
||||
jlib:jid_to_string(JID), AuthModule]),
|
||||
?INFO_MSG("(~w) Accepted legacy authentication for ~s by ~p from ~s",
|
||||
[StateData#state.socket,
|
||||
jlib:jid_to_string(JID), AuthModule,
|
||||
jlib:ip_to_list(StateData#state.ip)]),
|
||||
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
|
||||
[true, U, StateData#state.server,
|
||||
StateData#state.ip]),
|
||||
Conn = get_conn_type(StateData),
|
||||
Info = [{ip, StateData#state.ip}, {conn, Conn},
|
||||
{auth_module, AuthModule}],
|
||||
@@ -659,12 +681,13 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
||||
privacy_list = PrivList},
|
||||
fsm_next_state(session_established, NewStateData);
|
||||
_ ->
|
||||
IP = peerip(StateData#state.sockmod,
|
||||
StateData#state.socket),
|
||||
?INFO_MSG("(~w) Failed legacy authentication for "
|
||||
"~s from IP ~s",
|
||||
[StateData#state.socket,
|
||||
jlib:jid_to_string(JID), jlib:ip_to_list(IP)]),
|
||||
?INFO_MSG("(~w) Failed legacy authentication for ~s from ~s",
|
||||
[StateData#state.socket,
|
||||
jlib:jid_to_string(JID),
|
||||
jlib:ip_to_list(StateData#state.ip)]),
|
||||
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
|
||||
[false, U, StateData#state.server,
|
||||
StateData#state.ip]),
|
||||
Err = jlib:make_error_reply(El, ?ERR_NOT_AUTHORIZED),
|
||||
send_element(StateData, Err),
|
||||
fsm_next_state(wait_for_auth, StateData)
|
||||
@@ -679,9 +702,13 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
||||
fsm_next_state(wait_for_auth, StateData);
|
||||
true ->
|
||||
?INFO_MSG("(~w) Forbidden legacy authentication "
|
||||
"for ~s",
|
||||
"for ~s from ~s",
|
||||
[StateData#state.socket,
|
||||
jlib:jid_to_string(JID)]),
|
||||
jlib:jid_to_string(JID),
|
||||
jlib:ip_to_list(StateData#state.ip)]),
|
||||
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
|
||||
[false, U, StateData#state.server,
|
||||
StateData#state.ip]),
|
||||
Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED),
|
||||
send_element(StateData, Err),
|
||||
fsm_next_state(wait_for_auth, StateData)
|
||||
@@ -718,7 +745,7 @@ wait_for_feature_request({xmlstreamelement, El},
|
||||
(StateData#state.sockmod):get_sockmod(StateData#state.socket),
|
||||
case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of
|
||||
{?NS_SASL, <<"auth">>}
|
||||
when not ((SockMod == gen_tcp) and TLSRequired) ->
|
||||
when TLSEnabled or not TLSRequired ->
|
||||
Mech = xml:get_attr_s(<<"mechanism">>, Attrs),
|
||||
ClientIn = jlib:decode_base64(xml:get_cdata(Els)),
|
||||
case cyrsasl:server_start(StateData#state.sasl_state,
|
||||
@@ -731,8 +758,12 @@ wait_for_feature_request({xmlstreamelement, El},
|
||||
%AuthModule = xml:get_attr_s(auth_module, Props),
|
||||
AuthModule = proplists:get_value(auth_module, Props, undefined),
|
||||
?INFO_MSG("(~w) Accepted authentication for ~s "
|
||||
"by ~p",
|
||||
[StateData#state.socket, U, AuthModule]),
|
||||
"by ~p from ~s",
|
||||
[StateData#state.socket, U, AuthModule,
|
||||
jlib:ip_to_list(StateData#state.ip)]),
|
||||
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
|
||||
[true, U, StateData#state.server,
|
||||
StateData#state.ip]),
|
||||
send_element(StateData,
|
||||
#xmlel{name = <<"success">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_SASL}],
|
||||
@@ -753,10 +784,13 @@ wait_for_feature_request({xmlstreamelement, El},
|
||||
fsm_next_state(wait_for_sasl_response,
|
||||
StateData#state{sasl_state = NewSASLState});
|
||||
{error, Error, Username} ->
|
||||
IP = peerip(StateData#state.sockmod, StateData#state.socket),
|
||||
?INFO_MSG("(~w) Failed authentication for ~s@~s from IP ~s",
|
||||
[StateData#state.socket,
|
||||
Username, StateData#state.server, jlib:ip_to_list(IP)]),
|
||||
?INFO_MSG("(~w) Failed authentication for ~s@~s from ~s",
|
||||
[StateData#state.socket,
|
||||
Username, StateData#state.server,
|
||||
jlib:ip_to_list(StateData#state.ip)]),
|
||||
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
|
||||
[false, Username, StateData#state.server,
|
||||
StateData#state.ip]),
|
||||
send_element(StateData,
|
||||
#xmlel{name = <<"failure">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_SASL}],
|
||||
@@ -832,7 +866,7 @@ wait_for_feature_request({xmlstreamelement, El},
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
if (SockMod == gen_tcp) and TLSRequired ->
|
||||
if TLSRequired and not TLSEnabled ->
|
||||
Lang = StateData#state.lang,
|
||||
send_element(StateData,
|
||||
?POLICY_VIOLATION_ERR(Lang,
|
||||
@@ -877,8 +911,12 @@ wait_for_sasl_response({xmlstreamelement, El},
|
||||
% AuthModule = xml:get_attr_s(auth_module, Props),
|
||||
AuthModule = proplists:get_value(auth_module, Props, <<>>),
|
||||
?INFO_MSG("(~w) Accepted authentication for ~s "
|
||||
"by ~p",
|
||||
[StateData#state.socket, U, AuthModule]),
|
||||
"by ~p from ~s",
|
||||
[StateData#state.socket, U, AuthModule,
|
||||
jlib:ip_to_list(StateData#state.ip)]),
|
||||
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
|
||||
[true, U, StateData#state.server,
|
||||
StateData#state.ip]),
|
||||
send_element(StateData,
|
||||
#xmlel{name = <<"success">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_SASL}],
|
||||
@@ -896,8 +934,12 @@ wait_for_sasl_response({xmlstreamelement, El},
|
||||
% AuthModule = xml:get_attr_s(auth_module, Props),
|
||||
AuthModule = proplists:get_value(auth_module, Props, undefined),
|
||||
?INFO_MSG("(~w) Accepted authentication for ~s "
|
||||
"by ~p",
|
||||
[StateData#state.socket, U, AuthModule]),
|
||||
"by ~p from ~s",
|
||||
[StateData#state.socket, U, AuthModule,
|
||||
jlib:ip_to_list(StateData#state.ip)]),
|
||||
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
|
||||
[true, U, StateData#state.server,
|
||||
StateData#state.ip]),
|
||||
send_element(StateData,
|
||||
#xmlel{name = <<"success">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_SASL}],
|
||||
@@ -920,10 +962,13 @@ wait_for_sasl_response({xmlstreamelement, El},
|
||||
fsm_next_state(wait_for_sasl_response,
|
||||
StateData#state{sasl_state = NewSASLState});
|
||||
{error, Error, Username} ->
|
||||
IP = peerip(StateData#state.sockmod, StateData#state.socket),
|
||||
?INFO_MSG("(~w) Failed authentication for ~s@~s from IP ~s",
|
||||
[StateData#state.socket,
|
||||
Username, StateData#state.server, jlib:ip_to_list(IP)]),
|
||||
?INFO_MSG("(~w) Failed authentication for ~s@~s from ~s",
|
||||
[StateData#state.socket,
|
||||
Username, StateData#state.server,
|
||||
jlib:ip_to_list(StateData#state.ip)]),
|
||||
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
|
||||
[false, Username, StateData#state.server,
|
||||
StateData#state.ip]),
|
||||
send_element(StateData,
|
||||
#xmlel{name = <<"failure">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_SASL}],
|
||||
@@ -1136,6 +1181,17 @@ wait_for_session(closed, StateData) ->
|
||||
session_established({xmlstreamelement, #xmlel{name = Name} = El}, StateData)
|
||||
when ?IS_STREAM_MGMT_TAG(Name) ->
|
||||
fsm_next_state(session_established, dispatch_stream_mgmt(El, StateData));
|
||||
session_established({xmlstreamelement,
|
||||
#xmlel{name = <<"active">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}]}},
|
||||
StateData) ->
|
||||
NewStateData = csi_queue_flush(StateData),
|
||||
fsm_next_state(session_established, NewStateData#state{csi_state = active});
|
||||
session_established({xmlstreamelement,
|
||||
#xmlel{name = <<"inactive">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}]}},
|
||||
StateData) ->
|
||||
fsm_next_state(session_established, StateData#state{csi_state = inactive});
|
||||
session_established({xmlstreamelement, El},
|
||||
StateData) ->
|
||||
FromJID = StateData#state.jid,
|
||||
@@ -1167,9 +1223,7 @@ session_established({xmlstreamerror, _}, StateData) ->
|
||||
send_element(StateData, ?INVALID_XML_ERR),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
session_established(closed, StateData)
|
||||
when StateData#state.mgmt_timeout > 0,
|
||||
StateData#state.mgmt_state == active ->
|
||||
session_established(closed, #state{mgmt_state = active} = StateData) ->
|
||||
fsm_next_state(wait_for_resume, StateData);
|
||||
session_established(closed, StateData) ->
|
||||
{stop, normal, StateData}.
|
||||
@@ -1640,12 +1694,23 @@ handle_info({route, From, To,
|
||||
jlib:replace_from_to_attrs(jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To), NewAttrs),
|
||||
FixedPacket = #xmlel{name = Name, attrs = Attrs2, children = Els},
|
||||
SentStateData = send_packet(NewState, FixedPacket),
|
||||
ejabberd_hooks:run(user_receive_packet,
|
||||
SentStateData#state.server,
|
||||
[SentStateData#state.jid, From, To, FixedPacket]),
|
||||
FinalState =
|
||||
case ejabberd_hooks:run_fold(c2s_filter_packet_in,
|
||||
NewState#state.server, FixedPacket,
|
||||
[NewState#state.jid, From, To])
|
||||
of
|
||||
drop ->
|
||||
NewState;
|
||||
FinalPacket = #xmlel{} ->
|
||||
SentState = send_packet(NewState, FinalPacket),
|
||||
ejabberd_hooks:run(user_receive_packet,
|
||||
SentState#state.server,
|
||||
[SentState#state.jid, From, To,
|
||||
FinalPacket]),
|
||||
SentState
|
||||
end,
|
||||
ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]),
|
||||
fsm_next_state(StateName, SentStateData);
|
||||
fsm_next_state(StateName, FinalState);
|
||||
true ->
|
||||
ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]),
|
||||
fsm_next_state(StateName, NewState)
|
||||
@@ -1653,8 +1718,7 @@ handle_info({route, From, To,
|
||||
handle_info({'DOWN', Monitor, _Type, _Object, _Info},
|
||||
_StateName, StateData)
|
||||
when Monitor == StateData#state.socket_monitor ->
|
||||
if StateData#state.mgmt_timeout > 0,
|
||||
StateData#state.mgmt_state == active orelse
|
||||
if StateData#state.mgmt_state == active;
|
||||
StateData#state.mgmt_state == pending ->
|
||||
fsm_next_state(wait_for_resume, StateData);
|
||||
true ->
|
||||
@@ -1689,6 +1753,32 @@ handle_info({force_update_presence, LUser}, StateName,
|
||||
_ -> StateData
|
||||
end,
|
||||
fsm_next_state(StateName, NewStateData);
|
||||
handle_info({send_filtered, Feature, From, To, Packet}, StateName, StateData) ->
|
||||
Drop = ejabberd_hooks:run_fold(c2s_filter_packet, StateData#state.server,
|
||||
true, [StateData#state.server, StateData,
|
||||
Feature, To, Packet]),
|
||||
NewStateData = if Drop ->
|
||||
?DEBUG("Dropping packet from ~p to ~p",
|
||||
[jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To)]),
|
||||
StateData;
|
||||
true ->
|
||||
FinalPacket = jlib:replace_from_to(From, To, Packet),
|
||||
case StateData#state.jid of
|
||||
To ->
|
||||
case privacy_check_packet(StateData, From, To,
|
||||
FinalPacket, in) of
|
||||
deny ->
|
||||
StateData;
|
||||
allow ->
|
||||
send_stanza(StateData, FinalPacket)
|
||||
end;
|
||||
_ ->
|
||||
ejabberd_router:route(From, To, FinalPacket),
|
||||
StateData
|
||||
end
|
||||
end,
|
||||
fsm_next_state(StateName, NewStateData);
|
||||
handle_info({broadcast, Type, From, Packet}, StateName, StateData) ->
|
||||
Recipients = ejabberd_hooks:run_fold(
|
||||
c2s_broadcast_recipients, StateData#state.server,
|
||||
@@ -1710,11 +1800,10 @@ handle_info(Info, StateName, StateData) ->
|
||||
%% Purpose: Prepare the state to be printed on error log
|
||||
%% Returns: State to print
|
||||
%%----------------------------------------------------------------------
|
||||
print_state(State = #state{pres_t = T, pres_f = F, pres_a = A, pres_i = I}) ->
|
||||
print_state(State = #state{pres_t = T, pres_f = F, pres_a = A}) ->
|
||||
State#state{pres_t = {pres_t, ?SETS:size(T)},
|
||||
pres_f = {pres_f, ?SETS:size(F)},
|
||||
pres_a = {pres_a, ?SETS:size(A)},
|
||||
pres_i = {pres_i, ?SETS:size(I)}
|
||||
pres_a = {pres_a, ?SETS:size(A)}
|
||||
}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
@@ -1750,8 +1839,6 @@ terminate(_Reason, StateName, StateData) ->
|
||||
<<"Replaced by new connection">>),
|
||||
presence_broadcast(StateData, From,
|
||||
StateData#state.pres_a, Packet),
|
||||
presence_broadcast(StateData, From,
|
||||
StateData#state.pres_i, Packet),
|
||||
handle_unacked_stanzas(StateData);
|
||||
_ ->
|
||||
?INFO_MSG("(~w) Close session for ~s",
|
||||
@@ -1759,10 +1846,7 @@ terminate(_Reason, StateName, StateData) ->
|
||||
jlib:jid_to_string(StateData#state.jid)]),
|
||||
EmptySet = (?SETS):new(),
|
||||
case StateData of
|
||||
#state{pres_last = undefined,
|
||||
pres_a = EmptySet,
|
||||
pres_i = EmptySet,
|
||||
pres_invis = false} ->
|
||||
#state{pres_last = undefined, pres_a = EmptySet} ->
|
||||
ejabberd_sm:close_session(StateData#state.sid,
|
||||
StateData#state.user,
|
||||
StateData#state.server,
|
||||
@@ -1778,9 +1862,7 @@ terminate(_Reason, StateName, StateData) ->
|
||||
StateData#state.resource,
|
||||
<<"">>),
|
||||
presence_broadcast(StateData, From,
|
||||
StateData#state.pres_a, Packet),
|
||||
presence_broadcast(StateData, From,
|
||||
StateData#state.pres_i, Packet)
|
||||
StateData#state.pres_a, Packet)
|
||||
end,
|
||||
handle_unacked_stanzas(StateData)
|
||||
end,
|
||||
@@ -1812,7 +1894,8 @@ send_text(StateData, Text) when StateData#state.mgmt_state == active ->
|
||||
?DEBUG("Send XML on stream = ~p", [Text]),
|
||||
case catch (StateData#state.sockmod):send(StateData#state.socket, Text) of
|
||||
{'EXIT', _} ->
|
||||
(StateData#state.sockmod):close(StateData#state.socket);
|
||||
(StateData#state.sockmod):close(StateData#state.socket),
|
||||
error;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
@@ -1828,27 +1911,30 @@ send_element(StateData, El) when StateData#state.xml_socket ->
|
||||
send_element(StateData, El) ->
|
||||
send_text(StateData, xml:element_to_binary(El)).
|
||||
|
||||
send_stanza(StateData, Stanza) when StateData#state.csi_state == inactive ->
|
||||
csi_filter_stanza(StateData, Stanza);
|
||||
send_stanza(StateData, Stanza) when StateData#state.mgmt_state == pending ->
|
||||
mgmt_queue_add(StateData, Stanza);
|
||||
send_stanza(StateData, Stanza) when StateData#state.mgmt_state == active ->
|
||||
send_stanza_and_ack_req(StateData, Stanza),
|
||||
mgmt_queue_add(StateData, Stanza);
|
||||
NewStateData = case send_stanza_and_ack_req(StateData, Stanza) of
|
||||
ok ->
|
||||
StateData;
|
||||
error ->
|
||||
StateData#state{mgmt_state = pending}
|
||||
end,
|
||||
mgmt_queue_add(NewStateData, Stanza);
|
||||
send_stanza(StateData, Stanza) ->
|
||||
send_element(StateData, Stanza),
|
||||
StateData.
|
||||
|
||||
send_packet(StateData, Packet) when StateData#state.mgmt_state == active;
|
||||
StateData#state.mgmt_state == pending ->
|
||||
send_packet(StateData, Packet) ->
|
||||
case is_stanza(Packet) of
|
||||
true ->
|
||||
send_stanza(StateData, Packet);
|
||||
false ->
|
||||
send_element(StateData, Packet),
|
||||
StateData
|
||||
end;
|
||||
send_packet(StateData, Stanza) ->
|
||||
send_element(StateData, Stanza),
|
||||
StateData.
|
||||
end.
|
||||
|
||||
send_header(StateData, Server, Version, Lang)
|
||||
when StateData#state.xml_socket ->
|
||||
@@ -1957,28 +2043,15 @@ process_presence_probe(From, To, StateData) ->
|
||||
undefined ->
|
||||
ok;
|
||||
_ ->
|
||||
Cond1 = (not StateData#state.pres_invis)
|
||||
andalso (?SETS:is_element(LFrom, StateData#state.pres_f)
|
||||
orelse
|
||||
((LFrom /= LBFrom) andalso
|
||||
?SETS:is_element(LBFrom, StateData#state.pres_f)))
|
||||
andalso (not
|
||||
(?SETS:is_element(LFrom, StateData#state.pres_i)
|
||||
orelse
|
||||
((LFrom /= LBFrom) andalso
|
||||
?SETS:is_element(LBFrom, StateData#state.pres_i)))),
|
||||
Cond2 = StateData#state.pres_invis
|
||||
andalso ?SETS:is_element(LFrom, StateData#state.pres_f)
|
||||
andalso ?SETS:is_element(LFrom, StateData#state.pres_a),
|
||||
Cond = ?SETS:is_element(LFrom, StateData#state.pres_f)
|
||||
orelse
|
||||
((LFrom /= LBFrom) andalso
|
||||
?SETS:is_element(LBFrom, StateData#state.pres_f)),
|
||||
if
|
||||
Cond1 ->
|
||||
Timestamp = StateData#state.pres_timestamp,
|
||||
Packet = xml:append_subtags(
|
||||
StateData#state.pres_last,
|
||||
%% To is the one sending the presence (the target of the probe)
|
||||
[jlib:timestamp_to_xml(Timestamp, utc, To, <<"">>),
|
||||
%% TODO: Delete the next line once XEP-0091 is Obsolete
|
||||
jlib:timestamp_to_xml(Timestamp)]),
|
||||
Cond ->
|
||||
%% To is the one sending the presence (the probe target)
|
||||
Packet = jlib:add_delay_info(StateData#state.pres_last, To,
|
||||
StateData#state.pres_timestamp),
|
||||
case privacy_check_packet(StateData, To, From, Packet, out) of
|
||||
deny ->
|
||||
ok;
|
||||
@@ -1993,11 +2066,6 @@ process_presence_probe(From, To, StateData) ->
|
||||
ok
|
||||
end
|
||||
end;
|
||||
Cond2 ->
|
||||
ejabberd_router:route(To, From,
|
||||
#xmlel{name = <<"presence">>,
|
||||
attrs = [],
|
||||
children = []});
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
@@ -2035,12 +2103,11 @@ presence_update(From, Packet, StateData) ->
|
||||
OldPresence -> get_priority_from_presence(OldPresence)
|
||||
end,
|
||||
NewPriority = get_priority_from_presence(Packet),
|
||||
Timestamp = calendar:now_to_universal_time(now()),
|
||||
update_priority(NewPriority, Packet, StateData),
|
||||
FromUnavail = (StateData#state.pres_last == undefined),
|
||||
?DEBUG("from unavail = ~p~n", [FromUnavail]),
|
||||
NewStateData = StateData#state{pres_last = Packet,
|
||||
pres_timestamp = Timestamp},
|
||||
pres_timestamp = now()},
|
||||
NewState = if FromUnavail ->
|
||||
ejabberd_hooks:run(user_available_hook,
|
||||
NewStateData#state.server,
|
||||
@@ -2173,7 +2240,7 @@ presence_broadcast_first(From, StateData, Packet) ->
|
||||
[],
|
||||
StateData#state.pres_t),
|
||||
PacketProbe = #xmlel{name = <<"presence">>, attrs = [{<<"type">>,<<"probe">>}], children = []},
|
||||
JIDs2Probe = format_and_check_privacy(From, StateData, Packet, JIDsProbe, out),
|
||||
JIDs2Probe = format_and_check_privacy(From, StateData, PacketProbe, JIDsProbe, out),
|
||||
Server = StateData#state.server,
|
||||
send_multiple(StateData, From, JIDs2Probe, PacketProbe),
|
||||
{As, JIDs} =
|
||||
@@ -2429,11 +2496,24 @@ fsm_next_state_gc(StateName, PackedStateData) ->
|
||||
|
||||
%% fsm_next_state: Generate the next_state FSM tuple with different
|
||||
%% timeout, depending on the future state
|
||||
fsm_next_state(session_established, #state{mgmt_max_queue = exceeded} =
|
||||
StateData) ->
|
||||
?WARNING_MSG("ACK queue too long, terminating session for ~s",
|
||||
[jlib:jid_to_string(StateData#state.jid)]),
|
||||
Err = ?SERRT_POLICY_VIOLATION(StateData#state.lang,
|
||||
<<"Too many unacked stanzas">>),
|
||||
send_element(StateData, Err),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData#state{mgmt_resend = false}};
|
||||
fsm_next_state(session_established, #state{mgmt_state = pending} = StateData) ->
|
||||
fsm_next_state(wait_for_resume, StateData);
|
||||
fsm_next_state(session_established, StateData) ->
|
||||
{next_state, session_established, StateData,
|
||||
?C2S_HIBERNATE_TIMEOUT};
|
||||
fsm_next_state(wait_for_resume, StateData)
|
||||
when StateData#state.mgmt_state /= pending ->
|
||||
fsm_next_state(wait_for_resume, #state{mgmt_timeout = 0} = StateData) ->
|
||||
{stop, normal, StateData};
|
||||
fsm_next_state(wait_for_resume, #state{mgmt_pending_since = undefined} =
|
||||
StateData) ->
|
||||
?INFO_MSG("Waiting for resumption of stream for ~s",
|
||||
[jlib:jid_to_string(StateData#state.jid)]),
|
||||
{next_state, wait_for_resume,
|
||||
@@ -2451,11 +2531,6 @@ fsm_next_state(StateName, StateData) ->
|
||||
fsm_reply(Reply, session_established, StateData) ->
|
||||
{reply, Reply, session_established, StateData,
|
||||
?C2S_HIBERNATE_TIMEOUT};
|
||||
fsm_reply(Reply, wait_for_resume, #state{mgmt_pending_since = undefined} =
|
||||
StateData) ->
|
||||
{reply, Reply, wait_for_resume,
|
||||
StateData#state{mgmt_pending_since = os:timestamp()},
|
||||
StateData#state.mgmt_timeout};
|
||||
fsm_reply(Reply, wait_for_resume, StateData) ->
|
||||
Diff = timer:now_diff(os:timestamp(), StateData#state.mgmt_pending_since),
|
||||
Timeout = max(StateData#state.mgmt_timeout - Diff div 1000, 1),
|
||||
@@ -2464,9 +2539,9 @@ fsm_reply(Reply, StateName, StateData) ->
|
||||
{reply, Reply, StateName, StateData, ?C2S_OPEN_TIMEOUT}.
|
||||
|
||||
%% Used by c2s blacklist plugins
|
||||
is_ip_blacklisted(undefined) -> false;
|
||||
is_ip_blacklisted({IP, _Port}) ->
|
||||
ejabberd_hooks:run_fold(check_bl_c2s, false, [IP]).
|
||||
is_ip_blacklisted(undefined, _Lang) -> false;
|
||||
is_ip_blacklisted({IP, _Port}, Lang) ->
|
||||
ejabberd_hooks:run_fold(check_bl_c2s, false, [IP, Lang]).
|
||||
|
||||
%% Check from attributes
|
||||
%% returns invalid-from|NewElement
|
||||
@@ -2725,15 +2800,20 @@ handle_resume(StateData, Attrs) ->
|
||||
{<<"h">>, AttrH},
|
||||
{<<"previd">>, AttrId}],
|
||||
children = []}),
|
||||
SendFun = fun(_F, _T, El) -> send_element(NewState, El) end,
|
||||
SendFun = fun(_F, _T, El, Time) ->
|
||||
NewEl = add_resent_delay_info(NewState, El, Time),
|
||||
send_element(NewState, NewEl)
|
||||
end,
|
||||
handle_unacked_stanzas(NewState, SendFun),
|
||||
send_element(NewState,
|
||||
#xmlel{name = <<"r">>,
|
||||
attrs = [{<<"xmlns">>, AttrXmlns}],
|
||||
children = []}),
|
||||
FlushedState = csi_queue_flush(NewState),
|
||||
NewStateData = FlushedState#state{csi_state = active},
|
||||
?INFO_MSG("Resumed session for ~s",
|
||||
[jlib:jid_to_string(NewState#state.jid)]),
|
||||
{ok, NewState};
|
||||
[jlib:jid_to_string(NewStateData#state.jid)]),
|
||||
{ok, NewStateData};
|
||||
{error, El, Msg} ->
|
||||
send_element(StateData, El),
|
||||
?INFO_MSG("Cannot resume session for ~s@~s: ~s",
|
||||
@@ -2779,30 +2859,25 @@ mgmt_queue_add(StateData, El) ->
|
||||
Num ->
|
||||
Num + 1
|
||||
end,
|
||||
NewQueue = queue:in({NewNum, El}, StateData#state.mgmt_queue),
|
||||
NewQueue = queue:in({NewNum, now(), El}, StateData#state.mgmt_queue),
|
||||
NewState = StateData#state{mgmt_queue = NewQueue,
|
||||
mgmt_stanzas_out = NewNum},
|
||||
check_queue_length(NewState).
|
||||
|
||||
mgmt_queue_drop(StateData, NumHandled) ->
|
||||
NewQueue = jlib:queue_drop_while(fun({N, _Stanza}) -> N =< NumHandled end,
|
||||
NewQueue = jlib:queue_drop_while(fun({N, _T, _E}) -> N =< NumHandled end,
|
||||
StateData#state.mgmt_queue),
|
||||
StateData#state{mgmt_queue = NewQueue}.
|
||||
|
||||
check_queue_length(#state{mgmt_max_queue = Limit} = StateData)
|
||||
when Limit == infinity;
|
||||
Limit == unlimited ->
|
||||
Limit == exceeded ->
|
||||
StateData;
|
||||
check_queue_length(#state{mgmt_queue = Queue,
|
||||
mgmt_max_queue = Limit} = StateData) ->
|
||||
case queue:len(Queue) > Limit of
|
||||
true ->
|
||||
?WARNING_MSG("ACK queue too long, terminating session for ~s",
|
||||
[jlib:jid_to_string(StateData#state.jid)]),
|
||||
Lang = StateData#state.lang,
|
||||
Err = ?SERRT_POLICY_VIOLATION(Lang, <<"Too many unacked stanzas">>),
|
||||
self() ! {kick, queue_overflow, Err},
|
||||
StateData#state{mgmt_resend = false}; % Don't resend the flood!
|
||||
StateData#state{mgmt_max_queue = exceeded};
|
||||
false ->
|
||||
StateData
|
||||
end.
|
||||
@@ -2818,12 +2893,12 @@ handle_unacked_stanzas(StateData, F)
|
||||
?INFO_MSG("~B stanzas were not acknowledged by ~s",
|
||||
[N, jlib:jid_to_string(StateData#state.jid)]),
|
||||
lists:foreach(
|
||||
fun({_, #xmlel{attrs = Attrs} = El}) ->
|
||||
fun({_, Time, #xmlel{attrs = Attrs} = El}) ->
|
||||
From_s = xml:get_attr_s(<<"from">>, Attrs),
|
||||
From = jlib:string_to_jid(From_s),
|
||||
To_s = xml:get_attr_s(<<"to">>, Attrs),
|
||||
To = jlib:string_to_jid(To_s),
|
||||
F(From, To, El)
|
||||
F(From, To, El, Time)
|
||||
end, queue:to_list(Queue))
|
||||
end;
|
||||
handle_unacked_stanzas(_StateData, _F) ->
|
||||
@@ -2832,18 +2907,29 @@ handle_unacked_stanzas(_StateData, _F) ->
|
||||
handle_unacked_stanzas(StateData)
|
||||
when StateData#state.mgmt_state == active;
|
||||
StateData#state.mgmt_state == pending ->
|
||||
ReRoute = case StateData#state.mgmt_resend of
|
||||
ResendOnTimeout =
|
||||
case StateData#state.mgmt_resend of
|
||||
Resend when is_boolean(Resend) ->
|
||||
Resend;
|
||||
if_offline ->
|
||||
ejabberd_sm:get_user_resources(StateData#state.user,
|
||||
StateData#state.server) == []
|
||||
end,
|
||||
ReRoute = case ResendOnTimeout of
|
||||
true ->
|
||||
fun ejabberd_router:route/3;
|
||||
fun(From, To, El, Time) ->
|
||||
NewEl = add_resent_delay_info(StateData, El, Time),
|
||||
ejabberd_router:route(From, To, NewEl)
|
||||
end;
|
||||
false ->
|
||||
fun(From, To, El) ->
|
||||
fun(From, To, El, _Time) ->
|
||||
Err =
|
||||
jlib:make_error_reply(El,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end
|
||||
end,
|
||||
F = fun(From, To, El) ->
|
||||
F = fun(From, To, El, Time) ->
|
||||
%% We'll drop the stanza if it was <forwarded/> by some
|
||||
%% encapsulating protocol as per XEP-0297. One such protocol is
|
||||
%% XEP-0280, which says: "When a receiving server attempts to
|
||||
@@ -2856,7 +2942,7 @@ handle_unacked_stanzas(StateData)
|
||||
?DEBUG("Dropping forwarded stanza from ~s",
|
||||
[xml:get_attr_s(<<"from">>, El#xmlel.attrs)]);
|
||||
false ->
|
||||
ReRoute(From, To, El)
|
||||
ReRoute(From, To, El, Time)
|
||||
end
|
||||
end,
|
||||
handle_unacked_stanzas(StateData, F);
|
||||
@@ -2891,7 +2977,7 @@ is_encapsulated_forward(_El) ->
|
||||
|
||||
inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
|
||||
case jlib:base64_to_term(ResumeID) of
|
||||
{term, {U, S, R, Time}} ->
|
||||
{term, {R, Time}} ->
|
||||
case ejabberd_sm:get_session_pid(U, S, R) of
|
||||
none ->
|
||||
{error, <<"Previous session PID not found">>};
|
||||
@@ -2911,19 +2997,19 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
|
||||
{auth_module, StateData#state.auth_module}],
|
||||
ejabberd_sm:open_session(NewSID, U, S, R,
|
||||
Priority, Info),
|
||||
{ok, StateData#state{sid = NewSID,
|
||||
{ok, StateData#state{conn = Conn,
|
||||
sid = NewSID,
|
||||
jid = OldStateData#state.jid,
|
||||
resource = OldStateData#state.resource,
|
||||
pres_t = OldStateData#state.pres_t,
|
||||
pres_f = OldStateData#state.pres_f,
|
||||
pres_a = OldStateData#state.pres_a,
|
||||
pres_i = OldStateData#state.pres_i,
|
||||
pres_last = OldStateData#state.pres_last,
|
||||
pres_pri = OldStateData#state.pres_pri,
|
||||
pres_timestamp = OldStateData#state.pres_timestamp,
|
||||
pres_invis = OldStateData#state.pres_invis,
|
||||
privacy_list = OldStateData#state.privacy_list,
|
||||
aux_fields = OldStateData#state.aux_fields,
|
||||
csi_state = OldStateData#state.csi_state,
|
||||
csi_queue = OldStateData#state.csi_queue,
|
||||
mgmt_xmlns = OldStateData#state.mgmt_xmlns,
|
||||
mgmt_queue = OldStateData#state.mgmt_queue,
|
||||
mgmt_timeout = OldStateData#state.mgmt_timeout,
|
||||
@@ -2936,7 +3022,7 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
|
||||
{error, <<"Cannot grab session state">>}
|
||||
end
|
||||
end;
|
||||
error ->
|
||||
_ ->
|
||||
{error, <<"Invalid 'previd' value">>}
|
||||
end.
|
||||
|
||||
@@ -2945,11 +3031,74 @@ resume_session({Time, PID}) ->
|
||||
|
||||
make_resume_id(StateData) ->
|
||||
{Time, _} = StateData#state.sid,
|
||||
ID = {StateData#state.user,
|
||||
StateData#state.server,
|
||||
StateData#state.resource,
|
||||
Time},
|
||||
jlib:term_to_base64(ID).
|
||||
jlib:term_to_base64({StateData#state.resource, Time}).
|
||||
|
||||
add_resent_delay_info(#state{server = From}, El, Time) ->
|
||||
jlib:add_delay_info(El, From, Time, <<"Resent">>).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% XEP-0352
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
csi_filter_stanza(#state{csi_state = CsiState, jid = JID} = StateData,
|
||||
Stanza) ->
|
||||
Action = ejabberd_hooks:run_fold(csi_filter_stanza,
|
||||
StateData#state.server,
|
||||
send, [Stanza]),
|
||||
?DEBUG("Going to ~p stanza for inactive client ~p",
|
||||
[Action, jlib:jid_to_string(JID)]),
|
||||
case Action of
|
||||
queue -> csi_queue_add(StateData, Stanza);
|
||||
drop -> StateData;
|
||||
send ->
|
||||
From = xml:get_tag_attr_s(<<"from">>, Stanza),
|
||||
StateData1 = csi_queue_send(StateData, From),
|
||||
StateData2 = send_stanza(StateData1#state{csi_state = active},
|
||||
Stanza),
|
||||
StateData2#state{csi_state = CsiState}
|
||||
end.
|
||||
|
||||
csi_queue_add(#state{csi_queue = Queue} = StateData, Stanza) ->
|
||||
case length(StateData#state.csi_queue) >= csi_max_queue(StateData) of
|
||||
true -> csi_queue_add(csi_queue_flush(StateData), Stanza);
|
||||
false ->
|
||||
From = xml:get_tag_attr_s(<<"from">>, Stanza),
|
||||
NewQueue = lists:keystore(From, 1, Queue, {From, now(), Stanza}),
|
||||
StateData#state{csi_queue = NewQueue}
|
||||
end.
|
||||
|
||||
csi_queue_send(#state{csi_queue = Queue, csi_state = CsiState, server = Host} =
|
||||
StateData, From) ->
|
||||
case lists:keytake(From, 1, Queue) of
|
||||
{value, {From, Time, Stanza}, NewQueue} ->
|
||||
NewStanza = jlib:add_delay_info(Stanza, Host, Time,
|
||||
<<"Client Inactive">>),
|
||||
NewStateData = send_stanza(StateData#state{csi_state = active},
|
||||
NewStanza),
|
||||
NewStateData#state{csi_queue = NewQueue, csi_state = CsiState};
|
||||
false -> StateData
|
||||
end.
|
||||
|
||||
csi_queue_flush(#state{csi_queue = Queue, csi_state = CsiState, jid = JID,
|
||||
server = Host} = StateData) ->
|
||||
?DEBUG("Flushing CSI queue for ~s", [jlib:jid_to_string(JID)]),
|
||||
NewStateData =
|
||||
lists:foldl(fun({_From, Time, Stanza}, AccState) ->
|
||||
NewStanza =
|
||||
jlib:add_delay_info(Stanza, Host, Time,
|
||||
<<"Client Inactive">>),
|
||||
send_stanza(AccState, NewStanza)
|
||||
end, StateData#state{csi_state = active}, Queue),
|
||||
NewStateData#state{csi_queue = [], csi_state = CsiState}.
|
||||
|
||||
%% Make sure we won't push too many messages to the XEP-0198 queue when the
|
||||
%% client becomes 'active' again. Otherwise, the client might not manage to
|
||||
%% acknowledge the message flood in time. Also, don't let the queue grow to
|
||||
%% more than 100 stanzas.
|
||||
csi_max_queue(#state{mgmt_max_queue = infinity}) -> 100;
|
||||
csi_max_queue(#state{mgmt_max_queue = Max}) when Max > 200 -> 100;
|
||||
csi_max_queue(#state{mgmt_max_queue = Max}) when Max < 2 -> 1;
|
||||
csi_max_queue(#state{mgmt_max_queue = Max}) -> Max div 2.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% JID Set memory footprint reduction code
|
||||
|
||||
@@ -210,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.
|
||||
|
||||
|
||||
@@ -989,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},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 =
|
||||
|
||||
+1
-1
@@ -849,7 +849,7 @@ kick_user(User, Server) ->
|
||||
lists:foreach(
|
||||
fun(Resource) ->
|
||||
PID = get_session_pid(User, Server, Resource),
|
||||
PID ! disconnect
|
||||
PID ! kick
|
||||
end, Resources),
|
||||
length(Resources).
|
||||
|
||||
|
||||
+42
-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}).
|
||||
|
||||
@@ -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},
|
||||
|
||||
+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, <<>>).
|
||||
|
||||
@@ -299,7 +299,8 @@ process_blocklist_unblock_all(LUser, LServer, Filter,
|
||||
NewList = Filter(List),
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists),
|
||||
NewLists = [{Default, NewList} | NewLists1],
|
||||
case ejabberd_riak:put(P#privacy{lists = NewLists}) of
|
||||
case ejabberd_riak:put(P#privacy{lists = NewLists},
|
||||
mod_privacy:privacy_schema()) of
|
||||
ok ->
|
||||
{ok, Default, NewList};
|
||||
Err ->
|
||||
|
||||
+21
-1
@@ -48,7 +48,8 @@
|
||||
|
||||
%% hook handlers
|
||||
-export([user_send_packet/3, user_receive_packet/4,
|
||||
c2s_presence_in/2, c2s_broadcast_recipients/6]).
|
||||
c2s_presence_in/2, c2s_filter_packet/6,
|
||||
c2s_broadcast_recipients/6]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -266,6 +267,21 @@ c2s_presence_in(C2SState,
|
||||
true -> C2SState
|
||||
end.
|
||||
|
||||
c2s_filter_packet(InAcc, Host, C2SState, {pep_message, Feature}, To, _Packet) ->
|
||||
case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of
|
||||
{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_filter_packet(Acc, _, _, _, _, _) -> Acc.
|
||||
|
||||
c2s_broadcast_recipients(InAcc, Host, C2SState,
|
||||
{pep_message, Feature}, _From, _Packet) ->
|
||||
case ejabberd_c2s:get_aux_field(caps_resources,
|
||||
@@ -317,6 +333,8 @@ init([Host, Opts]) ->
|
||||
[{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,
|
||||
@@ -348,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,
|
||||
|
||||
+32
-10
@@ -138,8 +138,8 @@ 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) == <<"chat">> andalso
|
||||
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 ->
|
||||
@@ -167,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;
|
||||
@@ -180,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) ]),
|
||||
@@ -207,7 +215,7 @@ send_copies(JID, To, Packet, Direction)->
|
||||
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 = [
|
||||
@@ -223,7 +231,7 @@ build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_2) ->
|
||||
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 = [
|
||||
@@ -264,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
|
||||
|
||||
+2
-1
@@ -914,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 =
|
||||
|
||||
+8
-11
@@ -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,
|
||||
@@ -571,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).
|
||||
|
||||
+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}.
|
||||
|
||||
+124
-99
@@ -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,22 +134,57 @@ 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) ->
|
||||
@@ -148,26 +215,15 @@ 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),
|
||||
@@ -212,28 +268,7 @@ receive_all(US, Msgs, DBType) ->
|
||||
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;
|
||||
_ -> []
|
||||
@@ -247,11 +282,26 @@ 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">>) ->
|
||||
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
|
||||
@@ -269,7 +319,7 @@ store_packet(From, To, Packet) ->
|
||||
end;
|
||||
_ -> ok
|
||||
end;
|
||||
true -> ok
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
has_no_storage_hint(Packet) ->
|
||||
@@ -371,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
|
||||
@@ -625,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)),
|
||||
@@ -1048,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, <<"');">>]];
|
||||
|
||||
+81
-50
@@ -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,
|
||||
@@ -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
|
||||
|
||||
|
||||
+79
-33
@@ -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,
|
||||
@@ -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
|
||||
|
||||
|
||||
+1
-1
@@ -1238,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;
|
||||
|
||||
@@ -501,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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
+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
|
||||
|
||||
+208
-39
@@ -199,6 +199,8 @@ db_tests(riak) ->
|
||||
blocking,
|
||||
vcard,
|
||||
test_unregister]},
|
||||
{test_muc_register, [sequence],
|
||||
[muc_register_master, muc_register_slave]},
|
||||
{test_roster_subscribe, [parallel],
|
||||
[roster_subscribe_master,
|
||||
roster_subscribe_slave]},
|
||||
@@ -206,6 +208,10 @@ db_tests(riak) ->
|
||||
[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]}];
|
||||
@@ -224,6 +230,8 @@ db_tests(mnesia) ->
|
||||
vcard,
|
||||
pubsub,
|
||||
test_unregister]},
|
||||
{test_muc_register, [sequence],
|
||||
[muc_register_master, muc_register_slave]},
|
||||
{test_roster_subscribe, [parallel],
|
||||
[roster_subscribe_master,
|
||||
roster_subscribe_slave]},
|
||||
@@ -231,8 +239,14 @@ db_tests(mnesia) ->
|
||||
[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]}];
|
||||
@@ -252,6 +266,8 @@ db_tests(_) ->
|
||||
vcard,
|
||||
pubsub,
|
||||
test_unregister]},
|
||||
{test_muc_register, [sequence],
|
||||
[muc_register_master, muc_register_slave]},
|
||||
{test_roster_subscribe, [parallel],
|
||||
[roster_subscribe_master,
|
||||
roster_subscribe_slave]},
|
||||
@@ -259,6 +275,10 @@ db_tests(_) ->
|
||||
[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]}].
|
||||
@@ -513,16 +533,16 @@ sm(Config) ->
|
||||
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}),
|
||||
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{}),
|
||||
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{}),
|
||||
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)}.
|
||||
@@ -537,11 +557,11 @@ sm_resume(Config) ->
|
||||
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}),
|
||||
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}),
|
||||
send(Config, #sm_a{h = 1, xmlns = ?NS_STREAM_MGMT_3}),
|
||||
disconnect(Config).
|
||||
|
||||
private(Config) ->
|
||||
@@ -708,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{}],
|
||||
@@ -745,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),
|
||||
@@ -1004,7 +1060,8 @@ muc_master(Config) ->
|
||||
%% As this is the newly created room, we receive only the 2nd stanza.
|
||||
#presence{
|
||||
from = MyNickJID,
|
||||
sub_els = [#muc_user{
|
||||
sub_els = [#vcard_xupdate{},
|
||||
#muc_user{
|
||||
status_codes = Codes,
|
||||
items = [#muc_item{role = moderator,
|
||||
jid = MyJID,
|
||||
@@ -1073,7 +1130,8 @@ muc_master(Config) ->
|
||||
[#muc_invite{to = PeerJID}]}]}),
|
||||
%% Peer is joining
|
||||
#presence{from = PeerNickJID,
|
||||
sub_els = [#muc_user{
|
||||
sub_els = [#vcard_xupdate{},
|
||||
#muc_user{
|
||||
items = [#muc_item{role = visitor,
|
||||
jid = PeerJID,
|
||||
affiliation = none}]}]} = recv(),
|
||||
@@ -1106,7 +1164,8 @@ muc_master(Config) ->
|
||||
fields = ReplyVoiceReqFs}]}),
|
||||
%% Peer is becoming a participant
|
||||
#presence{from = PeerNickJID,
|
||||
sub_els = [#muc_user{
|
||||
sub_els = [#vcard_xupdate{},
|
||||
#muc_user{
|
||||
items = [#muc_item{role = participant,
|
||||
jid = PeerJID,
|
||||
affiliation = none}]}]} = recv(),
|
||||
@@ -1124,7 +1183,8 @@ muc_master(Config) ->
|
||||
affiliation = member}]}]}),
|
||||
%% Peer became a member
|
||||
#presence{from = PeerNickJID,
|
||||
sub_els = [#muc_user{
|
||||
sub_els = [#vcard_xupdate{},
|
||||
#muc_user{
|
||||
items = [#muc_item{affiliation = member,
|
||||
jid = PeerJID,
|
||||
role = participant}]}]} = recv(),
|
||||
@@ -1141,7 +1201,7 @@ muc_master(Config) ->
|
||||
role = none}]}]}),
|
||||
%% Got notification the peer is kicked
|
||||
%% 307 -> Inform user that he or she has been kicked from the room
|
||||
#presence{from = PeerNickJID,
|
||||
#presence{from = PeerNickJID, type = unavailable,
|
||||
sub_els = [#muc_user{
|
||||
status_codes = [307],
|
||||
items = [#muc_item{affiliation = member,
|
||||
@@ -1199,14 +1259,16 @@ muc_slave(Config) ->
|
||||
%% First presence is from the participant, i.e. from the peer
|
||||
#presence{
|
||||
from = PeerNickJID,
|
||||
sub_els = [#muc_user{
|
||||
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 = [#muc_user{
|
||||
sub_els = [#vcard_xupdate{},
|
||||
#muc_user{
|
||||
status_codes = [110],
|
||||
items = [#muc_item{role = visitor,
|
||||
affiliation = none}]}]} = recv(),
|
||||
@@ -1237,7 +1299,8 @@ muc_slave(Config) ->
|
||||
send(Config, #message{to = Room, sub_els = [VoiceReq]}),
|
||||
%% Becoming a participant
|
||||
#presence{from = MyNickJID,
|
||||
sub_els = [#muc_user{
|
||||
sub_els = [#vcard_xupdate{},
|
||||
#muc_user{
|
||||
items = [#muc_item{role = participant,
|
||||
affiliation = none}]}]} = recv(),
|
||||
%% Sending private message to the peer
|
||||
@@ -1245,7 +1308,8 @@ muc_slave(Config) ->
|
||||
body = [#text{data = Subject}]}),
|
||||
%% Becoming a member
|
||||
#presence{from = MyNickJID,
|
||||
sub_els = [#muc_user{
|
||||
sub_els = [#vcard_xupdate{},
|
||||
#muc_user{
|
||||
items = [#muc_item{role = participant,
|
||||
affiliation = member}]}]} = recv(),
|
||||
%% Retrieving a member list
|
||||
@@ -1273,6 +1337,91 @@ muc_slave(Config) ->
|
||||
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) ->
|
||||
Peer = ?config(slave, Config),
|
||||
LPeer = jlib:jid_remove_resource(Peer),
|
||||
@@ -1408,6 +1557,43 @@ carbons_slave(Config) ->
|
||||
#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
|
||||
%%%===================================================================
|
||||
@@ -1520,29 +1706,12 @@ split(Data) ->
|
||||
clear_riak_tables(Config) ->
|
||||
User = ?config(user, Config),
|
||||
Server = ?config(server, Config),
|
||||
Slave = jlib:make_jid(<<"test_slave">>, Server, <<>>),
|
||||
Master = jlib:make_jid(<<"test_master">>, Server, <<>>),
|
||||
Room = muc_room_jid(Config),
|
||||
{U, S, _} = jlib:jid_tolower(jlib:make_jid(User, Server, <<>>)),
|
||||
{USlave, SSlave, _} = LSlave = jlib:jid_tolower(Slave),
|
||||
{UMaster, SMaster, _} = LMaster = jlib:jid_tolower(Master),
|
||||
{URoom, SRoom, _} = jlib:jid_tolower(jlib:jid_remove_resource(Room)),
|
||||
US = {U, S},
|
||||
USSlave = {USlave, SSlave},
|
||||
USMaster = {UMaster, SMaster},
|
||||
USRoom = {URoom, SRoom},
|
||||
ok = ejabberd_riak:delete(roster, {USlave, SSlave, LMaster}),
|
||||
ok = ejabberd_riak:delete(roster, {UMaster, SMaster, LSlave}),
|
||||
ok = ejabberd_riak:delete(passwd, US),
|
||||
ok = ejabberd_riak:delete(passwd, USSlave),
|
||||
ok = ejabberd_riak:delete(passwd, USMaster),
|
||||
ok = ejabberd_riak:delete(roster_version, USSlave),
|
||||
ok = ejabberd_riak:delete(roster_version, USMaster),
|
||||
ok = ejabberd_riak:delete(last_activity, US),
|
||||
ok = ejabberd_riak:delete(last_activity, USSlave),
|
||||
ok = ejabberd_riak:delete(last_activity, USMaster),
|
||||
ok = ejabberd_riak:delete(vcard, US),
|
||||
ok = ejabberd_riak:delete(privacy, US),
|
||||
ok = ejabberd_riak:delete(private_storage, {U, S, <<"storage:bookmarks">>}),
|
||||
ok = ejabberd_riak:delete(muc_room, USRoom),
|
||||
{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,8 +145,13 @@ 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: []
|
||||
@@ -158,6 +170,7 @@ Welcome to this XMPP server."
|
||||
modules:
|
||||
mod_announce:
|
||||
db_type: riak
|
||||
access: local
|
||||
mod_blocking:
|
||||
db_type: riak
|
||||
mod_caps:
|
||||
@@ -178,6 +191,8 @@ Welcome to this XMPP server."
|
||||
db_type: riak
|
||||
mod_vcard:
|
||||
db_type: riak
|
||||
mod_vcard_xupdate:
|
||||
db_type: riak
|
||||
mod_adhoc: []
|
||||
mod_configure: []
|
||||
mod_disco: []
|
||||
|
||||
@@ -155,6 +155,8 @@ wait_auth_SASL_result(Config) ->
|
||||
lists:foldl(
|
||||
fun(#feature_sm{}, ConfigAcc) ->
|
||||
set_opt(sm, true, ConfigAcc);
|
||||
(#feature_csi{}, ConfigAcc) ->
|
||||
set_opt(csi, true, ConfigAcc);
|
||||
(_, ConfigAcc) ->
|
||||
ConfigAcc
|
||||
end, Config, Fs);
|
||||
|
||||
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
|
||||
+10410
-7651
File diff suppressed because it is too large
Load Diff
+60
-43
@@ -1,16 +1,24 @@
|
||||
%% 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()}).
|
||||
previd :: binary(),
|
||||
xmlns :: binary()}).
|
||||
|
||||
-record(carbons_enable, {}).
|
||||
|
||||
@@ -37,18 +45,21 @@
|
||||
from :: any(),
|
||||
to :: any()}).
|
||||
|
||||
-record(sm_a, {h :: non_neg_integer()}).
|
||||
-record(sm_a, {h :: non_neg_integer(),
|
||||
xmlns :: binary()}).
|
||||
|
||||
-record(starttls_proceed, {}).
|
||||
|
||||
-record(sm_resumed, {h :: non_neg_integer(),
|
||||
previd :: binary()}).
|
||||
previd :: binary(),
|
||||
xmlns :: binary()}).
|
||||
|
||||
-record(forwarded, {delay :: #delay{},
|
||||
sub_els = [] :: [any()]}).
|
||||
|
||||
-record(sm_enable, {max :: non_neg_integer(),
|
||||
resume = false :: any()}).
|
||||
resume = false :: any(),
|
||||
xmlns :: binary()}).
|
||||
|
||||
-record(starttls_failure, {}).
|
||||
|
||||
@@ -60,7 +71,7 @@
|
||||
|
||||
-record(p1_ack, {}).
|
||||
|
||||
-record(feature_sm, {}).
|
||||
-record(feature_sm, {xmlns :: binary()}).
|
||||
|
||||
-record(pubsub_item, {id :: binary(),
|
||||
xml_els = [] :: [any()]}).
|
||||
@@ -81,7 +92,10 @@
|
||||
node :: binary(),
|
||||
publisher :: binary()}).
|
||||
|
||||
-record(sm_r, {}).
|
||||
-record(sm_r, {xmlns :: binary()}).
|
||||
|
||||
-record(muc_actor, {jid :: any(),
|
||||
nick :: binary()}).
|
||||
|
||||
-record(stat, {name :: binary(),
|
||||
units :: binary(),
|
||||
@@ -102,7 +116,8 @@
|
||||
-record(sm_enabled, {id :: binary(),
|
||||
location :: binary(),
|
||||
max :: non_neg_integer(),
|
||||
resume = false :: any()}).
|
||||
resume = false :: any(),
|
||||
xmlns :: binary()}).
|
||||
|
||||
-record(pubsub_event_items, {node :: binary(),
|
||||
retract = [] :: [binary()],
|
||||
@@ -120,6 +135,8 @@
|
||||
|
||||
-record(p1_push, {}).
|
||||
|
||||
-record(feature_csi, {xmlns :: binary()}).
|
||||
|
||||
-record(legacy_delay, {stamp :: binary(),
|
||||
from :: any()}).
|
||||
|
||||
@@ -150,8 +167,15 @@
|
||||
subid :: binary(),
|
||||
type :: 'none' | 'pending' | 'subscribed' | 'unconfigured'}).
|
||||
|
||||
-record(muc_actor, {jid :: any(),
|
||||
nick :: binary()}).
|
||||
-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()}]}).
|
||||
|
||||
@@ -192,9 +216,6 @@
|
||||
notify = false :: any(),
|
||||
items = [] :: [#pubsub_item{}]}).
|
||||
|
||||
-record(text, {lang :: binary(),
|
||||
data :: binary()}).
|
||||
|
||||
-record(vcard_geo, {lat :: binary(),
|
||||
lon :: binary()}).
|
||||
|
||||
@@ -224,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{}],
|
||||
@@ -239,7 +252,7 @@
|
||||
status_codes = [] :: [pos_integer()],
|
||||
password :: binary()}).
|
||||
|
||||
-record(muc_admin, {items = [] :: [#muc_item{}]}).
|
||||
-record(vcard_xupdate, {photo :: binary()}).
|
||||
|
||||
-record(carbons_disable, {}).
|
||||
|
||||
@@ -288,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()}).
|
||||
|
||||
@@ -371,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()],
|
||||
@@ -378,7 +392,8 @@
|
||||
|
||||
-record(sasl_mechanisms, {list = [] :: [binary()]}).
|
||||
|
||||
-record(sm_failed, {reason :: atom() | #gone{} | #redirect{}}).
|
||||
-record(sm_failed, {reason :: atom() | #gone{} | #redirect{},
|
||||
xmlns :: binary()}).
|
||||
|
||||
-record(error, {type :: 'auth' | 'cancel' | 'continue' | 'modify' | 'wait',
|
||||
by :: binary(),
|
||||
@@ -467,3 +482,5 @@
|
||||
|
||||
-record(time, {tzo :: any(),
|
||||
utc :: any()}).
|
||||
|
||||
|
||||
|
||||
+86
-19
@@ -944,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'},
|
||||
@@ -1484,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">>,
|
||||
@@ -1755,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">>,
|
||||
@@ -2068,18 +2109,38 @@
|
||||
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:3">>,
|
||||
result = {feature_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:3">>,
|
||||
result = {sm_enable, '$max', '$resume'},
|
||||
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, []},
|
||||
@@ -2087,10 +2148,11 @@
|
||||
|
||||
-xml(sm_enabled,
|
||||
#elem{name = <<"enabled">>,
|
||||
xmlns = <<"urn:xmpp:sm:3">>,
|
||||
result = {sm_enabled, '$id', '$location', '$max', '$resume'},
|
||||
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, []}},
|
||||
@@ -2101,44 +2163,49 @@
|
||||
|
||||
-xml(sm_resume,
|
||||
#elem{name = <<"resume">>,
|
||||
xmlns = <<"urn:xmpp:sm:3">>,
|
||||
result = {sm_resume, '$h', '$previd'},
|
||||
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:3">>,
|
||||
result = {sm_resumed, '$h', '$previd'},
|
||||
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:3">>,
|
||||
result = {sm_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:3">>,
|
||||
result = {sm_a, '$h'},
|
||||
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, []}}]}).
|
||||
enc = {enc_int, []}},
|
||||
#attr{name = <<"xmlns">>}]}).
|
||||
|
||||
-xml(sm_failed,
|
||||
#elem{name = <<"failed">>,
|
||||
xmlns = <<"urn:xmpp:sm:3">>,
|
||||
result = {sm_failed, '$reason'},
|
||||
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,
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
{zlib, @zlib@}.
|
||||
{riak, @riak@}.
|
||||
{json, @json@}.
|
||||
{http, @http@}.
|
||||
{lager, @lager@}.
|
||||
{iconv, @iconv@}.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user