aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE15
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.md21
-rw-r--r--.github/ISSUE_TEMPLATE/feature_request.md18
-rw-r--r--.travis.yml9
-rw-r--r--CHANGELOG.md10
-rw-r--r--CONTRIBUTING.md148
-rw-r--r--CONTRIBUTORS.md37
-rw-r--r--Makefile.in7
-rw-r--r--README174
-rw-r--r--[l---------]README.md175
-rw-r--r--config/ejabberd.exs1
-rw-r--r--config/ejabberd.yml666
-rw-r--r--configure.ac17
-rw-r--r--ejabberd.yml.example37
-rw-r--r--include/ejabberd_stacktrace.hrl (renamed from include/mod_carboncopy.hrl)14
-rw-r--r--include/mod_muc_room.hrl14
-rw-r--r--include/pubsub.hrl2
-rw-r--r--lib/ejabberd/config/config.ex4
-rw-r--r--lib/ejabberd/config/ejabberd_module.ex1
-rw-r--r--lib/ejabberd/config/logger/ejabberd_logger.ex2
-rw-r--r--lib/ejabberd/config/validator/validation.ex2
-rw-r--r--lib/ejabberd/config/validator/validator_attrs.ex2
-rw-r--r--lib/mix/tasks/deps.tree.ex4
-rw-r--r--lib/mod_presence_demo.ex9
-rw-r--r--mix.exs28
-rw-r--r--mix.lock39
-rw-r--r--rebar.config45
-rw-r--r--sql/lite.new.sql11
-rw-r--r--sql/lite.sql10
-rw-r--r--sql/mssql.sql13
-rw-r--r--sql/mysql.new.sql19
-rw-r--r--sql/mysql.sql18
-rw-r--r--sql/pg.new.sql18
-rw-r--r--sql/pg.sql10
-rw-r--r--src/acl.erl2
-rw-r--r--src/ejabberd.erl2
-rw-r--r--src/ejabberd_acme.erl150
-rw-r--r--src/ejabberd_app.erl3
-rw-r--r--src/ejabberd_auth.erl44
-rw-r--r--src/ejabberd_auth_sql.erl28
-rw-r--r--src/ejabberd_bosh.erl109
-rw-r--r--src/ejabberd_c2s.erl143
-rw-r--r--src/ejabberd_captcha.erl8
-rw-r--r--src/ejabberd_commands.erl1
-rw-r--r--src/ejabberd_config.erl14
-rw-r--r--src/ejabberd_ctl.erl7
-rw-r--r--src/ejabberd_hooks.erl15
-rw-r--r--src/ejabberd_http.erl116
-rw-r--r--src/ejabberd_http_ws.erl94
-rw-r--r--src/ejabberd_listener.erl864
-rw-r--r--src/ejabberd_local.erl6
-rw-r--r--src/ejabberd_logger.erl2
-rw-r--r--src/ejabberd_mnesia.erl5
-rw-r--r--src/ejabberd_piefxis.erl16
-rw-r--r--src/ejabberd_pkix.erl1109
-rw-r--r--src/ejabberd_rdbms.erl6
-rw-r--r--src/ejabberd_redis.erl5
-rw-r--r--src/ejabberd_regexp.erl2
-rw-r--r--src/ejabberd_router.erl6
-rw-r--r--src/ejabberd_router_sql.erl6
-rw-r--r--src/ejabberd_s2s.erl16
-rw-r--r--src/ejabberd_s2s_in.erl94
-rw-r--r--src/ejabberd_s2s_out.erl20
-rw-r--r--src/ejabberd_service.erl90
-rw-r--r--src/ejabberd_sip.erl50
-rw-r--r--src/ejabberd_sm.erl210
-rw-r--r--src/ejabberd_sql.erl160
-rw-r--r--src/ejabberd_sql_pt.erl27
-rw-r--r--src/ejabberd_sql_sup.erl65
-rw-r--r--src/ejabberd_stun.erl74
-rw-r--r--src/ejabberd_sup.erl2
-rw-r--r--src/ejabberd_web_admin.erl4
-rw-r--r--src/ejabberd_xmlrpc.erl29
-rw-r--r--src/eldap_pool.erl2
-rw-r--r--src/gen_iq_handler.erl6
-rw-r--r--src/gen_mod.erl20
-rw-r--r--src/jd2ejd.erl3
-rw-r--r--src/misc.erl95
-rw-r--r--src/mod_admin_extra.erl22
-rw-r--r--src/mod_avatar.erl4
-rw-r--r--src/mod_block_strangers.erl22
-rw-r--r--src/mod_carboncopy.erl226
-rw-r--r--src/mod_carboncopy_mnesia.erl79
-rw-r--r--src/mod_carboncopy_redis.erl176
-rw-r--r--src/mod_carboncopy_riak.erl82
-rw-r--r--src/mod_carboncopy_sql.erl91
-rw-r--r--src/mod_client_state.erl4
-rw-r--r--src/mod_fail2ban.erl55
-rw-r--r--src/mod_http_api.erl62
-rw-r--r--src/mod_http_upload.erl31
-rw-r--r--src/mod_mam.erl235
-rw-r--r--src/mod_mam_mnesia.erl13
-rw-r--r--src/mod_mam_sql.erl21
-rw-r--r--src/mod_mix.erl323
-rw-r--r--src/mod_muc.erl31
-rw-r--r--src/mod_muc_admin.erl34
-rw-r--r--src/mod_muc_log.erl2
-rw-r--r--src/mod_muc_mnesia.erl2
-rw-r--r--src/mod_muc_room.erl963
-rw-r--r--src/mod_multicast.erl44
-rw-r--r--src/mod_offline.erl35
-rw-r--r--src/mod_offline_sql.erl17
-rw-r--r--src/mod_private.erl191
-rw-r--r--src/mod_proxy65.erl10
-rw-r--r--src/mod_proxy65_service.erl18
-rw-r--r--src/mod_proxy65_stream.erl31
-rw-r--r--src/mod_pubsub.erl10
-rw-r--r--src/mod_push.erl2
-rw-r--r--src/mod_register.erl13
-rw-r--r--src/mod_roster.erl12
-rw-r--r--src/mod_sip.erl2
-rw-r--r--src/mod_stream_mgmt.erl84
-rw-r--r--src/node_pep.erl3
-rw-r--r--src/node_pep_sql.erl3
-rw-r--r--src/prosody2ejabberd.erl47
-rw-r--r--src/proxy_protocol.erl184
-rw-r--r--src/xml_compress.erl958
-rw-r--r--test/acl_test.exs389
-rw-r--r--test/ejabberd_SUITE.erl35
-rw-r--r--test/ejabberd_SUITE_data/ca.key50
-rw-r--r--test/ejabberd_SUITE_data/ca.pem34
-rw-r--r--test/ejabberd_SUITE_data/cert.pem80
-rw-r--r--test/ejabberd_SUITE_data/ejabberd.yml2
-rwxr-xr-xtest/ejabberd_SUITE_data/gencerts.sh2
-rw-r--r--test/ejabberd_SUITE_data/self-signed-cert.pem80
-rw-r--r--test/ejabberd_admin_test.exs88
-rw-r--r--test/ejabberd_auth_mock.exs74
-rw-r--r--test/ejabberd_hooks_test.exs203
-rw-r--r--test/ejabberd_oauth_mock.exs50
-rw-r--r--test/ejabberd_sm_mock.exs121
-rw-r--r--test/elixir_SUITE.erl119
-rw-r--r--test/jid_test.exs45
-rw-r--r--test/mix_tests.erl154
-rw-r--r--test/mod_admin_extra_test.exs374
-rw-r--r--test/mod_http_api_mock_test.exs270
-rw-r--r--test/mod_http_api_test.exs127
-rw-r--r--test/mod_last_mock.exs79
-rw-r--r--test/mod_roster_mock.exs159
-rw-r--r--test/private_tests.erl105
-rw-r--r--test/suite.erl7
-rw-r--r--test/suite.hrl6
-rw-r--r--test/test_helper.exs7
-rw-r--r--tools/xml_compress_gen.erl417
-rw-r--r--vars.config.in1
144 files changed, 5333 insertions, 7217 deletions
diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE
deleted file mode 100644
index d31eb5bb3..000000000
--- a/.github/ISSUE_TEMPLATE
+++ /dev/null
@@ -1,15 +0,0 @@
-> What version of ejabberd are you using?
-
-
-
-> What operating system (version) are you using?
-
-
-
-> How did you install ejabberd (source, package, distribution)?
-
-
-
-> What did not work as expected? Are there error messages in the log? What
-> was the unexpected behavior? What was the expected result?
-
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 000000000..bb5a6e196
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,21 @@
+Environment
+-----------
+- ejabberd version: 18.09
+- Erlang version: `erl +V`
+- OS: Linux (Debian)
+- Installed from: source | distro package | official deb/rpm | official binary installer | other
+
+Configuration (only if needed): grep -Ev '^$|^\s*#' ejabberd.yml
+---------------------------------------------------------------------------
+```yaml
+loglevel: 4
+...
+```
+
+Errors from error.log/crash.log
+-------------------------------
+No errors
+
+Bug description
+---------------
+Nothing works, plz halp :(
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 000000000..751cb5de0
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,18 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+labels:
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.travis.yml b/.travis.yml
index ae0d3abb3..efe6a63a5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,9 @@
language: erlang
otp_release:
- - 17.5
- - 18.3
- - 19.2
+ - 19.0
+ - 20.3
+ - 21.2
services:
- redis-server
@@ -33,9 +33,12 @@ before_script:
- mysql -u root -e "CREATE USER 'ejabberd_test'@'localhost' IDENTIFIED BY 'ejabberd_test';"
- mysql -u root -e "CREATE DATABASE ejabberd_test;"
- mysql -u root -e "GRANT ALL ON ejabberd_test.* TO 'ejabberd_test'@'localhost';"
+ - mysql -u root ejabberd_test < sql/mysql.sql
- psql -U postgres -c "CREATE USER ejabberd_test WITH PASSWORD 'ejabberd_test';"
- psql -U postgres -c "CREATE DATABASE ejabberd_test;"
+ - psql -U postgres ejabberd_test -f sql/pg.sql
- psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE ejabberd_test TO ejabberd_test;"
+ - psql -U postgres ejabberd_test -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ejabberd_test;"
script:
- ./autogen.sh
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..b90b54f50
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,10 @@
+# Version NEXT
+
+*
+
+# Version 18.12
+
+* MAM data store compression
+* Proxy protocol support (http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)
+* MUC Self-Ping optimization (XEP-0410)
+* Bookmarks conversion (XEP-0411)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..9ac1c6b2e
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,148 @@
+# Contributing to ejabberd
+
+We'd love for you to contribute to our source code and to make ejabberd even better than it is
+today! Here are the guidelines we'd like you to follow:
+
+* [Code of Conduct](#coc)
+* [Questions and Problems](#question)
+* [Issues and Bugs](#issue)
+* [Feature Requests](#feature)
+* [Issue Submission Guidelines](#submit)
+* [Pull Request Submission Guidelines](#submit-pr)
+* [Signing the CLA](#cla)
+
+## <a name="coc"></a> Code of Conduct
+
+Help us keep ejabberd community open-minded and inclusive. Please read and follow our [Code of Conduct][coc].
+
+## <a name="requests"></a> Questions, Bugs, Features
+
+### <a name="question"></a> Got a Question or Problem?
+
+Do not open issues for general support questions as we want to keep GitHub issues for bug reports
+and feature requests. You've got much better chances of getting your question answered on dedicated
+support platforms, the best being [Stack Overflow][stackoverflow].
+
+Stack Overflow is a much better place to ask questions since:
+
+- there are thousands of people willing to help on Stack Overflow
+- questions and answers stay available for public viewing so your question / answer might help
+ someone else
+- Stack Overflow's voting system assures that the best answers are prominently visible.
+
+To save your and our time, we will systematically close all issues that are requests for general
+support and redirect people to the section you are reading right now.
+
+Other channels for support are:
+- [ejabberd Mailing List][list]
+- [ejabberd XMPP room][muc]: ejabberd@conference.process-one.net
+
+### <a name="issue"></a> Found an Issue or Bug?
+
+If you find a bug in the source code, you can help us by submitting an issue to our
+[GitHub Repository][github]. Even better, you can submit a Pull Request with a fix.
+
+### <a name="feature"></a> Missing a Feature?
+
+You can request a new feature by submitting an issue to our [GitHub Repository][github-issues].
+
+If you would like to implement a new feature then consider what kind of change it is:
+
+* **Major Changes** that you wish to contribute to the project should be discussed first in an
+ [GitHub issue][github-issues] that clearly outlines the changes and benefits of the feature.
+* **Small Changes** can directly be crafted and submitted to the [GitHub Repository][github]
+ as a Pull Request. See the section about [Pull Request Submission Guidelines](#submit-pr).
+
+## <a name="submit"></a> Issue Submission Guidelines
+
+Before you submit your issue search the archive, maybe your question was already answered.
+
+If your issue appears to be a bug, and hasn't been reported, open a new issue. Help us to maximize
+the effort we can spend fixing issues and adding new features, by not reporting duplicate issues.
+
+The "[new issue][github-new-issue]" form contains a number of prompts that you should fill out to
+make it easier to understand and categorize the issue.
+
+## <a name="submit-pr"></a> Pull Request Submission Guidelines
+
+By submitting a pull request for a code or doc contribution, you need to have the right
+to grant your contribution's copyright license to ProcessOne. Please check [ProcessOne CLA][cla]
+for details.
+
+Before you submit your pull request consider the following guidelines:
+
+* Search [GitHub][github-pr] for an open or closed Pull Request
+ that relates to your submission. You don't want to duplicate effort.
+* Create the [development environment][developer-setup]
+* Make your changes in a new git branch:
+
+ ```shell
+ git checkout -b my-fix-branch master
+ ```
+* Test your changes and, if relevant, expand the automated test suite.
+* Create your patch commit, including appropriate test cases.
+* If the changes affect public APIs, change or add relevant [documentation][doc-repo].
+* Commit your changes using a descriptive commit message.
+
+ ```shell
+ git commit -a
+ ```
+ Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
+
+* Push your branch to GitHub:
+
+ ```shell
+ git push origin my-fix-branch
+ ```
+
+* In GitHub, send a pull request to `ejabberd:master`. This will trigger the Travis integration and run the test.
+We will also notify you if you have not yet signed the [contribution agreement][cla].
+
+* If you find that the Travis integration has failed, look into the logs on Travis to find out
+if your changes caused test failures, the commit message was malformed etc. If you find that the
+tests failed or times out for unrelated reasons, you can ping a team member so that the build can be
+restarted.
+
+* If we suggest changes, then:
+
+ * Make the required updates.
+ * Test your changes and test cases.
+ * Commit your changes to your branch (e.g. `my-fix-branch`).
+ * Push the changes to your GitHub repository (this will update your Pull Request).
+
+ You can also amend the initial commits and force push them to the branch.
+
+ ```shell
+ git rebase master -i
+ git push origin my-fix-branch -f
+ ```
+
+ This is generally easier to follow, but separate commits are useful if the Pull Request contains
+ iterations that might be interesting to see side-by-side.
+
+That's it! Thank you for your contribution!
+
+## <a name="cla"></a> Signing the Contributor License Agreement (CLA)
+
+Upon submitting a Pull Request, we will ask you to sign our CLA if you haven't done
+so before. It's a quick process, we promise, and you will be able to do it all online
+
+You can read [ProcessOne Contribution License Agreement][cla] in PDF.
+
+This is part of the legal framework of the open-source ecosystem that adds some red tape,
+but protects both the contributor and the company / foundation behind the project. It also
+gives us the option to relicense the code with a more permissive license in the future.
+
+
+[coc]: https://github.com/processone/ejabberd/blob/master/CODE_OF_CONDUCT.md
+[stackoverflow]: https://stackoverflow.com/questions/tagged/ejabberd?sort=newest
+[list]: http://lists.jabber.ru/mailman/listinfo/ejabberd
+[muc]: xmpp:ejabberd@conference.process-one.net
+[github]: https://github.com/processone/ejabberd
+[github-issues]: https://github.com/processone/ejabberd/issues
+[github-new-issue]: https://github.com/processone/ejabberd/issues/new
+[github-pr]: https://github.com/processone/ejabberd/pulls
+[doc-repo]: https://github.com/processone/docs.ejabberd.im
+[developer-setup]: https://docs.ejabberd.im/developer/
+[cla]: https://www.process-one.net/resources/ejabberd-cla.pdf
+[license]: https://github.com/processone/ejabberd/blob/master/COPYING
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
new file mode 100644
index 000000000..50c7d614a
--- /dev/null
+++ b/CONTRIBUTORS.md
@@ -0,0 +1,37 @@
+# Contributors
+
+We would like to thanks official ejabberd source code contributors:
+
+- Sergey Abramyan
+- Badlop
+- Ludovic Bocquet
+- Emilio Bustos
+- Thiago Camargo
+- Juan Pablo Carlino
+- Paweł Chmielowski
+- Gabriel Gatu
+- Tsukasa Hamano
+- Konstantinos Kallas
+- Evgeny Khramtsov
+- Ben Langfeld
+- Peter Lemenkov
+- Anna Mukharram
+- Johan Oudinet
+- Pablo Polvorin
+- Mickaël Rémond
+- Matthias Rieber
+- Rafael Roemhild
+- Christophe Romain
+- Jérôme Sautret
+- Sonny Scroggin
+- Alexey Shchepin
+- Shelley Shyan
+- Radoslaw Szymczyszyn
+- Stu Tomlinson
+- Christian Ulrich
+- Holger Weiß
+
+Please, if you think we are missing your contribution, do not hesitate to contact us at ProcessOne.
+In case you do not want to appear in this list, please, let us know as well.
+
+Thanks !
diff --git a/Makefile.in b/Makefile.in
index 48dca7d8a..d3bcb282d 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -152,7 +152,7 @@ DEPS_FILES_FILTERED=$(filter-out $(BINARIES) deps/elixir/ebin/elixir.app,$(DEPS_
DEPS_DIRS=$(sort deps/ $(foreach DEP,$(DEPS),deps/$(DEP)/) $(dir $(DEPS_FILES)))
MAIN_FILES=$(filter-out %/configure.beam,$(call FILES_WILDCARD,ebin/*.beam ebin/*.app priv/msgs/*.msg priv/css/*.css priv/img/*.png priv/js/*.js priv/lib/* include/*.hrl COPYING))
-MAIN_DIRS=$(sort $(dir $(MAIN_FILES)) priv/bin priv/sql)
+MAIN_DIRS=$(sort $(dir $(MAIN_FILES)) priv/bin priv/sql priv/lua)
define DEP_VERSION_template
DEP_$(1)_VERSION:=$(shell $(SED) -e '/vsn/!d;s/.*, *"/$(1)-/;s/".*//' $(2) 2>/dev/null)
@@ -184,7 +184,10 @@ $(call TO_DEST,priv/sql/lite.sql): sql/lite.sql $(call TO_DEST,priv/sql)
$(call TO_DEST,priv/bin/captcha.sh): tools/captcha.sh $(call TO_DEST,priv/bin)
$(INSTALL) -m 755 $(O_USER) $< $@
-copy-files-sub2: $(call TO_DEST,$(DEPS_FILES) $(MAIN_FILES) priv/bin/captcha.sh priv/sql/lite.sql)
+$(call TO_DEST,priv/lua/redis_sm.lua): priv/lua/redis_sm.lua $(call TO_DEST,priv/lua)
+ $(INSTALL) -m 644 $< $@
+
+copy-files-sub2: $(call TO_DEST,$(DEPS_FILES) $(MAIN_FILES) priv/bin/captcha.sh priv/sql/lite.sql priv/lua/redis_sm.lua)
.PHONY: $(call TO_DEST,$(DEPS_FILES) $(MAIN_DIRS) $(DEPS_DIRS))
diff --git a/README b/README
deleted file mode 100644
index fa424c279..000000000
--- a/README
+++ /dev/null
@@ -1,174 +0,0 @@
-ejabberd Community Edition
-==========================
-
-[![Build Status](https://travis-ci.org/processone/ejabberd.svg?branch=master)](https://travis-ci.org/processone/ejabberd) [![Hex version](https://img.shields.io/hexpm/v/ejabberd.svg "Hex version")](https://hex.pm/packages/ejabberd)
-
-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 XMPP 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 and 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:
-
-- **Modularity**
- - 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).
- - Component support: interface with networks such as AIM, ICQ and MSN.
-
-
-Quickstart guide
-----------------
-
-### 0. Requirements
-
-To compile ejabberd you need:
-
- - GNU Make.
- - GCC.
- - Libexpat 1.95 or higher.
- - Libyaml 0.1.4 or higher.
- - Erlang/OTP 17.5 or higher.
- - OpenSSL 1.0.0 or higher, for STARTTLS, SASL and SSL encryption.
- - Zlib 1.2.3 or higher, for Stream Compression support (XEP-0138). Optional.
- - PAM library. Optional. For Pluggable Authentication Modules (PAM).
- - ImageMagick's Convert program. Optional. For CAPTCHA challenges.
-
-If your system splits packages in libraries and development headers, you must
-install the development packages also.
-
-### 1. Compile and install on *nix systems
-
-To compile ejabberd, execute the following commands. The first one is only
-necessary if your source tree didn't come with a `configure` script (In this
-case you need autoconf installed).
-
- ./autogen.sh
- ./configure
- make
-
-To install ejabberd, run this command with system administrator rights (root
-user):
-
- sudo make install
-
-These commands will:
-
-- Install the configuration files in `/etc/ejabberd/`
-- Install ejabberd binary, header and runtime files in `/lib/ejabberd/`
-- Install the administration script: `/sbin/ejabberdctl`
-- Install ejabberd documentation in `/share/doc/ejabberd/`
-- Create a spool directory: `/var/lib/ejabberd/`
-- Create a directory for log files: `/var/log/ejabberd/`
-
-
-### 2. Start ejabberd
-
-You can use the `ejabberdctl` command line administration script to
-start and stop ejabberd. For example:
-
- ejabberdctl start
-
-
-For detailed information please refer to the ejabberd Installation and
-Operation Guide available online and in the `doc` directory of the source
-tarball.
-
-
-Development
------------
-
-In order to assist in the development of ejabberd, and particularly the
-execution of the test suite, a Vagrant environment is available at
-https://github.com/processone/ejabberd-vagrant-dev.
-
-To start ejabberd in development mode from the repository directory, you can
-type a command like:
-
- EJABBERD_CONFIG_PATH=ejabberd.yml erl -pa ebin -pa deps/*/ebin -pa test -pa deps/elixir/lib/*/ebin/ -s ejabberd
-
-Links
------
-
-- Documentation: http://docs.ejabberd.im
-- Community site: https://www.ejabberd.im
-- ejabberd commercial offering and support: https://www.process-one.net/en/ejabberd
diff --git a/README.md b/README.md
index 100b93820..b410b2ac2 120000..100644
--- a/README.md
+++ b/README.md
@@ -1 +1,174 @@
-README \ No newline at end of file
+ejabberd Community Edition
+==========================
+
+[![Build Status](https://travis-ci.org/processone/ejabberd.svg?branch=master)](https://travis-ci.org/processone/ejabberd) [![Hex version](https://img.shields.io/hexpm/v/ejabberd.svg "Hex version")](https://hex.pm/packages/ejabberd)
+
+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 XMPP 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 and 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:
+
+- **Modularity**
+ - 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).
+ - Component support: interface with networks such as AIM, ICQ and MSN.
+
+
+Quickstart guide
+----------------
+
+### 0. Requirements
+
+To compile ejabberd you need:
+
+ - GNU Make.
+ - GCC.
+ - Libexpat ≥ 1.95.
+ - Libyaml ≥ 0.1.4.
+ - Erlang/OTP ≥ 19.0.
+ - OpenSSL ≥ 1.0.0.
+ - Zlib ≥ 1.2.3, for Stream Compression support (XEP-0138). Optional.
+ - PAM library. Optional. For Pluggable Authentication Modules (PAM).
+ - ImageMagick's Convert program. Optional. For CAPTCHA challenges.
+
+If your system splits packages in libraries and development headers, you must
+install the development packages also.
+
+### 1. Compile and install on *nix systems
+
+To compile ejabberd, execute the following commands. The first one is only
+necessary if your source tree didn't come with a `configure` script (In this
+case you need autoconf installed).
+
+ ./autogen.sh
+ ./configure
+ make
+
+To install ejabberd, run this command with system administrator rights (root
+user):
+
+ sudo make install
+
+These commands will:
+
+- Install the configuration files in `/etc/ejabberd/`
+- Install ejabberd binary, header and runtime files in `/lib/ejabberd/`
+- Install the administration script: `/sbin/ejabberdctl`
+- Install ejabberd documentation in `/share/doc/ejabberd/`
+- Create a spool directory: `/var/lib/ejabberd/`
+- Create a directory for log files: `/var/log/ejabberd/`
+
+
+### 2. Start ejabberd
+
+You can use the `ejabberdctl` command line administration script to
+start and stop ejabberd. For example:
+
+ ejabberdctl start
+
+
+For detailed information please refer to the ejabberd Installation and
+Operation Guide available online and in the `doc` directory of the source
+tarball.
+
+
+Development
+-----------
+
+In order to assist in the development of ejabberd, and particularly the
+execution of the test suite, a Vagrant environment is available at
+https://github.com/processone/ejabberd-vagrant-dev.
+
+To start ejabberd in development mode from the repository directory, you can
+type a command like:
+
+ EJABBERD_CONFIG_PATH=ejabberd.yml erl -pa ebin -pa deps/*/ebin -pa test -pa deps/elixir/lib/*/ebin/ -s ejabberd
+
+Links
+-----
+
+- Documentation: https://docs.ejabberd.im
+- Community site: https://www.ejabberd.im
+- ejabberd commercial offering and support: https://www.process-one.net/en/ejabberd
diff --git a/config/ejabberd.exs b/config/ejabberd.exs
index 85c738ff0..7e1b99016 100644
--- a/config/ejabberd.exs
+++ b/config/ejabberd.exs
@@ -61,7 +61,6 @@ defmodule Ejabberd.ConfigFile do
@opts [
port: 5280,
web_admin: true,
- http_poll: true,
http_bind: true,
captcha: true]
end
diff --git a/config/ejabberd.yml b/config/ejabberd.yml
deleted file mode 100644
index c72e50fe4..000000000
--- a/config/ejabberd.yml
+++ /dev/null
@@ -1,666 +0,0 @@
-###
-### ejabberd configuration file
-###
-###
-
-### The parameters used in this configuration file are explained in more detail
-### in the ejabberd Installation and Operation Guide.
-### Please consult the Guide in case of doubts, it is included with
-### your copy of ejabberd, and is also available online at
-### http://www.process-one.net/en/ejabberd/docs/
-
-### The configuration file is written in YAML.
-### Refer to http://en.wikipedia.org/wiki/YAML for the brief description.
-### However, ejabberd treats different literals as different types:
-###
-### - unquoted or single-quoted strings. They are called "atoms".
-### Example: dog, 'Jupiter', '3.14159', YELLOW
-###
-### - numeric literals. Example: 3, -45.0, .0
-###
-### - quoted or folded strings.
-### Examples of quoted string: "Lizzard", "orange".
-### Example of folded string:
-### > Art thou not Romeo,
-### and a Montague?
-
-### =======
-### LOGGING
-
-##
-## loglevel: Verbosity of log files generated by ejabberd.
-## 0: No ejabberd log at all (not recommended)
-## 1: Critical
-## 2: Error
-## 3: Warning
-## 4: Info
-## 5: Debug
-##
-loglevel: 4
-
-##
-## rotation: Describe how to rotate logs. Either size and/or date can trigger
-## log rotation. Setting count to N keeps N rotated logs. Setting count to 0
-## does not disable rotation, it instead rotates the file and keeps no previous
-## versions around. Setting size to X rotate log when it reaches X bytes.
-## To disable rotation set the size to 0 and the date to ""
-## Date syntax is taken from the syntax newsyslog uses in newsyslog.conf.
-## Some examples:
-## $D0 rotate every night at midnight
-## $D23 rotate every day at 23:00 hr
-## $W0D23 rotate every week on Sunday at 23:00 hr
-## $W5D16 rotate every week on Friday at 16:00 hr
-## $M1D0 rotate on the first day of every month at midnight
-## $M5D6 rotate on every 5th day of the month at 6:00 hr
-##
-log_rotate_size: 10485760
-log_rotate_date: ""
-log_rotate_count: 1
-
-##
-## overload protection: If you want to limit the number of messages per second
-## allowed from error_logger, which is a good idea if you want to avoid a flood
-## of messages when system is overloaded, you can set a limit.
-## 100 is ejabberd's default.
-log_rate_limit: 100
-
-##
-## watchdog_admins: Only useful for developers: if an ejabberd process
-## consumes a lot of memory, send live notifications to these XMPP
-## accounts.
-##
-## watchdog_admins:
-## - "bob@example.com"
-
-
-### ================
-### SERVED HOSTNAMES
-
-##
-## hosts: Domains served by ejabberd.
-## You can define one or several, for example:
-## hosts:
-## - "example.net"
-## - "example.com"
-## - "example.org"
-##
-hosts:
- - "localhost"
-
-##
-## route_subdomains: Delegate subdomains to other XMPP servers.
-## For example, if this ejabberd serves example.org and you want
-## to allow communication with an XMPP server called im.example.org.
-##
-## route_subdomains: s2s
-
-### ===============
-### LISTENING PORTS
-
-##
-## listen: The ports ejabberd will listen on, which service each is handled
-## by and what options to start it with.
-##
-listen:
- -
- port: 5222
- module: ejabberd_c2s
- ##
- ## If TLS is compiled in and you installed a SSL
- ## certificate, specify the full path to the
- ## 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:
- ## - "no_sslv3"
- ## - "no_tlsv1"
- max_stanza_size: 65536
- shaper: c2s_shaper
- access: c2s
- -
- port: 5269
- module: ejabberd_s2s_in
- ##
- ## ejabberd_service: Interact with external components (transports, ...)
- ##
- ## -
- ## port: 8888
- ## module: ejabberd_service
- ## access: all
- ## shaper_rule: fast
- ## ip: "127.0.0.1"
- ## hosts:
- ## "icq.example.org":
- ## password: "secret"
- ## "sms.example.org":
- ## password: "secret"
-
- ##
- ## ejabberd_stun: Handles STUN Binding requests
- ##
- ## -
- ## port: 3478
- ## transport: udp
- ## module: ejabberd_stun
-
- ##
- ## To handle XML-RPC requests that provide admin credentials:
- ##
- ## -
- ## port: 4560
- ## module: ejabberd_xmlrpc
- -
- port: 5280
- module: ejabberd_http
- ## request_handlers:
- ## "/pub/archive": mod_http_fileserver
- web_admin: true
- http_poll: true
- http_bind: true
- ## register: true
- captcha: true
-
-##
-## s2s_use_starttls: Enable STARTTLS + Dialback for S2S connections.
-## Allowed values are: false optional required required_trusted
-## You must specify a certificate file.
-##
-## s2s_use_starttls: optional
-
-##
-## s2s_certfile: Specify a certificate file.
-##
-## s2s_certfile: "/path/to/ssl.pem"
-
-## Custom OpenSSL options
-##
-## s2s_protocol_options:
-## - "no_sslv3"
-## - "no_tlsv1"
-
-##
-## domain_certfile: Specify a different certificate for each served hostname.
-##
-## host_config:
-## "example.org":
-## domain_certfile: "/path/to/example_org.pem"
-## "example.com":
-## domain_certfile: "/path/to/example_com.pem"
-
-##
-## S2S whitelist or blacklist
-##
-## Default s2s policy for undefined hosts.
-##
-## s2s_access: s2s
-
-##
-## Outgoing S2S options
-##
-## Preferred address families (which to try first) and connect timeout
-## in milliseconds.
-##
-## outgoing_s2s_families:
-## - ipv4
-## - ipv6
-## outgoing_s2s_timeout: 10000
-
-### ==============
-### AUTHENTICATION
-
-##
-## auth_method: Method used to authenticate the users.
-## The default method is the internal.
-## If you want to use a different method,
-## comment this line and enable the correct ones.
-##
-auth_method: internal
-
-##
-## Store the plain passwords or hashed for SCRAM:
-## auth_password_format: plain
-## auth_password_format: scram
-##
-## Define the FQDN if ejabberd doesn't detect it:
-## fqdn: "server3.example.com"
-
-##
-## Authentication using external script
-## Make sure the script is executable by ejabberd.
-##
-## auth_method: external
-## extauth_program: "/path/to/authentication/script"
-
-##
-## Authentication using ODBC
-## Remember to setup a database in the next section.
-##
-## auth_method: odbc
-
-##
-## Authentication using PAM
-##
-## auth_method: pam
-## pam_service: "pamservicename"
-
-##
-## Authentication using LDAP
-##
-## auth_method: ldap
-##
-## List of LDAP servers:
-## ldap_servers:
-## - "localhost"
-##
-## Encryption of connection to LDAP servers:
-## ldap_encrypt: none
-## ldap_encrypt: tls
-##
-## Port to connect to on LDAP servers:
-## ldap_port: 389
-## ldap_port: 636
-##
-## LDAP manager:
-## ldap_rootdn: "dc=example,dc=com"
-##
-## Password of LDAP manager:
-## ldap_password: "******"
-##
-## Search base of LDAP directory:
-## ldap_base: "dc=example,dc=com"
-##
-## LDAP attribute that holds user ID:
-## ldap_uids:
-## - "mail": "%u@mail.example.org"
-##
-## LDAP filter:
-## ldap_filter: "(objectClass=shadowAccount)"
-
-##
-## Anonymous login support:
-## auth_method: anonymous
-## anonymous_protocol: sasl_anon | login_anon | both
-## allow_multiple_connections: true | false
-##
-## host_config:
-## "public.example.org":
-## auth_method: anonymous
-## allow_multiple_connections: false
-## anonymous_protocol: sasl_anon
-##
-## To use both anonymous and internal authentication:
-##
-## host_config:
-## "public.example.org":
-## auth_method:
-## - internal
-## - anonymous
-
-### ==============
-### DATABASE SETUP
-
-## ejabberd by default uses the internal Mnesia database,
-## so you do not necessarily need this section.
-## This section provides configuration examples in case
-## you want to use other database backends.
-## Please consult the ejabberd Guide for details on database creation.
-
-##
-## MySQL server:
-##
-## odbc_type: mysql
-## odbc_server: "server"
-## odbc_database: "database"
-## odbc_username: "username"
-## odbc_password: "password"
-##
-## If you want to specify the port:
-## odbc_port: 1234
-
-##
-## PostgreSQL server:
-##
-## odbc_type: pgsql
-## odbc_server: "server"
-## odbc_database: "database"
-## odbc_username: "username"
-## odbc_password: "password"
-##
-## If you want to specify the port:
-## odbc_port: 1234
-##
-## If you use PostgreSQL, have a large database, and need a
-## faster but inexact replacement for "select count(*) from users"
-##
-## pgsql_users_number_estimate: true
-
-##
-## ODBC compatible or MSSQL server:
-##
-## odbc_type: odbc
-## odbc_server: "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"
-
-##
-## Number of connections to open to the database for each virtual host
-##
-## odbc_pool_size: 10
-
-##
-## Interval to make a dummy SQL request to keep the connections to the
-## database alive. Specify in seconds: for example 28800 means 8 hours
-##
-## odbc_keepalive_interval: undefined
-
-### ===============
-### TRAFFIC SHAPERS
-
-shaper:
- ##
- ## The "normal" shaper limits traffic speed to 1000 B/s
- ##
- normal: 1000
-
- ##
- ## The "fast" shaper limits traffic speed to 50000 B/s
- ##
- fast: 50000
-
-##
-## This option specifies the maximum number of elements in the queue
-## of the FSM. Refer to the documentation for details.
-##
-max_fsm_queue: 1000
-
-###. ====================
-###' ACCESS CONTROL LISTS
-acl:
- ##
- ## The 'admin' ACL grants administrative privileges to XMPP accounts.
- ## You can put here as many accounts as you want.
- ##
- ## admin:
- ## user:
- ## - "aleksey": "localhost"
- ## - "ermine": "example.org"
- ##
- ## Blocked users
- ##
- ## blocked:
- ## user:
- ## - "baduser": "example.org"
- ## - "test"
-
- ## Local users: don't modify this.
- ##
- local:
- user_regexp: ""
-
- ##
- ## More examples of ACLs
- ##
- ## jabberorg:
- ## server:
- ## - "jabber.org"
- ## aleksey:
- ## user:
- ## - "aleksey": "jabber.ru"
- ## test:
- ## user_regexp: "^test"
- ## user_glob: "test*"
-
- ##
- ## Loopback network
- ##
- loopback:
- ip:
- - "127.0.0.0/8"
-
- ##
- ## Bad XMPP servers
- ##
- ## bad_servers:
- ## server:
- ## - "xmpp.zombie.org"
- ## - "xmpp.spam.com"
-
-##
-## Define specific ACLs in a virtual host.
-##
-## host_config:
-## "localhost":
-## acl:
-## admin:
-## user:
-## - "bob-local": "localhost"
-
-### ============
-### ACCESS RULES
-access:
- ## Maximum number of simultaneous sessions allowed for a single user:
- max_user_sessions:
- all: 10
- ## Maximum number of offline messages that users can have:
- max_user_offline_messages:
- admin: 5000
- all: 100
- ## This rule allows access only for local users:
- local:
- local: allow
- ## Only non-blocked users can use c2s connections:
- c2s:
- blocked: deny
- all: allow
- ## For C2S connections, all users except admins use the "normal" shaper
- c2s_shaper:
- admin: none
- all: normal
- ## All S2S connections use the "fast" shaper
- s2s_shaper:
- all: fast
- ## Only admins can send announcement messages:
- announce:
- admin: allow
- ## Only admins can use the configuration interface:
- configure:
- admin: allow
- ## Admins of this server are also admins of the MUC service:
- muc_admin:
- admin: allow
- ## Only accounts of the local ejabberd server can create rooms:
- muc_create:
- local: allow
- ## All users are allowed to use the MUC service:
- muc:
- all: allow
- ## Only accounts on the local ejabberd server can create Pubsub nodes:
- pubsub_createnode:
- local: allow
- ## In-band registration allows registration of any possible username.
- ## To disable in-band registration, replace 'allow' with 'deny'.
- register:
- all: allow
- ## Only allow to register from localhost
- trusted_network:
- loopback: allow
- ## Do not establish S2S connections with bad servers
- ## s2s:
- ## bad_servers: deny
- ## all: allow
-
-## By default the frequency of account registrations from the same IP
-## is limited to 1 account every 10 minutes. To disable, specify: infinity
-## registration_timeout: 600
-
-##
-## Define specific Access Rules in a virtual host.
-##
-## host_config:
-## "localhost":
-## access:
-## c2s:
-## admin: allow
-## all: deny
-## register:
-## all: deny
-
-### ================
-### DEFAULT LANGUAGE
-
-##
-## language: Default language used for server messages.
-##
-language: "en"
-
-##
-## Set a different default language in a virtual host.
-##
-## host_config:
-## "localhost":
-## language: "ru"
-
-### =======
-### CAPTCHA
-
-##
-## Full path to a script that generates the image.
-##
-## captcha_cmd: "/lib/ejabberd/priv/bin/captcha.sh"
-
-##
-## Host for the URL and port where ejabberd listens for CAPTCHA requests.
-##
-## captcha_host: "example.org:5280"
-
-##
-## Limit CAPTCHA calls per minute for JID/IP to avoid DoS.
-##
-## captcha_limit: 5
-
-### =======
-### MODULES
-
-##
-## Modules enabled in all ejabberd virtual hosts.
-##
-modules:
- mod_adhoc: {}
- ## mod_admin_extra: {}
- mod_announce: # recommends mod_adhoc
- access: announce
- mod_blocking: {} # requires mod_privacy
- mod_caps: {}
- mod_carboncopy: {}
- mod_client_state:
- queue_chat_states: true
- queue_presence: false
- mod_configure: {} # requires mod_adhoc
- mod_disco: {}
- ## mod_echo: {}
- mod_http_bind: {}
- ## mod_http_fileserver:
- ## docroot: "/var/www"
- ## accesslog: "/var/log/ejabberd/access.log"
- mod_last: {}
- mod_muc:
- ## host: "conference.@HOST@"
- access: muc
- access_create: muc_create
- access_persistent: muc_create
- access_admin: muc_admin
- ## mod_muc_log: {}
- mod_offline:
- access_max_user_messages: max_user_offline_messages
- mod_ping: {}
- ## mod_pres_counter:
- ## count: 5
- ## interval: 60
- mod_privacy: {}
- mod_private: {}
- ## mod_proxy65: {}
- mod_pubsub:
- access_createnode: pubsub_createnode
- ## reduces resource comsumption, but XEP incompliant
- ignore_pep_from_offline: true
- ## XEP compliant, but increases resource comsumption
- ## ignore_pep_from_offline: false
- last_item_cache: false
- plugins:
- - "flat"
- - "hometree"
- - "pep" # pep requires mod_caps
- mod_register:
- ##
- ## Protect In-Band account registrations with CAPTCHA.
- ##
- ## captcha_protected: true
-
- ##
- ## Set the minimum informational entropy for passwords.
- ##
- ## password_strength: 32
-
- ##
- ## After successful registration, the user receives
- ## a message with this subject and body.
- ##
- welcome_message:
- subject: "Welcome!"
- body: |-
- Hi.
- Welcome to this XMPP server.
-
- ##
- ## When a user registers, send a notification to
- ## these XMPP accounts.
- ##
- ## registration_watchers:
- ## - "admin1@example.org"
-
- ##
- ## Only clients in the server machine can register accounts
- ##
- ip_access: trusted_network
-
- ##
- ## Local c2s or remote s2s users cannot register accounts
- ##
- ## access_from: deny
-
- access: register
- mod_roster: {}
- mod_shared_roster: {}
- mod_stats: {}
- mod_time: {}
- mod_vcard: {}
- mod_version: {}
-
-##
-## Enable modules with custom options in a specific virtual host
-##
-## host_config:
-## "localhost":
-## modules:
-## mod_echo:
-## host: "mirror.localhost"
-
-##
-## Enable modules management via ejabberdctl for installation and
-## uninstallation of public/private contributed modules
-## (enabled by default)
-##
-
-allow_contrib_modules: true
-
-### Local Variables:
-### mode: yaml
-### End:
-### vim: set filetype=yaml tabstop=8
diff --git a/configure.ac b/configure.ac
index f83c08024..81f0167ae 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3,7 +3,7 @@
AC_PREREQ(2.53)
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 0.0` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
-REQUIRE_ERLANG_MIN="6.4 (Erlang/OTP 17.5)"
+REQUIRE_ERLANG_MIN="8.0 (Erlang/OTP 19.0)"
REQUIRE_ERLANG_MAX="100.0.0 (No Max)"
AC_CONFIG_MACRO_DIR([m4])
@@ -109,10 +109,10 @@ AC_ARG_ENABLE(mssql,
esac],[db_type=generic])
AC_ARG_ENABLE(all,
-[AC_HELP_STRING([--enable-all], [same as --enable-odbc --enable-mysql --enable-pgsql --enable-sqlite --enable-pam --enable-zlib --enable-riak --enable-redis --enable-elixir --enable-iconv --enable-stun --enable-sip --enable-debug --enable-tools (useful for Dialyzer checks, default: no)])],
+[AC_HELP_STRING([--enable-all], [same as --enable-odbc --enable-mysql --enable-pgsql --enable-sqlite --enable-pam --enable-zlib --enable-riak --enable-redis --enable-elixir --enable-stun --enable-sip --enable-debug --enable-tools (useful for Dialyzer checks, default: no)])],
[case "${enableval}" in
- yes) odbc=true mysql=true pgsql=true sqlite=true pam=true zlib=true riak=true redis=true elixir=true iconv=true stun=true sip=true debug=true tools=true ;;
- no) odbc=false mysql=false pgsql=false sqlite=false pam=false zlib=false riak=false redis=false elixir=false iconv=false stun=false sip=false debug=false tools=false ;;
+ yes) odbc=true mysql=true pgsql=true sqlite=true pam=true zlib=true riak=true redis=true elixir=true stun=true sip=true debug=true tools=true ;;
+ no) odbc=false mysql=false pgsql=false sqlite=false pam=false zlib=false riak=false redis=false elixir=false stun=false sip=false debug=false tools=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;;
esac],[])
@@ -196,14 +196,6 @@ AC_ARG_ENABLE(elixir,
*) AC_MSG_ERROR(bad value ${enableval} for --enable-elixir) ;;
esac],[if test "x$elixir" = "x"; then elixir=false; fi])
-AC_ARG_ENABLE(iconv,
-[AC_HELP_STRING([--enable-iconv], [enable iconv support (default: no)])],
-[case "${enableval}" in
- yes) iconv=true ;;
- no) iconv=false ;;
- *) AC_MSG_ERROR(bad value ${enableval} for --enable-iconv) ;;
-esac],[if test "x$iconv" = "x"; then iconv=false; fi])
-
AC_ARG_ENABLE(debug,
[AC_HELP_STRING([--enable-debug], [enable debug information (default: yes)])],
[case "${enableval}" in
@@ -307,7 +299,6 @@ AC_SUBST(zlib)
AC_SUBST(riak)
AC_SUBST(redis)
AC_SUBST(elixir)
-AC_SUBST(iconv)
AC_SUBST(stun)
AC_SUBST(sip)
AC_SUBST(debug)
diff --git a/ejabberd.yml.example b/ejabberd.yml.example
index fd5c3e25e..2508f3fb0 100644
--- a/ejabberd.yml.example
+++ b/ejabberd.yml.example
@@ -36,14 +36,33 @@ log_rotate_count: 1
log_rate_limit: 100
certfiles:
- - "/etc/letsencrypt/live/*/*.pem"
+ - "/etc/letsencrypt/live/localhost/fullchain.pem"
+ - "/etc/letsencrypt/live/localhost/privkey.pem"
+
+define_macro:
+ # TLS options for client not being able to use modern ciphers (Windows XP+, Android 3.0+)
+ CIPHERS_INTERMEDIATE: "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"
+ PROTOCOL_OPTIONS_INTERMEDIATE:
+ - "no_sslv2"
+ - "no_sslv3"
+
+ # TLS options for client able to use modern ciphers (Windows 7+, Android 5.0+)
+ CIPHERS_MODERN: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"
+ PROTOCOL_OPTIONS_MODERN:
+ - "no_sslv2"
+ - "no_sslv3"
+ - "no_tlsv1"
+ - "no_tlsv1_1"
+
+c2s_ciphers: CIPHERS_INTERMEDIATE
+c2s_protocol_options: PROTOCOL_OPTIONS_INTERMEDIATE
listen:
-
port: 5222
ip: "::"
module: ejabberd_c2s
- max_stanza_size: 65536
+ max_stanza_size: 262144
shaper: c2s_shaper
access: c2s
starttls_required: true
@@ -51,6 +70,7 @@ listen:
port: 5269
ip: "::"
module: ejabberd_s2s_in
+ max_stanza_size: 524288
-
port: 5443
ip: "::"
@@ -62,7 +82,14 @@ listen:
"/ws": ejabberd_http_ws
web_admin: true
captcha: true
+ ciphers: CIPHERS_INTERMEDIATE
+ protocol_options: PROTOCOL_OPTIONS_INTERMEDIATE
tls: true
+ -
+ port: 5280
+ ip: "::"
+ module: ejabberd_http
+ web_admin: true
s2s_use_starttls: optional
@@ -73,7 +100,6 @@ acl:
ip:
- "127.0.0.0/8"
- "::1/128"
- - "::FFFF:127.0.0.1/128"
access_rules:
local:
@@ -177,13 +203,16 @@ modules:
mod_ping: {}
mod_privacy: {}
mod_private: {}
+ mod_proxy65:
+ access: local
+ max_connections: 5
mod_pubsub:
access_createnode: pubsub_createnode
plugins:
- "flat"
- "pep"
force_node_config:
- ## Comment out the following lines to enable OMEMO support
+ ## Change from "whitelist" to "open" to enable OMEMO support
## See https://github.com/processone/ejabberd/issues/2425
"eu.siacs.conversations.axolotl.*":
access_model: whitelist
diff --git a/include/mod_carboncopy.hrl b/include/ejabberd_stacktrace.hrl
index f02f3e8f3..470abed15 100644
--- a/include/mod_carboncopy.hrl
+++ b/include/ejabberd_stacktrace.hrl
@@ -18,10 +18,10 @@
%%%
%%%----------------------------------------------------------------------
--type matchspec_atom() :: '_' | '$1' | '$2' | '$3' | '$4'.
--record(carboncopy, {us :: {binary(), binary()} | matchspec_atom(),
- resource :: binary() | matchspec_atom(),
- version :: binary() | matchspec_atom(),
- node = node() :: node() | matchspec_atom()}).
-
--define(CARBONCOPY_CACHE, carboncopy_cache).
+-ifdef(DEPRECATED_GET_STACKTRACE).
+-define(EX_RULE(Class, Reason, Stack), Class:Reason:Stack).
+-define(EX_STACK(Stack), Stack).
+-else.
+-define(EX_RULE(Class, Reason, _), Class:Reason).
+-define(EX_STACK(_), erlang:get_stacktrace()).
+-endif.
diff --git a/include/mod_muc_room.hrl b/include/mod_muc_room.hrl
index 35bdb6ff7..65f3c5479 100644
--- a/include/mod_muc_room.hrl
+++ b/include/mod_muc_room.hrl
@@ -22,8 +22,6 @@
-define(SETS, gb_sets).
--define(DICT, dict).
-
-record(lqueue,
{
queue :: p1_queue:queue(),
@@ -105,13 +103,13 @@
access = {none,none,none,none} :: {atom(), atom(), atom(), atom()},
jid = #jid{} :: jid(),
config = #config{} :: config(),
- users = (?DICT):new() :: dict:dict(),
- subscribers = (?DICT):new() :: dict:dict(),
- subscriber_nicks = (?DICT):new() :: dict:dict(),
+ users = #{} :: map(),
+ subscribers = #{} :: map(),
+ subscriber_nicks = #{} :: map(),
last_voice_request_time = treap:empty() :: treap:treap(),
- robots = (?DICT):new() :: dict:dict(),
- nicks = (?DICT):new() :: dict:dict(),
- affiliations = (?DICT):new() :: dict:dict(),
+ robots = #{} :: map(),
+ nicks = #{} :: map(),
+ affiliations = #{} :: map(),
history :: lqueue(),
subject = [] :: [text()],
subject_author = <<"">> :: binary(),
diff --git a/include/pubsub.hrl b/include/pubsub.hrl
index 923f5f5a9..7a72c6955 100644
--- a/include/pubsub.hrl
+++ b/include/pubsub.hrl
@@ -27,7 +27,7 @@
%% this is currently a hard limit.
%% Would be nice to have it configurable.
--define(MAX_PAYLOAD_SIZE, 60000).
+-define(MAX_PAYLOAD_SIZE, 250000).
%% -------------------------------
%% Pubsub types
diff --git a/lib/ejabberd/config/config.ex b/lib/ejabberd/config/config.ex
index 4d1270bc1..4d1728d16 100644
--- a/lib/ejabberd/config/config.ex
+++ b/lib/ejabberd/config/config.ex
@@ -23,7 +23,7 @@ defmodule Ejabberd.Config do
# Could be also possible to interrupt the compilation&execution by throwing
# an exception if necessary.
def __before_compile__(_env) do
- get_modules_parsed_in_order
+ get_modules_parsed_in_order()
|> EjabberdModule.validate
|> EjabberdLogger.log_errors
end
@@ -48,7 +48,7 @@ defmodule Ejabberd.Config do
Returns a list with all the opts, formatted for ejabberd.
"""
def get_ejabberd_opts do
- get_general_opts
+ get_general_opts()
|> Dict.put(:modules, get_modules_parsed_in_order())
|> Dict.put(:listeners, get_listeners_parsed_in_order())
|> Ejabberd.Config.OptsFormatter.format_opts_for_ejabberd
diff --git a/lib/ejabberd/config/ejabberd_module.ex b/lib/ejabberd/config/ejabberd_module.ex
index 4de9a302e..6a74fe460 100644
--- a/lib/ejabberd/config/ejabberd_module.ex
+++ b/lib/ejabberd/config/ejabberd_module.ex
@@ -12,7 +12,6 @@ defmodule Ejabberd.Config.EjabberdModule do
defstruct [:module, :attrs]
alias Ejabberd.Config.EjabberdModule
- alias Ejabberd.Config.Attr
alias Ejabberd.Config.Validation
@doc """
diff --git a/lib/ejabberd/config/logger/ejabberd_logger.ex b/lib/ejabberd/config/logger/ejabberd_logger.ex
index 270fbfaa6..90970ba73 100644
--- a/lib/ejabberd/config/logger/ejabberd_logger.ex
+++ b/lib/ejabberd/config/logger/ejabberd_logger.ex
@@ -21,7 +21,7 @@ defmodule Ejabberd.Config.EjabberdLogger do
defp do_log_errors({:attribute, errors}), do: Enum.each errors, &log_attribute_error/1
defp do_log_errors({:dependency, errors}), do: Enum.each errors, &log_dependency_error/1
- defp log_attribute_error({{attr_name, val}, :attr_not_supported}), do:
+ defp log_attribute_error({{attr_name, _val}, :attr_not_supported}), do:
IO.puts "[ WARN ] Annotation @#{attr_name} is not supported."
defp log_attribute_error({{attr_name, val}, :type_not_supported}), do:
diff --git a/lib/ejabberd/config/validator/validation.ex b/lib/ejabberd/config/validator/validation.ex
index 2fe00361a..af582676e 100644
--- a/lib/ejabberd/config/validator/validation.ex
+++ b/lib/ejabberd/config/validator/validation.ex
@@ -7,9 +7,7 @@ defmodule Ejabberd.Config.Validation do
@type mod_validation_result :: {:ok, EjabberdModule.t} | {:error, EjabberdModule.t, map}
alias Ejabberd.Config.EjabberdModule
- alias Ejabberd.Config.Attr
alias Ejabberd.Config.Validator
- alias Ejabberd.Config.ValidatorUtility
@doc """
Given a module or a list of modules it runs validators on them
diff --git a/lib/ejabberd/config/validator/validator_attrs.ex b/lib/ejabberd/config/validator/validator_attrs.ex
index 94117ab21..6a85c068d 100644
--- a/lib/ejabberd/config/validator/validator_attrs.ex
+++ b/lib/ejabberd/config/validator/validator_attrs.ex
@@ -18,7 +18,7 @@ defmodule Ejabberd.Config.Validator.Attrs do
def validate({modules, mod, errors}) do
errors = Enum.reduce mod.attrs, errors, fn(attr, err) ->
case Attr.validate(attr) do
- {:ok, attr} -> err
+ {:ok, _attr} -> err
{:error, attr, cause} -> put_error(err, :attribute, {attr, cause})
end
end
diff --git a/lib/mix/tasks/deps.tree.ex b/lib/mix/tasks/deps.tree.ex
index 94cb85a50..9937270cd 100644
--- a/lib/mix/tasks/deps.tree.ex
+++ b/lib/mix/tasks/deps.tree.ex
@@ -40,7 +40,7 @@ defmodule Mix.Tasks.Ejabberd.Deps.Tree do
end
end
- defp build_dependency_tree(mods, mod, []), do: %{module: mod, dependency: []}
+ defp build_dependency_tree(_mods, mod, []), do: %{module: mod, dependency: []}
defp build_dependency_tree(mods, mod, deps) when is_list(deps) do
dependencies = Enum.map deps, fn dep ->
dep_deps = get_dependencies_of_mod(mods, dep)
@@ -65,7 +65,7 @@ defmodule Mix.Tasks.Ejabberd.Deps.Tree do
defp keep_only_mods_not_used_as_dep(mods, mods_used_as_dep) do
Enum.filter mods, fn %{module: mod} ->
- not mod in mods_used_as_dep
+ not (mod in mods_used_as_dep)
end
end
diff --git a/lib/mod_presence_demo.ex b/lib/mod_presence_demo.ex
index 09bf58405..3ce512c3d 100644
--- a/lib/mod_presence_demo.ex
+++ b/lib/mod_presence_demo.ex
@@ -17,4 +17,13 @@ defmodule ModPresenceDemo do
info('Receive presence for #{user}')
:none
end
+
+ def depends(_host, _opts) do
+ []
+ end
+
+ def mod_options(_host) do
+ []
+ end
+
end
diff --git a/mix.exs b/mix.exs
index f96f8c254..005731989 100644
--- a/mix.exs
+++ b/mix.exs
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
def project do
[app: :ejabberd,
- version: "18.6.0",
+ version: "18.12.1",
description: description(),
elixir: "~> 1.4",
elixirc_paths: ["lib"],
@@ -29,7 +29,7 @@ defmodule Ejabberd.Mixfile do
included_applications: [:lager, :mnesia, :inets, :p1_utils, :cache_tab,
:fast_tls, :stringprep, :fast_xml, :xmpp,
:stun, :fast_yaml, :esip, :jiffy, :p1_oauth2,
- :eimp, :base64url, :jose]
+ :eimp, :base64url, :jose, :pkix]
++ cond_apps()]
end
@@ -42,10 +42,19 @@ defmodule Ejabberd.Mixfile do
end
end
+ defp if_version_above(ver, okResult) do
+ if :erlang.system_info(:otp_release) > ver do
+ okResult
+ else
+ []
+ end
+ end
+
defp erlc_options do
# Use our own includes + includes from all dependencies
includes = ["include"] ++ deps_include(["fast_xml", "xmpp", "p1_utils"])
[:debug_info, {:d, :ELIXIR_ENABLED}] ++ cond_options() ++ Enum.map(includes, fn(path) -> {:i, path} end) ++
+ if_version_above('20', [{:d, :DEPRECATED_GET_STACKTRACE}]) ++
if_function_exported(:crypto, :strong_rand_bytes, 1, [{:d, :STRONG_RAND_BYTES}]) ++
if_function_exported(:rand, :uniform, 1, [{:d, :RAND_UNIFORM}]) ++
if_function_exported(:gb_sets, :iterator_from, 2, [{:d, :GB_SETS_ITERATOR_FROM}]) ++
@@ -58,7 +67,7 @@ defmodule Ejabberd.Mixfile do
end
defp deps do
- [{:lager, "~> 3.4.0"},
+ [{:lager, "~> 3.6.0"},
{:p1_utils, "~> 1.0"},
{:fast_xml, "~> 1.1"},
{:xmpp, "~> 1.2"},
@@ -72,7 +81,8 @@ defmodule Ejabberd.Mixfile do
{:p1_pgsql, "~> 1.1"},
{:jiffy, "~> 0.14.7"},
{:p1_oauth2, "~> 0.6.1"},
- {:distillery, "~> 1.0"},
+ {:distillery, "~> 2.0"},
+ {:pkix, "~> 1.0"},
{:ex_doc, ">= 0.0.0", only: :dev},
{:eimp, "~> 1.0"},
{:base64url, "~> 0.0.1"},
@@ -93,11 +103,8 @@ defmodule Ejabberd.Mixfile do
{config(:riak), {:riakc, "~> 2.4"}},
{config(:redis), {:eredis, "~> 1.0"}},
{config(:zlib), {:ezlib, "~> 1.0"}},
- {config(:iconv), {:iconv, "~> 1.0"}},
{config(:pam), {:epam, "~> 1.0"}},
- {config(:tools), {:luerl, "~> 0.3.1"}},
- {config(:tools), {:meck, "~> 0.8.4"}},
- {config(:tools), {:moka, github: "processone/moka", tag: "1.0.5c"}}], do:
+ {config(:tools), {:luerl, "~> 0.3.1"}}], do:
dep
end
@@ -106,8 +113,7 @@ defmodule Ejabberd.Mixfile do
{config(:mysql), :p1_mysql},
{config(:pgsql), :p1_pgsql},
{config(:sqlite), :sqlite3},
- {config(:zlib), :ezlib},
- {config(:iconv), :iconv}], do:
+ {config(:zlib), :ezlib}], do:
app
end
@@ -125,7 +131,7 @@ defmodule Ejabberd.Mixfile do
defp vars do
case :file.consult("vars.config") do
{:ok,config} -> config
- _ -> [zlib: true, iconv: false]
+ _ -> [zlib: true]
end
end
diff --git a/mix.lock b/mix.lock
index 372d85d7a..1b3d4bcae 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,35 +1,36 @@
%{
+ "artificery": {:hex, :artificery, "0.2.6", "f602909757263f7897130cbd006b0e40514a541b148d366ad65b89236b93497a", [:mix], [], "hexpm"},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
- "cache_tab": {:hex, :cache_tab, "1.0.14", "e68d24789ff596a7cb4f08780f72a725f3f18e93dee486559b261df904234871", [:rebar3], [{:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
- "distillery": {:hex, :distillery, "1.5.3", "b2f4fc34ec71ab4f1202a796f9290e068883b042319aa8c9aa45377ecac8597a", [:mix], [], "hexpm"},
- "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"},
- "eimp": {:hex, :eimp, "1.0.6", "087fc92daf7b03bac4aada8ea6063d9034d4b9088be24e050ff73323c8444a04", [:rebar3], [{:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
+ "cache_tab": {:hex, :cache_tab, "1.0.17", "0020e1036d7074d83a71be28b329ceb3e7f9d41cc5a8529b06c32ce4d8ee4995", [:rebar3], [{:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
+ "distillery": {:hex, :distillery, "2.0.12", "6e78fe042df82610ac3fa50bd7d2d8190ad287d120d3cd1682d83a44e8b34dfb", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm"},
+ "earmark": {:hex, :earmark, "1.3.0", "17f0c38eaafb4800f746b457313af4b2442a8c2405b49c645768680f900be603", [:mix], [], "hexpm"},
+ "eimp": {:hex, :eimp, "1.0.9", "daf0d2904be3ef5e4128d946e158113cdb4d52555023746d29b83ce86b531f3c", [:rebar3], [{:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"epam": {:hex, :epam, "1.0.4", "2a5e40cbf9b2cf41df515782894c3b33c81b8ad32fff2fc847c3f725071dfaed", [:rebar3], [], "hexpm"},
"eredis": {:hex, :eredis, "1.1.0", "8d8d74496f35216679b97726b75fb1c8715e99dd7f3ef9f9824a2264c3e0aac0", [:rebar3], [], "hexpm"},
- "esip": {:hex, :esip, "1.0.24", "c14efd0817012721dad3d1c36816e4d113adc86d185503f8c08f7faa11082018", [:rebar3], [{:fast_tls, "1.0.23", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.0.23", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm"},
- "ex_doc": {:hex, :ex_doc, "0.18.3", "f4b0e4a2ec6f333dccf761838a4b253d75e11f714b85ae271c9ae361367897b7", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
+ "esip": {:hex, :esip, "1.0.27", "13a94542b659a9b3e4e013aedaf2f6a92de53d35945902d693657a67c6955b83", [:rebar3], [{:fast_tls, "1.0.26", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.0.26", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm"},
+ "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ezlib": {:hex, :ezlib, "1.0.4", "2434e4bb549cb060d5ac02261ba48fbe1a69b2ae4e1bf7485a3b27b3f3ec618d", [:rebar3], [], "hexpm"},
- "fast_tls": {:hex, :fast_tls, "1.0.23", "5fd44b84b60f1caeb82270aae842599997e2ef70a628c954e494a6613cc6f026", [:rebar3], [{:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
- "fast_xml": {:hex, :fast_xml, "1.1.32", "0a82d84c35676c7b9adec41f96f36dd0e85dfac209ebd23be2267a20228320a9", [:rebar3], [{:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
- "fast_yaml": {:hex, :fast_yaml, "1.0.15", "55a8ff117e2bb44fda5ca96c53473799f864c865dc63a3b598cb626001207cab", [:rebar3], [{:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
+ "fast_tls": {:hex, :fast_tls, "1.0.26", "38d78859ca56e8600aca3ef73137582a279a280d71f7581c64a1eddbde38accb", [:rebar3], [{:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
+ "fast_xml": {:hex, :fast_xml, "1.1.34", "d76fc639d3607a44c4f0fb2dfdee1067b6c37b02b39706d8f067cb77eb2f6016", [:rebar3], [{:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
+ "fast_yaml": {:hex, :fast_yaml, "1.0.17", "e945ef64e0cb7c311c7b42804dbe32a24e13a2afc0ffe249b7e0f9f9ac08e176", [:rebar3], [{:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm"},
"hamcrest": {:hex, :basho_hamcrest, "0.4.1", "fb7b2c92d252a1e9db936750b86089addaebeb8f87967fb4bbdda61e8863338e", [:make, :mix, :rebar3], [], "hexpm"},
- "iconv": {:hex, :iconv, "1.0.8", "772a19153e9546b9f17206931ece321220feccfd0b3f5a5509dc92108326c2f9", [:rebar3], [{:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"jiffy": {:hex, :jiffy, "0.14.13", "225a9a35e26417832c611526567194b4d3adc4f0dfa5f2f7008f4684076f2a01", [:rebar3], [], "hexpm"},
"jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
- "lager": {:hex, :lager, "3.4.2", "150b9a17b23ae6d3265cc10dc360747621cf217b7a22b8cddf03b2909dbf7aa5", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"},
+ "lager": {:hex, :lager, "3.6.7", "2fbf823944caa0fc10df5ec13f3f047524a249bb32f0d801b7900c9610264286", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"},
"luerl": {:hex, :luerl, "0.3.1", "5412807630aac1aaf59ffe5a1bc09259c447b4faeb1d3fe2d4ef41b87676cb04", [:rebar3], [], "hexpm"},
- "meck": {:hex, :meck, "0.8.10", "455aaef8403be46752272206613e7a15467c014d40994b22fb54cde4d1ff7075", [:rebar3], [], "hexpm"},
- "moka": {:git, "https://github.com/processone/moka.git", "3eed3a6dd7dedb70a6cd18f86c7561a18626eb3b", [tag: "1.0.5c"]},
- "p1_mysql": {:hex, :p1_mysql, "1.0.6", "1fb48a907a7fe214a78be15a08f8ebfae2db424c4d9886891a298a395cc3afce", [:rebar3], [], "hexpm"},
+ "makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
+ "makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
+ "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"},
+ "p1_mysql": {:hex, :p1_mysql, "1.0.8", "34ed5fe2f0e16a6ee5805c0c6c1d30ffbc4c5c9753197cdf384ee6e82c57b506", [:rebar3], [], "hexpm"},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.3", "fbd91ba86bd7f03d2a4f6e62affa86bab9930abfd6b473d61eefb148f246cd46", [:rebar3], [], "hexpm"},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.6", "631004602b06ca6f55d759001f180185685c7097e583f3b0f7dd9b8e05ba5db1", [:rebar3], [], "hexpm"},
- "p1_utils": {:hex, :p1_utils, "1.0.12", "4304223a2b78d8031e980ae18d6b6d83f764cf2a91c779b2df7cbdfde73acbf8", [:rebar3], [], "hexpm"},
+ "p1_utils": {:hex, :p1_utils, "1.0.13", "176577cafb54a8c2fdc0a7fc62b9a21ab0f66588f4062792cd9e65f3e500bfdb", [:rebar3], [], "hexpm"},
+ "pkix": {:hex, :pkix, "1.0.0", "d88658eccc30227e929efa91c6ca6a4d2b4d40b4db3635ebd6ed9e246ecfcf82", [:rebar3], [], "hexpm"},
"riak_pb": {:hex, :riak_pb, "2.3.2", "48ffbf66dbb3f136ab9a7134bac4e496754baa5ef58c4f50a61326736d996390", [:make, :mix, :rebar3], [{:hamcrest, "~> 0.4.1", [hex: :basho_hamcrest, repo: "hexpm", optional: false]}], "hexpm"},
"riakc": {:hex, :riakc, "2.5.3", "6132d9e687a0dfd314b2b24c4594302ca8b55568a5d733c491d8fb6cd4004763", [:make, :mix, :rebar3], [{:riak_pb, "~> 2.3", [hex: :riak_pb, repo: "hexpm", optional: false]}], "hexpm"},
- "samerlib": {:git, "https://github.com/processone/samerlib", "fbbba035b1548ac4e681df00d61bf609645333a0", [tag: "0.8.0c"]},
"sqlite3": {:hex, :sqlite3, "1.1.6", "4ea71af0b45908b5f02c9b09e4c87177039ef404f20accb35049cd8924cc417c", [:rebar3], [], "hexpm"},
- "stringprep": {:hex, :stringprep, "1.0.12", "3364897b9a376b2fb5e429944fd34ca0b562b44c9e5acf4e0299564371a6fbef", [:rebar3], [{:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
- "stun": {:hex, :stun, "1.0.23", "79982f6a26dc65b58bb24082e8bbfba83a5228ddd6407753e9c3092fb45ba916", [:rebar3], [{:fast_tls, "1.0.23", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
- "xmpp": {:hex, :xmpp, "1.2.2", "1713ec401c538daf0276e799e7611ff71cf05dc22f53a52550f5c8cff82d09a5", [:rebar3], [{:fast_xml, "1.1.32", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "1.0.12", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm"},
+ "stringprep": {:hex, :stringprep, "1.0.14", "230a2d1c576bba168749d653fd6681166d02431ef07161a918444f3edb478ad0", [:rebar3], [{:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
+ "stun": {:hex, :stun, "1.0.26", "87b05229d0519f0db5c6b67b5c25ed3b79766beb96eba83d29bde4cae9e702e4", [:rebar3], [{:fast_tls, "1.0.26", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
+ "xmpp": {:hex, :xmpp, "1.2.8", "c506ea4c7e4b8d042654d54b080f4b6b4135c93658d7e156968a073c5b5f99a1", [:rebar3], [{:ezlib, "1.0.4", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "1.0.26", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "1.1.34", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "1.0.14", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm"},
}
diff --git a/rebar.config b/rebar.config
index 29a6b0e11..f413b991a 100644
--- a/rebar.config
+++ b/rebar.config
@@ -18,23 +18,23 @@
%%%
%%%----------------------------------------------------------------------
-{deps, [{lager, ".*", {git, "https://github.com/erlang-lager/lager",
- {tag, {if_version_above, "17", "3.6.5", "3.2.1"}}}},
- {p1_utils, ".*", {git, "https://github.com/processone/p1_utils", "6ff85e8"}},
- {cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.14"}}},
- {fast_tls, ".*", {git, "https://github.com/processone/fast_tls", "f36ea5b74526c2c1c9c38f8d473168d95804f59d"}},
- {stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.12"}}},
- {fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.32"}}},
- {xmpp, ".*", {git, "https://github.com/processone/xmpp", "8d85c4b"}},
- {fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.15"}}},
+{deps, [{lager, ".*", {git, "https://github.com/erlang-lager/lager", "3.6.7"}},
+ {p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.13"}}},
+ {cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.17"}}},
+ {fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.26"}}},
+ {stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.14"}}},
+ {fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.34"}}},
+ {xmpp, ".*", {git, "https://github.com/processone/xmpp", "af3a46e918cbad64a57477937444cfd11641b4d9"}},
+ {fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.17"}}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.3"}}},
+ {pkix, ".*", {git, "https://github.com/processone/pkix", {tag, "1.0.0"}}},
{jose, ".*", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.8.4"}}},
- {eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.6"}}},
- {if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.23"}}}},
- {if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.24"}}}},
+ {eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.9"}}},
+ {if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.26"}}}},
+ {if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.27"}}}},
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
- {tag, "1.0.6"}}}},
+ {tag, "1.0.8"}}}},
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
{tag, "1.1.6"}}}},
{if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
@@ -51,12 +51,8 @@
%% TODO: When modules are fully migrated to new structure and mix, we will not need anymore rebar_elixir_plugin
{if_not_rebar3, {if_var_true, elixir, {rebar_elixir_plugin, ".*",
{git, "https://github.com/processone/rebar_elixir_plugin", "0.1.0"}}}},
- {if_var_true, iconv, {iconv, ".*", {git, "https://github.com/processone/iconv",
- {tag, "1.0.8"}}}},
{if_var_true, tools, {luerl, ".*", {git, "https://github.com/rvirding/luerl",
{tag, "v0.3"}}}},
- {if_var_true, tools, {meck, "0.8.*", {git, "https://github.com/eproxus/meck",
- {tag, "0.8.4"}}}},
{if_var_true, redis, {eredis, ".*", {git, "https://github.com/wooga/eredis",
{tag, "v1.0.8"}}}}]}.
@@ -76,7 +72,7 @@
epam,
ezlib,
eimp,
- iconv]}}.
+ pkix]}}.
{erl_first_files, ["src/ejabberd_sql_pt.erl", "src/ejabberd_config.erl",
"src/gen_mod.erl", "src/mod_muc_room.erl",
@@ -91,13 +87,10 @@
{if_var_true, debug, debug_info},
{if_var_true, sip, {d, 'SIP'}},
{if_var_true, stun, {d, 'STUN'}},
+ {if_version_above, "20", {d, 'DEPRECATED_GET_STACKTRACE'}},
{if_var_true, roster_gateway_workaround, {d, 'ROSTER_GATWAY_WORKAROUND'}},
{if_var_match, db_type, mssql, {d, 'mssql'}},
{if_var_true, elixir, {d, 'ELIXIR_ENABLED'}},
- {if_have_fun, {crypto, strong_rand_bytes, 1}, {d, 'STRONG_RAND_BYTES'}},
- {if_have_fun, {rand, uniform, 1}, {d, 'RAND_UNIFORM'}},
- {if_have_fun, {gb_sets, iterator_from, 2}, {d, 'GB_SETS_ITERATOR_FROM'}},
- {if_have_fun, {public_key, short_name_hash, 1}, {d, 'SHORT_NAME_HASH'}},
{if_var_true, new_sql_schema, {d, 'NEW_SQL_SCHEMA'}},
{if_var_true, hipe, native},
{src_dirs, [src,
@@ -136,7 +129,6 @@
{if_var_true, riak, "(\"riak_object\":_/_)"},
{if_var_false, zlib, "(\"ezlib\":_/_)"},
{if_var_false, http, "(\"lhttpc\":_/_)"},
- {if_var_false, iconv, "(\"iconv\":_/_)"},
{if_var_false, odbc, "(\"odbc\":_/_)"},
{if_var_false, sqlite, "(\"sqlite3\":_/_)"},
{if_var_false, elixir, "(\"Elixir.*\":_/_)"},
@@ -148,19 +140,18 @@
{i, "deps/fast_xml/include"},
{i, "deps/xmpp/include"}]}.
-{if_version_above, "17", {cover_enabled, true}}.
+{cover_enabled, true}.
{cover_export_enabled, true}.
{recursive_cmds, ['configure-deps']}.
{post_hook_configure, [{"fast_tls", []},
{"stringprep", []},
{"fast_yaml", []},
- {"eimp", []},
+ {"eimp", []},
{if_var_true, sip, {"esip", []}},
{"fast_xml", [{if_var_true, full_xml, "--enable-full-xml"}]},
{if_var_true, pam, {"epam", []}},
- {if_var_true, zlib, {"ezlib", []}},
- {if_var_true, iconv, {"iconv", []}}]}.
+ {if_var_true, zlib, {"ezlib", []}}]}.
%% Local Variables:
%% mode: erlang
diff --git a/sql/lite.new.sql b/sql/lite.new.sql
index d58d04c77..de62cd169 100644
--- a/sql/lite.new.sql
+++ b/sql/lite.new.sql
@@ -387,17 +387,6 @@ CREATE TABLE bosh (
CREATE UNIQUE INDEX i_bosh_sid ON bosh(sid);
-CREATE TABLE carboncopy (
- username text NOT NULL,
- server_host text NOT NULL,
- resource text NOT NULL,
- namespace text NOT NULL,
- node text NOT NULL,
- PRIMARY KEY (server_host, username, resource)
-);
-
-CREATE INDEX i_carboncopy_sh_user ON carboncopy (server_host, username);
-
CREATE TABLE proxy65 (
sid text NOT NULL,
pid_t text NOT NULL,
diff --git a/sql/lite.sql b/sql/lite.sql
index abb25d5b4..50bfec3bd 100644
--- a/sql/lite.sql
+++ b/sql/lite.sql
@@ -357,16 +357,6 @@ CREATE TABLE bosh (
CREATE UNIQUE INDEX i_bosh_sid ON bosh(sid);
-CREATE TABLE carboncopy (
- username text NOT NULL,
- resource text NOT NULL,
- namespace text NOT NULL,
- node text NOT NULL
-);
-
-CREATE UNIQUE INDEX i_carboncopy_ur ON carboncopy (username, resource);
-CREATE INDEX i_carboncopy_user ON carboncopy (username);
-
CREATE TABLE proxy65 (
sid text NOT NULL,
pid_t text NOT NULL,
diff --git a/sql/mssql.sql b/sql/mssql.sql
index 04798ee51..9393a19c1 100644
--- a/sql/mssql.sql
+++ b/sql/mssql.sql
@@ -531,19 +531,6 @@ CREATE TABLE [dbo].[bosh] (
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
);
-CREATE TABLE [dbo].[carboncopy] (
- [username] [varchar] (255) NOT NULL,
- [resource] [varchar] (255) NOT NULL,
- [namespace] [varchar] (255) NOT NULL,
- [node] [varchar] (255) NOT NULL
-);
-
-CREATE UNIQUE CLUSTERED INDEX [carboncopy_ur] ON [carboncopy] (username, resource)
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);
-
-CREATE INDEX [carboncopy_user] ON [carboncopy] (username)
-WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);
-
CREATE TABLE [dbo].[push_session] (
[username] [varchar] (255) NOT NULL,
[timestamp] [bigint] NOT NULL,
diff --git a/sql/mysql.new.sql b/sql/mysql.new.sql
index 544135e82..88769ddae 100644
--- a/sql/mysql.new.sql
+++ b/sql/mysql.new.sql
@@ -90,7 +90,7 @@ CREATE INDEX i_sr_user_sh_grp ON sr_user(server_host(191), grp);
CREATE TABLE spool (
username varchar(191) NOT NULL,
server_host text NOT NULL,
- xml BLOB NOT NULL,
+ xml mediumtext NOT NULL,
seq BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
@@ -104,8 +104,8 @@ CREATE TABLE archive (
timestamp BIGINT UNSIGNED NOT NULL,
peer varchar(191) NOT NULL,
bare_peer varchar(191) NOT NULL,
- xml text NOT NULL,
- txt text,
+ xml mediumtext NOT NULL,
+ txt mediumtext,
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
kind varchar(10),
nick varchar(191),
@@ -276,7 +276,7 @@ CREATE TABLE pubsub_item (
publisher text NOT NULL,
creation varchar(32) NOT NULL,
modification varchar(32) NOT NULL,
- payload text NOT NULL
+ payload mediumtext NOT NULL
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE INDEX i_pubsub_item_itemid ON pubsub_item(itemid(36));
CREATE UNIQUE INDEX i_pubsub_item_tuple ON pubsub_item(nodeid, itemid(36));
@@ -403,17 +403,6 @@ CREATE TABLE bosh (
CREATE UNIQUE INDEX i_bosh_sid ON bosh(sid(75));
-CREATE TABLE carboncopy (
- username text NOT NULL,
- server_host text NOT NULL,
- resource text NOT NULL,
- namespace text NOT NULL,
- node text NOT NULL,
- PRIMARY KEY (server_host(191), username(191), resource(191))
-) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-
-CREATE INDEX i_carboncopy_sh_user ON carboncopy (server_host(191), username(75));
-
CREATE TABLE proxy65 (
sid text NOT NULL,
pid_t text NOT NULL,
diff --git a/sql/mysql.sql b/sql/mysql.sql
index 7894fa5b7..2fcea38f5 100644
--- a/sql/mysql.sql
+++ b/sql/mysql.sql
@@ -80,7 +80,7 @@ CREATE INDEX i_sr_user_grp ON sr_user(grp);
CREATE TABLE spool (
username varchar(191) NOT NULL,
- xml BLOB NOT NULL,
+ xml mediumtext NOT NULL,
seq BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
@@ -93,8 +93,8 @@ CREATE TABLE archive (
timestamp BIGINT UNSIGNED NOT NULL,
peer varchar(191) NOT NULL,
bare_peer varchar(191) NOT NULL,
- xml text NOT NULL,
- txt text,
+ xml mediumtext NOT NULL,
+ txt mediumtext,
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
kind varchar(10),
nick varchar(191),
@@ -253,7 +253,7 @@ CREATE TABLE pubsub_item (
publisher text NOT NULL,
creation varchar(32) NOT NULL,
modification varchar(32) NOT NULL,
- payload text NOT NULL
+ payload mediumtext NOT NULL
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE INDEX i_pubsub_item_itemid ON pubsub_item(itemid(36));
CREATE UNIQUE INDEX i_pubsub_item_tuple ON pubsub_item(nodeid, itemid(36));
@@ -373,16 +373,6 @@ CREATE TABLE bosh (
CREATE UNIQUE INDEX i_bosh_sid ON bosh(sid(75));
-CREATE TABLE carboncopy (
- username text NOT NULL,
- resource text NOT NULL,
- namespace text NOT NULL,
- node text NOT NULL
-) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-
-CREATE UNIQUE INDEX i_carboncopy_ur ON carboncopy (username(75), resource(75));
-CREATE INDEX i_carboncopy_user ON carboncopy (username(75));
-
CREATE TABLE proxy65 (
sid text NOT NULL,
pid_t text NOT NULL,
diff --git a/sql/pg.new.sql b/sql/pg.new.sql
index 928d74db0..6244eeea9 100644
--- a/sql/pg.new.sql
+++ b/sql/pg.new.sql
@@ -156,13 +156,6 @@
-- CREATE INDEX i_sm_sh_username ON sm USING btree (server_host, username);
-- ALTER TABLE sm ALTER COLUMN server_host DROP DEFAULT;
--- ALTER TABLE carboncopy ADD COLUMN server_host text NOT NULL DEFAULT '<HOST>';
--- DROP INDEX i_carboncopy_ur;
--- DROP INDEX i_carboncopy_user;
--- ALTER TABLE carboncopy ADD PRIMARY KEY (server_host, username, resource);
--- CREATE INDEX i_carboncopy_sh_user ON carboncopy USING btree (server_host, username);
--- ALTER TABLE carboncopy ALTER COLUMN server_host DROP DEFAULT;
-
CREATE TABLE users (
username text NOT NULL,
@@ -555,17 +548,6 @@ CREATE TABLE bosh (
CREATE UNIQUE INDEX i_bosh_sid ON bosh USING btree (sid);
-CREATE TABLE carboncopy (
- username text NOT NULL,
- server_host text NOT NULL,
- resource text NOT NULL,
- namespace text NOT NULL,
- node text NOT NULL,
- PRIMARY KEY (server_host, username, resource)
-);
-
-CREATE INDEX i_carboncopy_sh_user ON carboncopy USING btree (server_host, username);
-
CREATE TABLE proxy65 (
sid text NOT NULL,
pid_t text NOT NULL,
diff --git a/sql/pg.sql b/sql/pg.sql
index a0cfe0ea6..ad1e4b9c2 100644
--- a/sql/pg.sql
+++ b/sql/pg.sql
@@ -377,16 +377,6 @@ CREATE TABLE bosh (
CREATE UNIQUE INDEX i_bosh_sid ON bosh USING btree (sid);
-CREATE TABLE carboncopy (
- username text NOT NULL,
- resource text NOT NULL,
- namespace text NOT NULL,
- node text NOT NULL
-);
-
-CREATE UNIQUE INDEX i_carboncopy_ur ON carboncopy USING btree (username, resource);
-CREATE INDEX i_carboncopy_user ON carboncopy USING btree (username);
-
CREATE TABLE proxy65 (
sid text NOT NULL,
pid_t text NOT NULL,
diff --git a/src/acl.erl b/src/acl.erl
index f0c437b02..1bffd8fa9 100644
--- a/src/acl.erl
+++ b/src/acl.erl
@@ -312,7 +312,7 @@ normalize_spec(Spec) ->
{ok, Net, Mask} ->
{ip, {Net, Mask}};
error ->
- ?INFO_MSG("Invalid network address: ~p", [S]),
+ ?WARNING_MSG("Invalid network address: ~p", [S]),
none
end;
BadVal ->
diff --git a/src/ejabberd.erl b/src/ejabberd.erl
index 4fdb67067..4d08fa5b3 100644
--- a/src/ejabberd.erl
+++ b/src/ejabberd.erl
@@ -38,7 +38,7 @@
-protocol({xep, 270, '1.0'}).
-export([start/0, stop/0, halt/0, start_app/1, start_app/2,
- get_pid_file/0, check_app/1, module_name/1]).
+ get_pid_file/0, check_app/1, module_name/1, is_loaded/0]).
-include("logger.hrl").
diff --git a/src/ejabberd_acme.erl b/src/ejabberd_acme.erl
index 4145b0a4f..9e25ed4e6 100644
--- a/src/ejabberd_acme.erl
+++ b/src/ejabberd_acme.erl
@@ -25,6 +25,7 @@
-include("ejabberd_commands.hrl").
-include("ejabberd_acme.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include("ejabberd_stacktrace.hrl").
-define(DEFAULT_CONFIG_CONTACT, <<"mailto:example-admin@example.com">>).
-define(DEFAULT_CONFIG_CA_URL, "https://acme-v01.api.letsencrypt.org").
@@ -100,10 +101,10 @@ is_valid_domain_opt(DomainString) ->
end.
-spec is_valid_revoke_cert(string()) -> boolean().
-is_valid_revoke_cert(DomainOrFile) ->
+is_valid_revoke_cert(DomainOrFile) ->
lists:prefix("file:", DomainOrFile) orelse
lists:prefix("domain:", DomainOrFile).
-
+
%% Commands
get_commands_spec() ->
[#ejabberd_commands{name = get_certificates, tags = [acme],
@@ -142,7 +143,7 @@ get_commands_spec() ->
%%
-spec get_certificates(domains_opt()) -> string() | {'error', _}.
get_certificates(Domains) ->
- case is_valid_domain_opt(Domains) of
+ case is_valid_domain_opt(Domains) of
true ->
try
CAUrl = get_config_ca_url(),
@@ -150,9 +151,8 @@ get_certificates(Domains) ->
catch
throw:Throw ->
Throw;
- E:R ->
- St = erlang:get_stacktrace(),
- ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]),
+ ?EX_RULE(E, R, St) ->
+ ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]),
{error, get_certificates}
end;
false ->
@@ -171,7 +171,7 @@ retrieve_or_create_account(CAUrl) ->
case read_account_persistent() of
none ->
create_save_new_account(CAUrl);
-
+
{ok, AccId, CAUrl, PrivateKey} ->
{ok, AccId, PrivateKey};
{ok, _AccId, _, _PrivateKey} ->
@@ -199,7 +199,7 @@ get_certificates2(CAUrl, PrivateKey, Hosts) ->
%% Format the result to send back to ejabberdctl
format_get_certificates_result(SavedCerts).
--spec format_get_certificates_result([{'ok', bitstring(), _} |
+-spec format_get_certificates_result([{'ok', bitstring(), _} |
{'error', bitstring(), _}]) ->
string().
format_get_certificates_result(Certs) ->
@@ -211,26 +211,26 @@ format_get_certificates_result(Certs) ->
case Cond of
true ->
Result = io_lib:format("Success:~n~s", [FormattedCerts]),
- lists:flatten(Result);
+ lists:flatten(Result);
_ ->
Result = io_lib:format("Error with one or more certificates~n~s", [FormattedCerts]),
lists:flatten(Result)
end.
--spec format_get_certificate({'ok', bitstring(), _} |
+-spec format_get_certificate({'ok', bitstring(), _} |
{'error', bitstring(), _}) ->
string().
-format_get_certificate({ok, Domain, saved}) ->
+format_get_certificate({ok, Domain, saved}) ->
io_lib:format(" Certificate for domain: \"~s\" acquired and saved", [Domain]);
-format_get_certificate({ok, Domain, not_found}) ->
+format_get_certificate({ok, Domain, not_found}) ->
io_lib:format(" Certificate for domain: \"~s\" not found, so it was not renewed", [Domain]);
format_get_certificate({ok, Domain, no_expire}) ->
io_lib:format(" Certificate for domain: \"~s\" is not close to expiring", [Domain]);
format_get_certificate({error, Domain, Reason}) ->
io_lib:format(" Error for domain: \"~s\", with reason: \'~s\'", [Domain, Reason]).
--spec get_certificate(url(), bitstring(), jose_jwk:key()) ->
- {'ok', bitstring(), pem()} |
+-spec get_certificate(url(), bitstring(), jose_jwk:key()) ->
+ {'ok', bitstring(), pem()} |
{'error', bitstring(), _}.
get_certificate(CAUrl, DomainName, PrivateKey) ->
try
@@ -243,9 +243,8 @@ get_certificate(CAUrl, DomainName, PrivateKey) ->
catch
throw:Throw ->
Throw;
- E:R ->
- St = erlang:get_stacktrace(),
- ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]),
+ ?EX_RULE(E, R, St) ->
+ ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]),
{error, DomainName, get_certificate}
end.
@@ -267,23 +266,23 @@ create_save_new_account(CAUrl) ->
%% TODO:
%% Find a way to ask the user if he accepts the TOS
--spec create_new_account(url(), bitstring(), jose_jwk:key()) -> {'ok', string()} |
+-spec create_new_account(url(), bitstring(), jose_jwk:key()) -> {'ok', string()} |
no_return().
create_new_account(CAUrl, Contact, PrivateKey) ->
try
{ok, Dirs, Nonce0} = ejabberd_acme_comm:directory(CAUrl),
Req0 = [{ <<"contact">>, [Contact]}],
- {ok, {TOS, Account}, Nonce1} =
+ {ok, {TOS, Account}, Nonce1} =
ejabberd_acme_comm:new_account(Dirs, PrivateKey, Req0, Nonce0),
{<<"id">>, AccIdInt} = lists:keyfind(<<"id">>, 1, Account),
AccId = integer_to_list(AccIdInt),
Req1 = [{ <<"agreement">>, list_to_bitstring(TOS)}],
- {ok, _Account2, _Nonce2} =
+ {ok, _Account2, _Nonce2} =
ejabberd_acme_comm:update_account({CAUrl, AccId}, PrivateKey, Req1, Nonce1),
{ok, AccId}
catch
E:R ->
- ?ERROR_MSG("Error: ~p creating an account for contact: ~p",
+ ?ERROR_MSG("Error: ~p creating an account for contact: ~p",
[{E,R}, Contact]),
throw({error,create_new_account})
end.
@@ -298,7 +297,7 @@ create_new_authorization(CAUrl, DomainName, PrivateKey) ->
{[{<<"type">>, <<"dns">>},
{<<"value">>, DomainName}]}},
{<<"existing">>, <<"accept">>}],
- {ok, {AuthzUrl, Authz}, Nonce1} =
+ {ok, {AuthzUrl, Authz}, Nonce1} =
ejabberd_acme_comm:new_authz(Dirs, PrivateKey, Req0, Nonce0),
{ok, AuthzId} = location_to_id(AuthzUrl),
@@ -321,7 +320,7 @@ create_new_authorization(CAUrl, DomainName, PrivateKey) ->
acme_challenge:unregister_hooks(DomainName)
end.
--spec create_new_certificate(url(), {bitstring(), [bitstring()]}, jose_jwk:key()) ->
+-spec create_new_certificate(url(), {bitstring(), [bitstring()]}, jose_jwk:key()) ->
{ok, bitstring(), pem()}.
create_new_certificate(CAUrl, {DomainName, AllSubDomains}, PrivateKey) ->
try
@@ -338,7 +337,7 @@ create_new_certificate(CAUrl, {DomainName, AllSubDomains}, PrivateKey) ->
{ok, {IssuerCertLink, Certificate}, _Nonce1} =
ejabberd_acme_comm:new_cert(Dirs, PrivateKey, Req, Nonce0),
- DecodedCert = public_key:pkix_decode_cert(list_to_binary(Certificate), plain),
+ DecodedCert = public_key:pkix_decode_cert(list_to_binary(Certificate), plain),
PemEntryCert = public_key:pem_entry_encode('Certificate', DecodedCert),
{ok, IssuerCert, _Nonce2} = ejabberd_acme_comm:get_issuer_cert(IssuerCertLink),
@@ -351,7 +350,7 @@ create_new_certificate(CAUrl, {DomainName, AllSubDomains}, PrivateKey) ->
PemCertKey = public_key:pem_encode([PemEntryKey, PemEntryCert, PemEntryIssuerCert]),
{ok, DomainName, PemCertKey}
- catch
+ catch
E:R ->
?ERROR_MSG("Error: ~p getting an authorization for domain: ~p~n",
[{E,R}, DomainName]),
@@ -383,9 +382,8 @@ renew_certificates() ->
catch
throw:Throw ->
Throw;
- E:R ->
- St = erlang:get_stacktrace(),
- ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]),
+ ?EX_RULE(E, R, St) ->
+ ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]),
{error, get_certificates}
end.
@@ -406,8 +404,8 @@ renew_certificates0(CAUrl) ->
%% Format the result to send back to ejabberdctl
format_get_certificates_result(SavedCerts).
--spec renew_certificate(url(), {bitstring(), data_cert()}, jose_jwk:key()) ->
- {'ok', bitstring(), _} |
+-spec renew_certificate(url(), {bitstring(), data_cert()}, jose_jwk:key()) ->
+ {'ok', bitstring(), _} |
{'error', bitstring(), _}.
renew_certificate(CAUrl, {DomainName, _} = Cert, PrivateKey) ->
case cert_to_expire(Cert) of
@@ -449,9 +447,8 @@ list_certificates(Verbose) ->
catch
throw:Throw ->
Throw;
- E:R ->
- St = erlang:get_stacktrace(),
- ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]),
+ ?EX_RULE(E, R, St) ->
+ ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]),
{error, list_certificates}
end;
false ->
@@ -492,34 +489,33 @@ format_certificate(DataCert, Verbose) ->
format_certificate_verbose(DomainName, SANs, NotAfter, PemCert)
end
catch
- E:R ->
- St = erlang:get_stacktrace(),
- ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]),
+ ?EX_RULE(E, R, St) ->
+ ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]),
fail_format_certificate(DomainName)
end.
--spec format_certificate_plain(bitstring(), [string()], {expired | ok, string()}, string())
+-spec format_certificate_plain(bitstring(), [string()], {expired | ok, string()}, string())
-> string().
format_certificate_plain(DomainName, SANs, NotAfter, Path) ->
Result = lists:flatten(io_lib:format(
- " Domain: ~s~n"
+ " Domain: ~s~n"
"~s"
- " ~s~n"
- " Path: ~s",
+ " ~s~n"
+ " Path: ~s",
[DomainName,
lists:flatten([io_lib:format(" SAN: ~s~n", [SAN]) || SAN <- SANs]),
format_validity(NotAfter), Path])),
Result.
--spec format_certificate_verbose(bitstring(), [string()], {expired | ok, string()}, bitstring())
+-spec format_certificate_verbose(bitstring(), [string()], {expired | ok, string()}, bitstring())
-> string().
format_certificate_verbose(DomainName, SANs, NotAfter, PemCert) ->
Result = lists:flatten(io_lib:format(
" Domain: ~s~n"
- "~s"
- " ~s~n"
- " Certificate In PEM format: ~n~s",
- [DomainName,
+ "~s"
+ " ~s~n"
+ " Certificate In PEM format: ~n~s",
+ [DomainName,
lists:flatten([io_lib:format(" SAN: ~s~n", [SAN]) || SAN <- SANs]),
format_validity(NotAfter), PemCert])),
Result.
@@ -533,8 +529,8 @@ format_validity({ok, NotAfter}) ->
-spec fail_format_certificate(bitstring()) -> string().
fail_format_certificate(DomainName) ->
Result = lists:flatten(io_lib:format(
- " Domain: ~s~n"
- " Failed to format Certificate",
+ " Domain: ~s~n"
+ " Failed to format Certificate",
[DomainName])),
Result.
@@ -542,7 +538,7 @@ fail_format_certificate(DomainName) ->
get_commonName(#'Certificate'{tbsCertificate = TbsCertificate}) ->
#'TBSCertificate'{
subject = {rdnSequence, SubjectList}
- } = TbsCertificate,
+ } = TbsCertificate,
%% TODO: Not the best way to find the commonName
ShallowSubjectList = [Attribute || [Attribute] <- SubjectList],
@@ -560,9 +556,9 @@ get_notAfter(Certificate) ->
true -> "19" ++ [Y1,Y2];
_ -> "20" ++ [Y1,Y2]
end,
- NotAfter = lists:flatten(io_lib:format("~s-~s-~s ~s:~s:~s",
+ NotAfter = lists:flatten(io_lib:format("~s-~s-~s ~s:~s:~s",
[YEAR, [MO1,MO2], [D1,D2],
- [H1,H2], [MI1,MI2], [S1,S2]])),
+ [H1,H2], [MI1,MI2], [S1,S2]])),
case close_to_expire(UtcTime, 0) of
true ->
@@ -577,7 +573,7 @@ get_subjectAltNames(#'Certificate'{tbsCertificate = TbsCertificate}) ->
extensions = Exts
} = TbsCertificate,
- EncodedSANs = [Val || #'Extension'{extnID = Oid, extnValue = Val} <- Exts,
+ EncodedSANs = [Val || #'Extension'{extnID = Oid, extnValue = Val} <- Exts,
Oid =:= attribute_oid(subjectAltName)],
lists:flatmap(
@@ -586,7 +582,7 @@ get_subjectAltNames(#'Certificate'{tbsCertificate = TbsCertificate}) ->
[Name || {dNSName, Name} <- SANs0]
end, EncodedSANs).
-
+
-spec get_utc_validity(#'Certificate'{}) -> string().
get_utc_validity(#'Certificate'{tbsCertificate = TbsCertificate}) ->
@@ -618,18 +614,17 @@ revoke_certificates(DomainOrFile) ->
catch
throw:Throw ->
Throw;
- E:R ->
- St = erlang:get_stacktrace(),
- ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]),
+ ?EX_RULE(E, R, St) ->
+ ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]),
{error, revoke_certificate}
- end.
+ end.
-spec revoke_certificate0(url(), string()) -> {ok, deleted}.
revoke_certificate0(CAUrl, DomainOrFile) ->
ParsedCert = parse_revoke_cert_argument(DomainOrFile),
revoke_certificate1(CAUrl, ParsedCert).
--spec revoke_certificate1(url(), {domain, bitstring()} | {file, file:filename()}) ->
+-spec revoke_certificate1(url(), {domain, bitstring()} | {file, file:filename()}) ->
{ok, deleted}.
revoke_certificate1(CAUrl, {domain, Domain}) ->
case domain_certificate_exists(Domain) of
@@ -650,7 +645,7 @@ revoke_certificate1(CAUrl, {file, File}) ->
?ERROR_MSG("Error: ~p reading pem certificate-key file: ~p", [Reason, File]),
throw({error, Reason})
end.
-
+
-spec revoke_certificate2(url(), pem()) -> ok.
revoke_certificate2(CAUrl, PemEncodedCert) ->
@@ -676,10 +671,10 @@ prepare_certificate_revoke(PemEncodedCert) ->
DerCert = public_key:der_encode('Certificate', PemCert),
Base64Cert = base64url:encode(DerCert),
- {ok, Key} = find_private_key_in_pem(PemEncodedCert),
+ {ok, Key} = find_private_key_in_pem(PemEncodedCert),
{Base64Cert, Key}.
--spec domain_certificate_exists(bitstring()) -> {bitstring(), data_cert()} | false.
+-spec domain_certificate_exists(bitstring()) -> {bitstring(), data_cert()} | false.
domain_certificate_exists(Domain) ->
Certs = read_certificates_persistent(),
lists:keyfind(Domain, 1, Certs).
@@ -693,7 +688,7 @@ domain_certificate_exists(Domain) ->
%% For now we accept only generating a key of
%% specific type for signing the csr
--spec make_csr(proplist(), [{dNSName, bitstring()}])
+-spec make_csr(proplist(), [{dNSName, bitstring()}])
-> {binary(), jose_jwk:key()}.
make_csr(Attributes, SANs) ->
Key = generate_key(),
@@ -749,9 +744,9 @@ extension(SANs) ->
extension_request(SANs) ->
#'AttributePKCS-10'{
type = ?'pkcs-9-at-extensionRequest',
- values = [{'asn1_OPENTYPE',
+ values = [{'asn1_OPENTYPE',
public_key:der_encode(
- 'ExtensionRequest',
+ 'ExtensionRequest',
[extension(SANs)])}]
}.
@@ -918,7 +913,7 @@ find_private_key_in_pem(Pem) ->
JoseKey = jose_jwk:from_key(Key),
{ok, JoseKey}
end.
-
+
-spec find_private_key_in_pem1([public_key:pki_asn1_type()],
[public_key:pem_entry()]) ->
@@ -948,10 +943,10 @@ private_key_types() ->
find_all_sub_domains(DomainName) ->
AllRoutes = ejabberd_router:get_all_routes(),
DomainLen = size(DomainName),
- [Route || Route <- AllRoutes,
- binary:longest_common_suffix([DomainName, Route])
+ [Route || Route <- AllRoutes,
+ binary:longest_common_suffix([DomainName, Route])
=:= DomainLen].
-
+
-spec is_error(_) -> boolean().
is_error({error, _}) -> true;
@@ -981,13 +976,13 @@ data_get_account(Data) ->
end.
-spec data_set_account(acme_data(), {list(), url(), jose_jwk:key()}) -> acme_data().
-data_set_account(Data, {AccId, CAUrl, PrivateKey}) ->
+data_set_account(Data, {AccId, CAUrl, PrivateKey}) ->
NewAcc = {account, #data_acc{id = AccId, ca_url = CAUrl, key = PrivateKey}},
lists:keystore(account, 1, Data, NewAcc).
%%
%% Certificates
-%%
+%%
-spec data_get_certificates(acme_data()) -> data_certs().
data_get_certificates(Data) ->
@@ -999,7 +994,7 @@ data_get_certificates(Data) ->
end.
-spec data_set_certificates(acme_data(), data_certs()) -> acme_data().
-data_set_certificates(Data, NewCerts) ->
+data_set_certificates(Data, NewCerts) ->
lists:keystore(certs, 1, Data, {certs, NewCerts}).
%% ATM we preserve one certificate for each domain
@@ -1053,7 +1048,7 @@ write_persistent(Data) ->
{error, Reason} ->
?ERROR_MSG("Error: ~p writing acme data file", [Reason]),
throw({error, Reason})
- end.
+ end.
-spec create_persistent() -> ok | no_return().
create_persistent() ->
@@ -1069,7 +1064,7 @@ create_persistent() ->
{error, Reason} ->
?ERROR_MSG("Error: ~p creating acme data file", [Reason]),
throw({error, Reason})
- end.
+ end.
-spec write_account_persistent({list(), url(), jose_jwk:key()}) -> ok | no_return().
write_account_persistent({AccId, CAUrl, PrivateKey}) ->
@@ -1099,7 +1094,7 @@ remove_certificate_persistent(DataCert) ->
NewData = data_remove_certificate(Data, DataCert),
ok = write_persistent(NewData).
--spec save_certificate({ok, bitstring(), binary()} | {error, _, _}) ->
+-spec save_certificate({ok, bitstring(), binary()} | {error, _, _}) ->
{ok, bitstring(), saved} | {error, bitstring(), _}.
save_certificate({error, _, _} = Error) ->
Error;
@@ -1112,7 +1107,7 @@ save_certificate({ok, DomainName, Cert}) ->
%% that there is no certificate saved if it cannot be added in
%% certificate persistent storage
write_cert(CertificateFile, Cert, DomainName),
- ok = ejabberd_pkix:add_certfile(CertificateFile),
+ {ok, _} = ejabberd_pkix:add_certfile(CertificateFile),
DataCert = #data_cert{
domain = DomainName,
pem = Cert,
@@ -1123,13 +1118,12 @@ save_certificate({ok, DomainName, Cert}) ->
catch
throw:Throw ->
Throw;
- E:R ->
- St = erlang:get_stacktrace(),
- ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]),
+ ?EX_RULE(E, R, St) ->
+ ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]),
{error, DomainName, saving}
end.
--spec save_renewed_certificate({ok, bitstring(), _} | {error, _, _}) ->
+-spec save_renewed_certificate({ok, bitstring(), _} | {error, _, _}) ->
{ok, bitstring(), _} | {error, bitstring(), _}.
save_renewed_certificate({error, _, _} = Error) ->
Error;
diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl
index 284be384b..90c36a593 100644
--- a/src/ejabberd_app.erl
+++ b/src/ejabberd_app.erl
@@ -55,6 +55,7 @@ start(normal, _Args) ->
ejabberd_system_monitor:start(),
register_elixir_config_hooks(),
ejabberd_cluster:wait_for_sync(infinity),
+ ejabberd_hooks:run(ejabberd_started, []),
{T2, _} = statistics(wall_clock),
?INFO_MSG("ejabberd ~s is started in the node ~p in ~.2fs",
[ejabberd_config:get_version(),
@@ -76,6 +77,7 @@ start(_, _) ->
%% This function is called when an application is about to be stopped,
%% before shutting down the processes of the application.
prep_stop(State) ->
+ ejabberd_hooks:run(ejabberd_stopping, []),
ejabberd_listener:stop_listeners(),
ejabberd_sm:stop(),
gen_mod:stop_modules(),
@@ -153,6 +155,7 @@ start_apps() ->
ejabberd:start_app(p1_utils),
ejabberd:start_app(fast_yaml),
ejabberd:start_app(fast_tls),
+ ejabberd:start_app(pkix),
ejabberd:start_app(xmpp),
ejabberd:start_app(cache_tab),
ejabberd:start_app(eimp).
diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl
index 5659ee389..bc0211548 100644
--- a/src/ejabberd_auth.erl
+++ b/src/ejabberd_auth.erl
@@ -41,7 +41,8 @@
get_password_s/2, get_password_with_authmodule/2,
user_exists/2, user_exists_in_other_modules/3,
remove_user/2, remove_user/3, plain_password_required/1,
- store_type/1, entropy/1, backend_type/1, password_format/1]).
+ store_type/1, entropy/1, backend_type/1, password_format/1,
+ which_users_exists/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
@@ -411,6 +412,47 @@ user_exists_in_other_modules_loop([AuthModule | AuthModules], User, Server) ->
maybe
end.
+-spec which_users_exists(list({binary(), binary()})) -> list({binary(), binary()}).
+which_users_exists(USPairs) ->
+ ByServer = lists:foldl(
+ fun({User, Server}, Dict) ->
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ case gb_trees:lookup(LServer, Dict) of
+ none ->
+ gb_trees:insert(LServer, gb_sets:singleton(LUser), Dict);
+ {value, Set} ->
+ gb_trees:update(LServer, gb_sets:add(LUser, Set), Dict)
+ end
+ end, gb_trees:empty(), USPairs),
+ Set = lists:foldl(
+ fun({LServer, UsersSet}, Results) ->
+ UsersList = gb_sets:to_list(UsersSet),
+ lists:foldl(
+ fun(M, Results2) ->
+ try M:which_users_exists(LServer, UsersList) of
+ {error, _} ->
+ Results2;
+ Res ->
+ gb_sets:union(
+ gb_sets:from_list([{U, LServer} || U <- Res]),
+ Results2)
+ catch
+ _:undef ->
+ lists:foldl(
+ fun(U, R2) ->
+ case user_exists(U, LServer) of
+ true ->
+ gb_sets:add({U, LServer}, R2);
+ _ ->
+ R2
+ end
+ end, Results2, UsersList)
+ end
+ end, Results, auth_modules(LServer))
+ end, gb_sets:empty(), gb_trees:to_list(ByServer)),
+ gb_sets:to_list(Set).
+
-spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) ->
case validate_credentials(User, Server) of
diff --git a/src/ejabberd_auth_sql.erl b/src/ejabberd_auth_sql.erl
index 4b774642a..cd9c02b91 100644
--- a/src/ejabberd_auth_sql.erl
+++ b/src/ejabberd_auth_sql.erl
@@ -35,7 +35,7 @@
-export([start/1, stop/1, set_password/3, try_register/3,
get_users/2, count_users/2, get_password/2,
remove_user/2, store_type/1, plain_password_required/1,
- convert_to_scram/1, opt_type/1, export/1]).
+ convert_to_scram/1, opt_type/1, export/1, which_users_exists/2]).
-include("scram.hrl").
-include("logger.hrl").
@@ -247,6 +247,32 @@ users_number(LServer, [{prefix, Prefix}])
users_number(LServer, []) ->
users_number(LServer).
+which_users_exists(LServer, LUsers) when length(LUsers) =< 100 ->
+ try ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(username)s from users where username in %(LUsers)ls")) of
+ {selected, Matching} ->
+ [U || {U} <- Matching];
+ {error, _} = E ->
+ E
+ catch _:B ->
+ {error, B}
+ end;
+which_users_exists(LServer, LUsers) ->
+ {First, Rest} = lists:split(100, LUsers),
+ case which_users_exists(LServer, First) of
+ {error, _} = E ->
+ E;
+ V ->
+ case which_users_exists(LServer, Rest) of
+ {error, _} = E2 ->
+ E2;
+ V2 ->
+ V ++ V2
+ end
+ end.
+
+
convert_to_scram(Server) ->
LServer = jid:nameprep(Server),
if
diff --git a/src/ejabberd_bosh.erl b/src/ejabberd_bosh.erl
index 1a650803e..e39a67132 100644
--- a/src/ejabberd_bosh.erl
+++ b/src/ejabberd_bosh.erl
@@ -23,20 +23,18 @@
%%%
%%%-------------------------------------------------------------------
-module(ejabberd_bosh).
-
+-behaviour(xmpp_socket).
+-behaviour(p1_fsm).
-protocol({xep, 124, '1.11'}).
-protocol({xep, 206, '1.4'}).
--behaviour(p1_fsm).
-
%% API
-export([start/2, start/3, start_link/3]).
-export([send_xml/2, setopts/2, controlling_process/2,
- migrate/3, become_controller/2,
- reset_stream/1, change_shaper/2, monitor/1, close/1,
+ reset_stream/1, change_shaper/2, close/1,
sockname/1, peername/1, process_request/3, send/2,
- change_controller/2]).
+ get_transport/1, get_owner/1]).
%% gen_fsm callbacks
-export([init/1, wait_for_session/2, wait_for_session/3,
@@ -167,22 +165,12 @@ setopts({http_bind, FsmRef, _IP}, Opts) ->
controlling_process(_Socket, _Pid) -> ok.
-become_controller(FsmRef, C2SPid) ->
- p1_fsm:send_all_state_event(FsmRef,
- {become_controller, C2SPid}).
-
-change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
- become_controller(FsmRef, C2SPid).
-
reset_stream({http_bind, _FsmRef, _IP} = Socket) ->
Socket.
change_shaper({http_bind, FsmRef, _IP}, Shaper) ->
p1_fsm:send_all_state_event(FsmRef, {change_shaper, Shaper}).
-monitor({http_bind, FsmRef, _IP}) ->
- erlang:monitor(process, FsmRef).
-
close({http_bind, FsmRef, _IP}) ->
catch p1_fsm:sync_send_all_state_event(FsmRef,
close).
@@ -191,10 +179,11 @@ sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}.
peername({http_bind, _FsmRef, IP}) -> {ok, IP}.
-migrate(FsmRef, Node, After) when node(FsmRef) == node() ->
- catch erlang:send_after(After, FsmRef, {migrate, Node});
-migrate(_FsmRef, _Node, _After) ->
- ok.
+get_transport(_Socket) ->
+ http_bind.
+
+get_owner({http_bind, FsmRef, _IP}) ->
+ FsmRef.
process_request(Data, IP, Type) ->
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
@@ -295,30 +284,26 @@ init([#body{attrs = Attrs}, IP, SID]) ->
buf_new(XMPPDomain)),
Opts2}
end,
- xmpp_socket:start(ejabberd_c2s, ?MODULE, Socket,
- [{receiver, self()}|Opts]),
- Inactivity = gen_mod:get_module_opt(XMPPDomain,
- mod_bosh, max_inactivity),
- MaxConcat = gen_mod:get_module_opt(XMPPDomain, mod_bosh, max_concat),
- ShapedReceivers = buf_new(XMPPDomain, ?MAX_SHAPED_REQUESTS_QUEUE_LEN),
- State = #state{host = XMPPDomain, sid = SID, ip = IP,
- xmpp_ver = XMPPVer, el_ibuf = InBuf,
- max_concat = MaxConcat, el_obuf = buf_new(XMPPDomain),
- inactivity_timeout = Inactivity,
- shaped_receivers = ShapedReceivers,
- shaper_state = ShaperState},
- NewState = restart_inactivity_timer(State),
- mod_bosh:open_session(SID, self()),
- {ok, wait_for_session, NewState};
-init([StateName, State]) ->
- mod_bosh:open_session(State#state.sid, self()),
- case State#state.c2s_pid of
- C2SPid when is_pid(C2SPid) ->
- NewSocket = make_socket(self(), State#state.ip),
- C2SPid ! {change_socket, NewSocket},
- NewState = restart_inactivity_timer(State),
- {ok, StateName, NewState};
- _ -> {stop, normal}
+ case ejabberd_c2s:start({?MODULE, Socket}, [{receiver, self()}|Opts]) of
+ {ok, C2SPid} ->
+ ejabberd_c2s:accept(C2SPid),
+ Inactivity = gen_mod:get_module_opt(XMPPDomain,
+ mod_bosh, max_inactivity),
+ MaxConcat = gen_mod:get_module_opt(XMPPDomain, mod_bosh, max_concat),
+ ShapedReceivers = buf_new(XMPPDomain, ?MAX_SHAPED_REQUESTS_QUEUE_LEN),
+ State = #state{host = XMPPDomain, sid = SID, ip = IP,
+ xmpp_ver = XMPPVer, el_ibuf = InBuf,
+ max_concat = MaxConcat, el_obuf = buf_new(XMPPDomain),
+ inactivity_timeout = Inactivity,
+ shaped_receivers = ShapedReceivers,
+ shaper_state = ShaperState},
+ NewState = restart_inactivity_timer(State),
+ mod_bosh:open_session(SID, self()),
+ {ok, wait_for_session, NewState};
+ {error, Reason} ->
+ {stop, Reason};
+ ignore ->
+ ignore
end.
wait_for_session(_Event, State) ->
@@ -525,7 +510,7 @@ active1(#body{attrs = Attrs} = Req, From, State) ->
end
end.
-handle_event({become_controller, C2SPid}, StateName,
+handle_event({activate, C2SPid}, StateName,
State) ->
State1 = route_els(State#state{c2s_pid = C2SPid}),
{next_state, StateName, State1};
@@ -598,24 +583,11 @@ handle_info({timeout, TRef, shaper_timeout}, StateName,
{stop, normal, State};
_ -> {next_state, StateName, State}
end;
-handle_info({migrate, Node}, StateName, State) ->
- if Node /= node() ->
- NewState = bounce_receivers(State, migrated),
- {migrate, NewState,
- {Node, ?MODULE, start, [StateName, NewState]}, 0};
- true -> {next_state, StateName, State}
- end;
handle_info(_Info, StateName, State) ->
?ERROR_MSG("unexpected info:~n** Msg: ~p~n** StateName: ~p",
[_Info, StateName]),
{next_state, StateName, State}.
-terminate({migrated, ClonePid}, _StateName, State) ->
- ?INFO_MSG("Migrating session \"~s\" (c2s_pid = "
- "~p) to ~p on node ~p",
- [State#state.sid, State#state.c2s_pid, ClonePid,
- node(ClonePid)]),
- mod_bosh:close_session(State#state.sid);
terminate(_Reason, _StateName, State) ->
mod_bosh:close_session(State#state.sid),
case State#state.c2s_pid of
@@ -718,7 +690,7 @@ do_reply(State, From, Body, RID) ->
Responses2 = gb_trees:insert(RID, Body, Responses1),
State#state{responses = Responses2}.
-bounce_receivers(State, Reason) ->
+bounce_receivers(State, _Reason) ->
Receivers = gb_trees:to_list(State#state.receivers),
ShapedReceivers = lists:map(fun ({_, From,
#body{attrs = Attrs} = Body}) ->
@@ -726,18 +698,13 @@ bounce_receivers(State, Reason) ->
{RID, {From, Body}}
end,
p1_queue:to_list(State#state.shaped_receivers)),
- lists:foldl(fun ({RID, {From, Body}}, AccState) ->
- NewBody = if Reason == closed ->
- #body{http_reason =
- <<"Session closed">>,
- attrs =
- [{type, <<"terminate">>},
- {condition,
- <<"other-request">>}]};
- Reason == migrated ->
- Body#body{http_reason =
- <<"Session migrated">>}
- end,
+ lists:foldl(fun ({RID, {From, _Body}}, AccState) ->
+ NewBody = #body{http_reason =
+ <<"Session closed">>,
+ attrs =
+ [{type, <<"terminate">>},
+ {condition,
+ <<"other-request">>}]},
do_reply(AccState, From, NewBody, RID)
end,
State, Receivers ++ ShapedReceivers).
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 76166db9e..ba5b04af8 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -22,14 +22,14 @@
-module(ejabberd_c2s).
-behaviour(xmpp_stream_in).
-behaviour(ejabberd_config).
--behaviour(xmpp_socket).
+-behaviour(ejabberd_listener).
-protocol({rfc, 6121}).
-%% xmpp_socket callbacks
--export([start/2, start_link/2, socket_type/0]).
+%% ejabberd_listener callbacks
+-export([start/2, start_link/2, accept/1, listen_opt_type/1, listen_options/0]).
%% ejabberd_config callbacks
--export([opt_type/1, listen_opt_type/1, transform_listen_option/2]).
+-export([opt_type/1, transform_listen_option/2]).
%% xmpp_stream_in callbacks
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
@@ -49,7 +49,7 @@
-export([get_presence/1, set_presence/2, resend_presence/1, resend_presence/2,
open_session/1, call/3, cast/2, send/2, close/1, close/2, stop/1,
reply/2, copy_state/2, set_timeout/2, route/2,
- host_up/1, host_down/1]).
+ host_up/1, host_down/1, send_ws_ping/1]).
-include("xmpp.hrl").
-include("logger.hrl").
@@ -61,26 +61,18 @@
-export_type([state/0]).
%%%===================================================================
-%%% xmpp_socket API
+%%% ejabberd_listener API
%%%===================================================================
start(SockData, Opts) ->
- case proplists:get_value(supervisor, Opts, true) of
- true ->
- case supervisor:start_child(ejabberd_c2s_sup, [SockData, Opts]) of
- {ok, undefined} -> ignore;
- Res -> Res
- end;
- _ ->
- xmpp_stream_in:start(?MODULE, [SockData, Opts],
- ejabberd_config:fsm_limit_opts(Opts))
- end.
+ xmpp_stream_in:start(?MODULE, [SockData, Opts],
+ ejabberd_config:fsm_limit_opts(Opts)).
start_link(SockData, Opts) ->
xmpp_stream_in:start_link(?MODULE, [SockData, Opts],
ejabberd_config:fsm_limit_opts(Opts)).
-socket_type() ->
- xml_stream.
+accept(Ref) ->
+ xmpp_stream_in:accept(Ref).
%%%===================================================================
%%% Common API
@@ -145,6 +137,11 @@ send_error(#{lserver := LServer} = State, Pkt, Err) ->
{Pkt1, State1} -> xmpp_stream_in:send_error(State1, Pkt1, Err)
end.
+-spec send_ws_ping(pid()) -> ok;
+ (state()) -> state().
+send_ws_ping(Ref) ->
+ xmpp_stream_in:send_ws_ping(Ref).
+
-spec route(pid(), term()) -> boolean().
route(Pid, Term) ->
ejabberd_cluster:send(Pid, Term).
@@ -416,8 +413,8 @@ bind(R, #{user := U, server := S, access := Access, lang := Lang,
{ok, State2};
deny ->
ejabberd_hooks:run(forbidden_session_hook, LServer, [JID]),
- ?INFO_MSG("(~s) Forbidden c2s session for ~s",
- [xmpp_socket:pp(Socket), jid:encode(JID)]),
+ ?WARNING_MSG("(~s) Forbidden c2s session for ~s",
+ [xmpp_socket:pp(Socket), jid:encode(JID)]),
Txt = <<"Access denied by service policy">>,
{error, xmpp:err_not_allowed(Txt, Lang), State}
end
@@ -452,12 +449,12 @@ handle_auth_success(User, Mech, AuthModule,
handle_auth_failure(User, Mech, Reason,
#{socket := Socket,
ip := IP, lserver := LServer} = State) ->
- ?INFO_MSG("(~s) Failed c2s ~s authentication ~sfrom ~s: ~s",
- [xmpp_socket:pp(Socket), Mech,
- if User /= <<"">> -> ["for ", User, "@", LServer, " "];
- true -> ""
- end,
- ejabberd_config:may_hide_data(misc:ip_to_list(IP)), Reason]),
+ ?WARNING_MSG("(~s) Failed c2s ~s authentication ~sfrom ~s: ~s",
+ [xmpp_socket:pp(Socket), Mech,
+ if User /= <<"">> -> ["for ", User, "@", LServer, " "];
+ true -> ""
+ end,
+ ejabberd_config:may_hide_data(misc:ip_to_list(IP)), Reason]),
ejabberd_hooks:run_fold(c2s_auth_result, LServer, State, [false, User]).
handle_unbinded_packet(Pkt, #{lserver := LServer} = State) ->
@@ -641,7 +638,7 @@ route_probe_reply(From, #{jid := To,
Subscription = get_subscription(To, From),
if IsAnotherResource orelse
Subscription == both orelse Subscription == from ->
- Packet = xmpp_util:add_delay_info(LastPres, To, TS),
+ Packet = misc:add_delay_info(LastPres, To, TS),
case privacy_check_packet(State, Packet, out) of
deny ->
ok;
@@ -711,13 +708,11 @@ process_presence_out(#{lserver := LServer, jid := JID,
end.
-spec process_self_presence(state(), presence()) -> state().
-process_self_presence(#{ip := IP, conn := Conn, lserver := LServer,
- auth_module := AuthMod, sid := SID,
+process_self_presence(#{lserver := LServer, sid := SID,
user := U, server := S, resource := R} = State,
#presence{type = unavailable} = Pres) ->
Status = xmpp:get_text(Pres#presence.status),
- Info = [{ip, IP}, {conn, Conn}, {auth_module, AuthMod}],
- ejabberd_sm:unset_presence(SID, U, S, R, Status, Info),
+ ejabberd_sm:unset_presence(SID, U, S, R, Status),
{Pres1, State1} = ejabberd_hooks:run_fold(
c2s_self_presence, LServer, {Pres, State}, []),
State2 = broadcast_presence_unavailable(State1, Pres1),
@@ -735,13 +730,11 @@ process_self_presence(#{lserver := LServer} = State,
process_self_presence(State, _Pres) ->
State.
--spec update_priority(state(), presence()) -> ok.
-update_priority(#{ip := IP, conn := Conn, auth_module := AuthMod,
- sid := SID, user := U, server := S, resource := R},
+-spec update_priority(state(), presence()) -> ok | {error, notfound}.
+update_priority(#{sid := SID, user := U, server := S, resource := R},
Pres) ->
Priority = get_priority_from_presence(Pres),
- Info = [{ip, IP}, {conn, Conn}, {auth_module, AuthMod}],
- ejabberd_sm:set_presence(SID, U, S, R, Priority, Pres, Info).
+ ejabberd_sm:set_presence(SID, U, S, R, Priority, Pres).
-spec broadcast_presence_unavailable(state(), presence()) -> state().
broadcast_presence_unavailable(#{jid := JID, pres_a := PresA} = State, Pres) ->
@@ -986,54 +979,54 @@ opt_type(_) ->
c2s_protocol_options, c2s_tls_compression, resource_conflict,
disable_sasl_mechanisms].
--spec listen_opt_type(atom()) -> fun((any()) -> any()) | [atom()].
-listen_opt_type(access) -> fun acl:access_rules_validator/1;
-listen_opt_type(shaper) -> fun acl:shaper_rules_validator/1;
listen_opt_type(certfile = Opt) ->
fun(S) ->
?WARNING_MSG("Listening option '~s' for ~s is deprecated, use "
"'certfiles' global option instead", [Opt, ?MODULE]),
- ejabberd_pkix:add_certfile(S),
- iolist_to_binary(S)
+ {ok, File} = ejabberd_pkix:add_certfile(S),
+ File
end;
-listen_opt_type(ciphers) -> opt_type(c2s_ciphers);
-listen_opt_type(dhfile) -> opt_type(c2s_dhfile);
-listen_opt_type(cafile) -> opt_type(c2s_cafile);
-listen_opt_type(protocol_options) -> opt_type(c2s_protocol_options);
-listen_opt_type(tls_compression) -> opt_type(c2s_tls_compression);
-listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(starttls) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(starttls_required) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(tls_verify) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(zlib) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(supervisor) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(max_stanza_size) ->
- fun(I) when is_integer(I), I>0 -> I;
- (unlimited) -> infinity;
- (infinity) -> infinity
+listen_opt_type(zlib) ->
+ fun(true) ->
+ ejabberd:start_app(ezlib),
+ true;
+ (false) ->
+ false
end;
-listen_opt_type(max_fsm_queue) ->
- fun(I) when is_integer(I), I>0 -> I end;
listen_opt_type(stream_management) ->
- ?ERROR_MSG("listening option 'stream_management' is ignored: "
- "use mod_stream_mgmt module", []),
- fun(B) when is_boolean(B) -> B end;
-listen_opt_type(inet) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(inet6) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(backlog) ->
- fun(I) when is_integer(I), I>0 -> I end;
-listen_opt_type(accept_interval) ->
- fun(I) when is_integer(I), I>=0 -> I end;
+ fun(B) when is_boolean(B) ->
+ ?ERROR_MSG("Listening option 'stream_management' is ignored: "
+ "use mod_stream_mgmt module", []),
+ B
+ end;
listen_opt_type(O) ->
- StreamOpts = mod_stream_mgmt:mod_options(ejabberd_config:get_myname()),
- case lists:keyfind(O, 1, StreamOpts) of
- false ->
- [access, shaper, certfile, ciphers, dhfile, cafile,
- protocol_options, tls, tls_compression, starttls,
- starttls_required, tls_verify, zlib, max_fsm_queue,
- backlog, inet, inet6, accept_interval];
- _ ->
- ?ERROR_MSG("Listening option '~s' is ignored: use '~s' "
- "option from mod_stream_mgmt module", [O, O]),
- mod_stream_mgmt:mod_opt_type(O)
+ MgmtOpts = mod_stream_mgmt:mod_options(ejabberd_config:get_myname()),
+ case lists:keymember(O, 1, MgmtOpts) of
+ true ->
+ fun(V) ->
+ ?ERROR_MSG("Listening option '~s' is ignored: use '~s' "
+ "option from mod_stream_mgmt module", [O, O]),
+ (mod_stream_mgmt:mod_opt_type(O))(V)
+ end
end.
+
+listen_options() ->
+ [{access, all},
+ {shaper, none},
+ {certfile, undefined},
+ {ciphers, undefined},
+ {dhfile, undefined},
+ {cafile, undefined},
+ {protocol_options, undefined},
+ {tls, false},
+ {tls_compression, false},
+ {starttls, false},
+ {starttls_required, false},
+ {tls_verify, false},
+ {zlib, false},
+ {max_stanza_size, infinity},
+ {max_fsm_queue, 5000}|
+ mod_stream_mgmt:mod_options(ejabberd_config:get_myname())].
diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl
index 209c68fa4..76e7b37b6 100644
--- a/src/ejabberd_captcha.erl
+++ b/src/ejabberd_captcha.erl
@@ -105,12 +105,13 @@ create_captcha(SID, From, To, Lang, Limiter, Args) ->
"To unblock them, visit ~s">>, [JID, get_url(Id)]},
Body = xmpp:mk_text(BodyString, Lang),
OOB = #oob_x{url = get_url(Id)},
+ Hint = #hint{type = 'no-store'},
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE,
{remove_id, Id}),
ets:insert(captcha,
#captcha{id = Id, pid = self(), key = Key, tref = Tref,
args = Args}),
- {ok, Id, Body, [OOB, Captcha, Data]};
+ {ok, Id, Body, [Hint, OOB, Captcha, Data]};
Err -> Err
end.
@@ -128,10 +129,10 @@ create_captcha_x(SID, To, Lang, Limiter, #xdata{fields = Fs} = X) ->
"visit the web page.">>),
Imageurl = get_url(<<Id/binary, "/image">>),
NewFs = [mk_field(hidden, <<"FORM_TYPE">>, ?NS_CAPTCHA)|Fs] ++
- [#xdata_field{type = fixed, values = [HelpTxt]},
+ [#xdata_field{type = fixed, var = <<"captcha-fallback-text">>, values = [HelpTxt]},
#xdata_field{type = hidden, var = <<"captchahidden">>,
values = [<<"workaround-for-psi">>]},
- #xdata_field{type = 'text-single', var = <<"url">>,
+ #xdata_field{type = 'text-single', var = <<"captcha-fallback-url">>,
label = translate:translate(
Lang, <<"CAPTCHA web page">>),
values = [Imageurl]},
@@ -230,7 +231,6 @@ process_iq(#iq{type = get, lang = Lang} = IQ) ->
Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
process_iq(#iq{lang = Lang} = IQ) ->
- ?INFO_MSG("IQ = ~p", [IQ]),
Txt = <<"No module is handling this query">>,
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl
index 56a1518e4..eb8c0443d 100644
--- a/src/ejabberd_commands.erl
+++ b/src/ejabberd_commands.erl
@@ -492,6 +492,7 @@ do_execute_command(Command, Arguments) ->
Module = Command#ejabberd_commands.module,
Function = Command#ejabberd_commands.function,
?DEBUG("Executing command ~p:~p with Args=~p", [Module, Function, Arguments]),
+ ejabberd_hooks:run(api_call, [Module, Function, Arguments]),
apply(Module, Function, Arguments).
-spec get_tags_commands() -> [{string(), [string()]}].
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index 6146695c2..90bbed179 100644
--- a/src/ejabberd_config.erl
+++ b/src/ejabberd_config.erl
@@ -500,7 +500,8 @@ get_config_option_key(Name, Val) ->
maps_to_lists(IMap) ->
maps:fold(fun(Name, Map, Res) when Name == host_config orelse Name == append_host_config ->
- [{Name, [{Host, maps_to_lists(SMap)} || {Host,SMap} <- maps:values(Map)]} | Res];
+ [{Name, [{jid:nameprep(Host), maps_to_lists(SMap)} ||
+ {Host,SMap} <- maps:values(Map)]} | Res];
(Name, Map, Res) when is_map(Map) ->
[{Name, maps:values(Map)} | Res];
(Name, Val, Res) ->
@@ -513,8 +514,9 @@ merge_configs(Terms, ResMap) ->
New = lists:foldl(fun(SVal, OMap) ->
NVal = if Name == host_config orelse Name == append_host_config ->
{Host, Opts} = SVal,
- {_, SubMap} = maps:get(Host, OMap, {Host, #{}}),
- {Host, merge_configs(Opts, SubMap)};
+ HostNP = jid:nameprep(Host),
+ {_, SubMap} = maps:get(HostNP, OMap, {HostNP, #{}}),
+ {HostNP, merge_configs(Opts, SubMap)};
true ->
SVal
end,
@@ -1081,12 +1083,12 @@ validate_opts(#state{opts = Opts} = State, ModOpts) ->
?ERROR_MSG("Invalid value for "
"option '~s' (~s): ~s",
[Opt, Error,
- misc:format_val(Val)]),
+ misc:format_val({yaml, Val})]),
erlang:error(invalid_option);
- _:_ ->
+ _:R when R /= undef ->
?ERROR_MSG("Invalid value for "
"option '~s': ~s",
- [Opt, misc:format_val(Val)]),
+ [Opt, misc:format_val({yaml, Val})]),
erlang:error(invalid_option)
end;
_ ->
diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl
index c71649562..3930d8865 100644
--- a/src/ejabberd_ctl.erl
+++ b/src/ejabberd_ctl.erl
@@ -59,6 +59,7 @@
-include("ejabberd_ctl.hrl").
-include("ejabberd_commands.hrl").
-include("logger.hrl").
+-include("ejabberd_stacktrace.hrl").
-define(DEFAULT_VERSION, 1000000).
@@ -327,9 +328,9 @@ try_call_command(Args, Auth, AccessCommands, Version) ->
catch
throw:Error ->
{io_lib:format("~p", [Error]), ?STATUS_ERROR};
- A:Why ->
- Stack = erlang:get_stacktrace(),
- {io_lib:format("Problem '~p ~p' occurred executing the command.~nStacktrace: ~p", [A, Why, Stack]), ?STATUS_ERROR}
+ ?EX_RULE(A, Why, Stack) ->
+ {io_lib:format("Problem '~p ~p' occurred executing the command.~nStacktrace: ~p",
+ [A, Why, ?EX_STACK(Stack)]), ?STATUS_ERROR}
end.
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType}
diff --git a/src/ejabberd_hooks.erl b/src/ejabberd_hooks.erl
index bc67b4c67..383f203cc 100644
--- a/src/ejabberd_hooks.erl
+++ b/src/ejabberd_hooks.erl
@@ -57,6 +57,7 @@
terminate/2]).
-include("logger.hrl").
+-include("ejabberd_stacktrace.hrl").
-record(state, {}).
-type local_hook() :: { Seq :: integer(), Module :: atom(), Function :: atom()}.
@@ -129,14 +130,14 @@ delete_dist(Hook, Node, Module, Function, Seq) ->
delete_dist(Hook, Host, Node, Module, Function, Seq) ->
gen_server:call(ejabberd_hooks, {delete, Hook, Host, Node, Module, Function, Seq}).
--spec delete_all_hooks() -> true.
+-spec delete_all_hooks() -> true.
%% @doc Primarily for testing / instrumentation
delete_all_hooks() ->
gen_server:call(ejabberd_hooks, {delete_all}).
-spec get_handlers(atom(), binary() | global) -> [local_hook() | distributed_hook()].
-%% @doc Returns currently set handler for hook name
+%% @doc Returns currently set handler for hook name
get_handlers(Hookname, Host) ->
gen_server:call(ejabberd_hooks, {get_handlers, Hookname, Host}).
@@ -264,7 +265,7 @@ handle_delete(Hook, Host, El) ->
ok;
[] ->
ok
- end.
+ end.
%%----------------------------------------------------------------------
%% Func: handle_cast/2
@@ -379,15 +380,11 @@ safe_apply(Hook, Module, Function, Args) ->
true ->
apply(Module, Function, Args)
end
- catch E:R when E /= exit; R /= normal ->
- St = get_stacktrace(),
+ catch ?EX_RULE(E, R, St) when E /= exit; R /= normal ->
?ERROR_MSG("Hook ~p crashed when running ~p:~p/~p:~n"
"** Reason = ~p~n"
"** Arguments = ~p",
[Hook, Module, Function, length(Args),
- {E, R, St}, Args]),
+ {E, R, ?EX_STACK(St)}, Args]),
'EXIT'
end.
-
-get_stacktrace() ->
- [{Mod, Fun, Loc, Args} || {Mod, Fun, Args, Loc} <- erlang:get_stacktrace()].
diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl
index db9182cd8..769577371 100644
--- a/src/ejabberd_http.erl
+++ b/src/ejabberd_http.erl
@@ -24,15 +24,16 @@
%%%----------------------------------------------------------------------
-module(ejabberd_http).
-
+-behaviour(ejabberd_listener).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
%% External exports
--export([start/2, start_link/2, become_controller/1,
- socket_type/0, receive_headers/1, recv_file/2,
- transform_listen_option/2, listen_opt_type/1]).
+-export([start/2, start_link/2,
+ accept/1, receive_headers/1, recv_file/2,
+ transform_listen_option/2, listen_opt_type/1,
+ listen_options/0]).
-export([init/2, opt_type/1]).
@@ -68,7 +69,8 @@
default_host,
custom_headers,
trail = <<>>,
- addr_re
+ addr_re,
+ sock_peer_name = none
}).
-define(XHTML_DOCTYPE,
@@ -101,6 +103,7 @@ init({SockMod, Socket}, Opts) ->
TLSEnabled = proplists:get_bool(tls, Opts),
TLSOpts1 = lists:filter(fun ({ciphers, _}) -> true;
({dhfile, _}) -> true;
+ ({cafile, _}) -> true;
({protocol_options, _}) -> true;
(_) -> false
end,
@@ -141,6 +144,7 @@ init({SockMod, Socket}, Opts) ->
true -> [{[], ejabberd_xmlrpc}];
false -> []
end,
+ SockPeer = proplists:get_value(sock_peer_name, Opts, none),
DefinedHandlers = proplists:get_value(request_handlers, Opts, []),
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
Admin ++ Bind ++ XMLRPC,
@@ -157,6 +161,7 @@ init({SockMod, Socket}, Opts) ->
custom_headers = CustomHeaders,
options = Opts,
request_handlers = RequestHandlers,
+ sock_peer_name = SockPeer,
addr_re = RE},
try receive_headers(State) of
V -> V
@@ -164,12 +169,9 @@ init({SockMod, Socket}, Opts) ->
{error, _} -> State
end.
-become_controller(_Pid) ->
+accept(_Pid) ->
ok.
-socket_type() ->
- raw.
-
send_text(_State, none) ->
ok;
send_text(State, Text) ->
@@ -202,8 +204,8 @@ send_file(State, Fd, Size, FileName) ->
end
catch _:{case_clause, {error, Why}} ->
if Why /= closed ->
- ?INFO_MSG("Failed to read ~s: ~s",
- [FileName, file_format_error(Why)]),
+ ?WARNING_MSG("Failed to read ~s: ~s",
+ [FileName, file_format_error(Why)]),
exit(normal);
true ->
ok
@@ -412,11 +414,11 @@ extract_path_query(#state{request_method = Method,
when Method =:= 'GET' orelse
Method =:= 'HEAD' orelse
Method =:= 'DELETE' orelse Method =:= 'OPTIONS' ->
- case catch url_decode_q_split(Path) of
- {'EXIT', _} -> {State, false};
- {NPath, Query} ->
- LPath = normalize_path([NPE
- || NPE <- str:tokens(path_decode(NPath), <<"/">>)]),
+ case catch url_decode_q_split_normalize(Path) of
+ {'EXIT', Error} ->
+ ?DEBUG("Error decoding URL '~p': ~p", [Path, Error]),
+ {State, false};
+ {LPath, Query} ->
LQuery = case catch parse_urlencoded(Query) of
{'EXIT', _Reason} -> [];
LQ -> LQ
@@ -430,11 +432,11 @@ extract_path_query(#state{request_method = Method,
sockmod = _SockMod,
socket = _Socket} = State)
when (Method =:= 'POST' orelse Method =:= 'PUT') andalso Len>0 ->
- case catch url_decode_q_split(Path) of
- {'EXIT', _} -> {State, false};
- {NPath, _Query} ->
- LPath = normalize_path(
- [NPE || NPE <- str:tokens(path_decode(NPath), <<"/">>)]),
+ case catch url_decode_q_split_normalize(Path) of
+ {'EXIT', Error} ->
+ ?DEBUG("Error decoding URL '~p': ~p", [Path, Error]),
+ {State, false};
+ {LPath, _Query} ->
case Method of
'PUT' ->
{State, {LPath, [], Trail}};
@@ -464,6 +466,7 @@ process_request(#state{request_method = Method,
request_version = Version,
sockmod = SockMod,
socket = Socket,
+ sock_peer_name = SockPeer,
options = Options,
request_host = Host,
request_port = Port,
@@ -482,13 +485,17 @@ process_request(#state{request_method = Method,
{State2, false} ->
{State2, make_bad_request(State)};
{State2, {LPath, LQuery, Data}} ->
- PeerName =
- case SockMod of
- gen_tcp ->
- inet:peername(Socket);
- _ ->
- SockMod:peername(Socket)
- end,
+ PeerName = case SockPeer of
+ none ->
+ case SockMod of
+ gen_tcp ->
+ inet:peername(Socket);
+ _ ->
+ SockMod:peername(Socket)
+ end;
+ {_, Peer} ->
+ {ok, Peer}
+ end,
IPHere = case PeerName of
{ok, V} -> V;
{error, _} = E -> throw(E)
@@ -574,7 +581,7 @@ is_ipchain_trusted(UserIPs, Masks) ->
lists:any(
fun({Mask, MaskLen}) ->
acl:ip_matches_mask(IP2, Mask, MaskLen)
- end, [{{127,0,0,1}, 8} | Masks]);
+ end, Masks);
_ ->
false
end
@@ -725,6 +732,12 @@ file_format_error(Reason) ->
Text -> Text
end.
+url_decode_q_split_normalize(Path) ->
+ {NPath, Query} = url_decode_q_split(Path),
+ LPath = normalize_path([NPE
+ || NPE <- str:tokens(path_decode(NPath), <<"/">>)]),
+ {LPath, Query}.
+
% Code below is taken (with some modifications) from the yaws webserver, which
% is distributed under the following license:
%
@@ -962,24 +975,13 @@ opt_type(trusted_proxies) ->
end;
opt_type(_) -> [trusted_proxies].
--spec listen_opt_type(atom()) -> fun((any()) -> any()) | [atom()].
-listen_opt_type(tls) ->
- fun(B) when is_boolean(B) -> B end;
listen_opt_type(certfile = Opt) ->
fun(S) ->
?WARNING_MSG("Listening option '~s' for ~s is deprecated, use "
"'certfiles' global option instead", [Opt, ?MODULE]),
- ejabberd_pkix:add_certfile(S),
- iolist_to_binary(S)
+ {ok, File} = ejabberd_pkix:add_certfile(S),
+ File
end;
-listen_opt_type(ciphers) ->
- fun iolist_to_binary/1;
-listen_opt_type(dhfile) ->
- fun misc:try_read_file/1;
-listen_opt_type(protocol_options) ->
- fun(Options) -> str:join(Options, <<"|">>) end;
-listen_opt_type(tls_compression) ->
- fun(B) when is_boolean(B) -> B end;
listen_opt_type(captcha) ->
fun(B) when is_boolean(B) -> B end;
listen_opt_type(register) ->
@@ -1006,15 +1008,23 @@ listen_opt_type(request_handlers) ->
end} || {Path, Mod} <- Hs2]
end;
listen_opt_type(default_host) ->
- fun(A) -> A end;
+ fun iolist_to_binary/1;
listen_opt_type(custom_headers) ->
- fun expand_custom_headers/1;
-listen_opt_type(inet) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(inet6) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(backlog) ->
- fun(I) when is_integer(I), I>0 -> I end;
-listen_opt_type(accept_interval) ->
- fun(I) when is_integer(I), I>=0 -> I end;
-listen_opt_type(_) ->
- %% TODO
- fun(A) -> A end.
+ fun expand_custom_headers/1.
+
+listen_options() ->
+ [{certfile, undefined},
+ {ciphers, undefined},
+ {dhfile, undefined},
+ {cafile, undefined},
+ {protocol_options, undefined},
+ {tls, false},
+ {tls_compression, false},
+ {captcha, false},
+ {register, false},
+ {web_admin, false},
+ {http_bind, false},
+ {xmlrpc, false},
+ {request_handlers, []},
+ {default_host, undefined},
+ {custom_headers, []}].
diff --git a/src/ejabberd_http_ws.erl b/src/ejabberd_http_ws.erl
index a9d98b882..d10dbd108 100644
--- a/src/ejabberd_http_ws.erl
+++ b/src/ejabberd_http_ws.erl
@@ -23,19 +23,17 @@
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_http_ws).
-
--behaviour(ejabberd_config).
-
-author('ecestari@process-one.net').
-
+-behaviour(ejabberd_config).
+-behaviour(xmpp_socket).
-behaviour(p1_fsm).
-export([start/1, start_link/1, init/1, handle_event/3,
handle_sync_event/4, code_change/4, handle_info/3,
terminate/3, send_xml/2, setopts/2, sockname/1,
- peername/1, controlling_process/2, become_controller/2,
- monitor/1, reset_stream/1, close/1, change_shaper/2,
- socket_handoff/3, opt_type/1]).
+ peername/1, controlling_process/2, get_owner/1,
+ reset_stream/1, close/1, change_shaper/2,
+ socket_handoff/3, get_transport/1, opt_type/1]).
-include("logger.hrl").
@@ -54,8 +52,8 @@
timeout = ?WEBSOCKET_TIMEOUT :: non_neg_integer(),
timer = make_ref() :: reference(),
input = [] :: list(),
- waiting_input = false :: false | pid(),
- last_receiver = self() :: pid(),
+ active = false :: boolean(),
+ c2s_pid :: pid(),
ws :: {#ws{}, pid()},
rfc_compilant = undefined :: boolean() | undefined}).
@@ -104,15 +102,9 @@ peername({http_ws, _FsmRef, IP}) -> {ok, IP}.
controlling_process(_Socket, _Pid) -> ok.
-become_controller(FsmRef, C2SPid) ->
- p1_fsm:send_all_state_event(FsmRef, {activate, C2SPid}).
-
close({http_ws, FsmRef, _IP}) ->
catch p1_fsm:sync_send_all_state_event(FsmRef, close).
-monitor({http_ws, FsmRef, _IP}) ->
- erlang:monitor(process, FsmRef).
-
reset_stream({http_ws, _FsmRef, _IP} = Socket) ->
Socket.
@@ -120,6 +112,12 @@ change_shaper({http_ws, _FsmRef, _IP}, _Shaper) ->
%% TODO???
ok.
+get_transport(_Socket) ->
+ websocket.
+
+get_owner({http_ws, FsmRef, _IP}) ->
+ FsmRef.
+
socket_handoff(LocalPath, Request, Opts) ->
ejabberd_websocket:socket_handoff(LocalPath, Request, Opts, ?MODULE, fun get_human_html_xmlel/0).
@@ -145,31 +143,34 @@ init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
Socket = {http_ws, self(), IP},
?DEBUG("Client connected through websocket ~p",
[Socket]),
- xmpp_socket:start(ejabberd_c2s, ?MODULE, Socket,
- [{receiver, self()}|Opts]),
- Timer = erlang:start_timer(WSTimeout, self(), []),
- {ok, loop,
- #state{socket = Socket, timeout = WSTimeout,
- timer = Timer, ws = WS,
- ping_interval = PingInterval}}.
-
-handle_event({activate, From}, StateName, StateData) ->
- case StateData#state.input of
- [] ->
- {next_state, StateName,
- StateData#state{waiting_input = From}};
- Input ->
- Receiver = From,
- lists:foreach(fun(I) when is_binary(I)->
- Receiver ! {tcp, StateData#state.socket, I};
- (I2) ->
- Receiver ! {tcp, StateData#state.socket, [I2]}
- end, Input),
- {next_state, StateName,
- StateData#state{input = [], waiting_input = false,
- last_receiver = Receiver}}
+ case ejabberd_c2s:start({?MODULE, Socket}, [{receiver, self()}|Opts]) of
+ {ok, C2SPid} ->
+ ejabberd_c2s:accept(C2SPid),
+ Timer = erlang:start_timer(WSTimeout, self(), []),
+ {ok, loop,
+ #state{socket = Socket, timeout = WSTimeout,
+ timer = Timer, ws = WS, c2s_pid = C2SPid,
+ ping_interval = PingInterval}};
+ {error, Reason} ->
+ {stop, Reason};
+ ignore ->
+ ignore
end.
+handle_event({activate, From}, StateName, State) ->
+ State1 = case State#state.input of
+ [] -> State#state{active = true};
+ Input ->
+ lists:foreach(
+ fun(I) when is_binary(I)->
+ From ! {tcp, State#state.socket, I};
+ (I2) ->
+ From ! {tcp, State#state.socket, [I2]}
+ end, Input),
+ State#state{active = false, input = []}
+ end,
+ {next_state, StateName, State1#state{c2s_pid = From}}.
+
handle_sync_event({send_xml, Packet}, _From, StateName,
#state{ws = {_, WsPid}, rfc_compilant = R} = StateData) ->
Packet2 = case {case R of undefined -> true; V -> V end, Packet} of
@@ -233,14 +234,13 @@ handle_info(closed, _StateName, StateData) ->
{stop, normal, StateData};
handle_info({received, Packet}, StateName, StateDataI) ->
{StateData, Parsed} = parse(StateDataI, Packet),
- SD = case StateData#state.waiting_input of
+ SD = case StateData#state.active of
false ->
Input = StateData#state.input ++ if is_binary(Parsed) -> [Parsed]; true -> Parsed end,
StateData#state{input = Input};
- Receiver ->
- Receiver ! {tcp, StateData#state.socket, Parsed},
- setup_timers(StateData#state{waiting_input = false,
- last_receiver = Receiver})
+ true ->
+ StateData#state.c2s_pid ! {tcp, StateData#state.socket, Parsed},
+ setup_timers(StateData#state{active = false})
end,
{next_state, StateName, SD};
handle_info(PingPong, StateName, StateData) when PingPong == ping orelse
@@ -273,13 +273,7 @@ code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
terminate(_Reason, _StateName, StateData) ->
- case StateData#state.waiting_input of
- false -> ok;
- Receiver ->
- ?DEBUG("C2S Pid : ~p", [Receiver]),
- Receiver ! {tcp_closed, StateData#state.socket}
- end,
- ok.
+ StateData#state.c2s_pid ! {tcp_closed, StateData#state.socket}.
setup_timers(StateData) ->
misc:cancel_timer(StateData#state.timer),
diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl
index 54bc877cc..e8742413b 100644
--- a/src/ejabberd_listener.erl
+++ b/src/ejabberd_listener.erl
@@ -24,19 +24,36 @@
%%%----------------------------------------------------------------------
-module(ejabberd_listener).
-
+-behaviour(supervisor).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
+-author('ekhramtsov@process-one.net').
-export([start_link/0, init/1, start/3, init/3,
start_listeners/0, start_listener/3, stop_listeners/0,
- stop_listener/2, parse_listener_portip/2,
- add_listener/3, delete_listener/2, transform_options/1,
- validate_cfg/1, opt_type/1, config_reloaded/0]).
+ stop_listener/2, add_listener/3, delete_listener/2,
+ transform_options/1, validate_cfg/1, opt_type/1,
+ config_reloaded/0, get_certfiles/0]).
+%% Legacy API
+-export([parse_listener_portip/2]).
-include("logger.hrl").
-%% We do not block on send anymore.
+-type transport() :: tcp | udp.
+-type endpoint() :: {inet:port_number(), inet:ip_address(), transport()}.
+-type listen_opts() :: [proplists:property()].
+-type listener() :: {endpoint(), module(), listen_opts()}.
+
+-callback start({gen_tcp, inet:socket()}, listen_opts()) ->
+ {ok, pid()} | {error, any()} | ignore.
+-callback start_link({gen_tcp, inet:socket()}, listen_opts()) ->
+ {ok, pid()} | {error, any()} | ignore.
+-callback accept(pid()) -> any().
+-callback listen_opt_type(atom()) -> fun((term()) -> term()).
+-callback listen_options() -> listen_opts().
+
+-optional_callbacks([listen_opt_type/1]).
+
-define(TCP_SEND_TIMEOUT, 15000).
start_link() ->
@@ -45,119 +62,107 @@ start_link() ->
init(_) ->
ets:new(?MODULE, [named_table, public]),
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50),
- {ok, {{one_for_one, 10, 1}, listeners_childspec()}}.
-
-listeners_childspec() ->
- Ls = ejabberd_config:get_option(listen, []),
- Specs = lists:map(
- fun({Port, Module, Opts}) ->
- ets:insert(?MODULE, {Port, Module, Opts}),
- {Port,
- {?MODULE, start, [Port, Module, Opts]},
- transient,
- brutal_kill,
- worker,
- [?MODULE]}
- end, Ls),
- report_duplicated_portips(Ls),
- Specs.
+ Listeners = ejabberd_config:get_option(listen, []),
+ {ok, {{one_for_one, 10, 1}, listeners_childspec(Listeners)}}.
+-spec listeners_childspec([listener()]) -> [supervisor:child_spec()].
+listeners_childspec(Listeners) ->
+ lists:map(
+ fun({EndPoint, Module, Opts}) ->
+ ets:insert(?MODULE, {EndPoint, Module, Opts}),
+ {EndPoint,
+ {?MODULE, start, [EndPoint, Module, Opts]},
+ transient, brutal_kill, worker, [?MODULE]}
+ end, Listeners).
+
+-spec start_listeners() -> ok.
start_listeners() ->
+ Listeners = ejabberd_config:get_option(listen, []),
lists:foreach(
fun(Spec) ->
supervisor:start_child(?MODULE, Spec)
- end, listeners_childspec()).
-
-report_duplicated_portips(L) ->
- LKeys = [Port || {Port, _, _} <- L],
- LNoDupsKeys = proplists:get_keys(L),
- case LKeys -- LNoDupsKeys of
- [] -> ok;
- Dups ->
- ?CRITICAL_MSG("In the ejabberd configuration there are duplicated "
- "Port number + IP address:~n ~p",
- [Dups])
- end.
-
-start(Port, Module, Opts) ->
- NewOpts = validate_module_options(Module, Opts),
- %% Check if the module is an ejabberd listener or an independent listener
- case Module:socket_type() of
- independent -> Module:start_listener(Port, NewOpts);
- _ -> start_dependent(Port, Module, NewOpts)
- end.
+ end, listeners_childspec(Listeners)).
-%% @spec(Port, Module, Opts) -> {ok, Pid} | {error, ErrorMessage}
-start_dependent(Port, Module, Opts) ->
- proc_lib:start_link(?MODULE, init, [Port, Module, Opts]).
-
-init(PortIP, Module, RawOpts) ->
- {Port, IPT, IPV, Proto, OptsClean} = parse_listener_portip(PortIP, RawOpts),
- {Opts, SockOpts} = prepare_opts(IPT, IPV, OptsClean),
- if Proto == udp ->
- init_udp(PortIP, Module, Opts, SockOpts, Port);
- true ->
- init_tcp(PortIP, Module, Opts, SockOpts, Port)
- end.
+-spec start(endpoint(), module(), listen_opts()) -> term().
+start(EndPoint, Module, Opts) ->
+ proc_lib:start_link(?MODULE, init, [EndPoint, Module, Opts]).
+
+-spec init(endpoint(), module(), listen_opts()) -> ok.
+init(EndPoint, Module, AllOpts) ->
+ {ModuleOpts, SockOpts} = split_opts(AllOpts),
+ init(EndPoint, Module, ModuleOpts, SockOpts).
-init_udp(PortIP, Module, Opts, SockOpts, Port) ->
+-spec init(endpoint(), module(), listen_opts(), [gen_tcp:option()]) -> ok.
+init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) ->
case gen_udp:open(Port, [binary,
{active, false},
{reuseaddr, true} |
SockOpts]) of
{ok, Socket} ->
- %% Inform my parent that this port was opened successfully
- proc_lib:init_ack({ok, self()}),
- application:ensure_started(ejabberd),
- start_module_sup(Port, Module),
- ?INFO_MSG("Start accepting UDP connections at ~s for ~p",
- [format_portip(PortIP), Module]),
- case erlang:function_exported(Module, udp_init, 2) of
- false ->
- udp_recv(Socket, Module, Opts);
- true ->
- case catch Module:udp_init(Socket, Opts) of
- {'EXIT', _} = Err ->
- ?ERROR_MSG("failed to process callback function "
- "~p:~s(~p, ~p): ~p",
- [Module, udp_init, Socket, Opts, Err]),
+ case inet:sockname(Socket) of
+ {ok, {Addr, Port1}} ->
+ proc_lib:init_ack({ok, self()}),
+ application:ensure_started(ejabberd),
+ ?INFO_MSG("Start accepting UDP connections at ~s for ~p",
+ [format_endpoint({Port1, Addr, udp}), Module]),
+ case erlang:function_exported(Module, udp_init, 2) of
+ false ->
udp_recv(Socket, Module, Opts);
- NewOpts ->
- udp_recv(Socket, Module, NewOpts)
- end
+ true ->
+ case catch Module:udp_init(Socket, Opts) of
+ {'EXIT', _} = Err ->
+ ?ERROR_MSG("failed to process callback function "
+ "~p:~s(~p, ~p): ~p",
+ [Module, udp_init, Socket, Opts, Err]),
+ udp_recv(Socket, Module, Opts);
+ NewOpts ->
+ udp_recv(Socket, Module, NewOpts)
+ end
+ end;
+ {error, Reason} = Err ->
+ report_socket_error(Reason, EndPoint, Module),
+ proc_lib:init_ack(Err)
end;
{error, Reason} = Err ->
- report_socket_error(Reason, PortIP, Module),
+ report_socket_error(Reason, EndPoint, Module),
proc_lib:init_ack(Err)
- end.
-
-init_tcp(PortIP, Module, Opts, SockOpts, Port) ->
- case listen_tcp(PortIP, Module, SockOpts, Port) of
+ end;
+init({Port, _, tcp} = EndPoint, Module, Opts, SockOpts) ->
+ case listen_tcp(Port, SockOpts) of
{ok, ListenSocket} ->
- proc_lib:init_ack({ok, self()}),
- application:ensure_started(ejabberd),
- start_module_sup(Port, Module),
- ?INFO_MSG("Start accepting TCP connections at ~s for ~p",
- [format_portip(PortIP), Module]),
- case erlang:function_exported(Module, tcp_init, 2) of
- false ->
- accept(ListenSocket, Module, Opts);
- true ->
- case catch Module:tcp_init(ListenSocket, Opts) of
- {'EXIT', _} = Err ->
- ?ERROR_MSG("failed to process callback function "
- "~p:~s(~p, ~p): ~p",
- [Module, tcp_init, ListenSocket, Opts, Err]),
- accept(ListenSocket, Module, Opts);
- NewOpts ->
- accept(ListenSocket, Module, NewOpts)
- end
+ case inet:sockname(ListenSocket) of
+ {ok, {Addr, Port1}} ->
+ proc_lib:init_ack({ok, self()}),
+ application:ensure_started(ejabberd),
+ Sup = start_module_sup(Module, Opts),
+ ?INFO_MSG("Start accepting TCP connections at ~s for ~p",
+ [format_endpoint({Port1, Addr, tcp}), Module]),
+ case erlang:function_exported(Module, tcp_init, 2) of
+ false ->
+ accept(ListenSocket, Module, Opts, Sup);
+ true ->
+ case catch Module:tcp_init(ListenSocket, Opts) of
+ {'EXIT', _} = Err ->
+ ?ERROR_MSG("failed to process callback function "
+ "~p:~s(~p, ~p): ~p",
+ [Module, tcp_init, ListenSocket, Opts, Err]),
+ accept(ListenSocket, Module, Opts, Sup);
+ NewOpts ->
+ accept(ListenSocket, Module, NewOpts, Sup)
+ end
+ end;
+ {error, Reason} = Err ->
+ report_socket_error(Reason, EndPoint, Module),
+ Err
end;
- {error, _} = Err ->
+ {error, Reason} = Err ->
+ report_socket_error(Reason, EndPoint, Module),
proc_lib:init_ack(Err)
end.
-listen_tcp(PortIP, Module, SockOpts, Port) ->
+-spec listen_tcp(inet:port_number(), [gen_tcp:option()]) ->
+ {ok, inet:socket()} | {error, system_limit | inet:posix()}.
+listen_tcp(Port, SockOpts) ->
Res = gen_tcp:listen(Port, [binary,
{packet, 0},
{active, false},
@@ -170,105 +175,82 @@ listen_tcp(PortIP, Module, SockOpts, Port) ->
case Res of
{ok, ListenSocket} ->
{ok, ListenSocket};
- {error, Reason} = Err ->
- report_socket_error(Reason, PortIP, Module),
+ {error, _} = Err ->
Err
end.
-parse_listener_portip(PortIP, Opts) ->
- {IPOpt, Opts2} = strip_ip_option(Opts),
- {IPVOpt, OptsClean} = case proplists:get_bool(inet6, Opts2) of
- true -> {inet6, proplists:delete(inet6, Opts2)};
- false -> {inet, Opts2}
- end,
- {Port, IPT, Proto} =
- case add_proto(PortIP, Opts) of
- {P, Prot} ->
- T = get_ip_tuple(IPOpt, IPVOpt),
- {P, T, Prot};
- {P, T, Prot} when is_integer(P) and is_tuple(T) ->
- {P, T, Prot};
- {P, S, Prot} when is_integer(P) and is_binary(S) ->
- {ok, T} = inet_parse:address(binary_to_list(S)),
- {P, T, Prot}
- end,
- IPV = case tuple_size(IPT) of
- 4 -> inet;
- 8 -> inet6
- end,
- {Port, IPT, IPV, Proto, OptsClean}.
-
-prepare_opts(IPT, IPV, OptsClean) ->
- %% The first inet|inet6 and the last {ip, _} work,
- %% so overriding those in Opts
- Opts = [IPV | OptsClean] ++ [{ip, IPT}],
- SockOpts = lists:filter(fun({ip, _}) -> true;
- (inet6) -> true;
- (inet) -> true;
- ({backlog, _}) -> true;
- (_) -> false
- end, Opts),
- {Opts, SockOpts}.
-
-add_proto(Port, Opts) when is_integer(Port) ->
- {Port, get_proto(Opts)};
-add_proto({Port, Proto}, _Opts) when is_atom(Proto) ->
- {Port, normalize_proto(Proto)};
-add_proto({Port, Addr}, Opts) ->
- {Port, Addr, get_proto(Opts)};
-add_proto({Port, Addr, Proto}, _Opts) ->
- {Port, Addr, normalize_proto(Proto)}.
-
-strip_ip_option(Opts) ->
- {IPL, OptsNoIP} = lists:partition(
- fun({ip, _}) -> true;
- (_) -> false
- end,
- Opts),
- case IPL of
- %% Only the first ip option is considered
- [{ip, T1} | _] ->
- {T1, OptsNoIP};
- [] ->
- {no_ip_option, OptsNoIP}
- end.
-
-get_ip_tuple(no_ip_option, inet) ->
- {0, 0, 0, 0};
-get_ip_tuple(no_ip_option, inet6) ->
- {0, 0, 0, 0, 0, 0, 0, 0};
-get_ip_tuple(IPOpt, _IPVOpt) ->
- IPOpt.
+-spec split_opts(listen_opts()) -> {listen_opts(), [gen_tcp:option()]}.
+split_opts(Opts) ->
+ lists:foldl(
+ fun(Opt, {ModOpts, SockOpts} = Acc) ->
+ case Opt of
+ {ip, _} -> {ModOpts, [Opt|SockOpts]};
+ {backlog, _} -> {ModOpts, [Opt|SockOpts]};
+ {inet, true} -> {ModOpts, [inet|SockOpts]};
+ {inet6, true} -> {ModOpts, [int6|SockOpts]};
+ {inet, false} -> Acc;
+ {inet6, false} -> Acc;
+ _ -> {[Opt|ModOpts], SockOpts}
+ end
+ end, {[], []}, Opts).
-accept(ListenSocket, Module, Opts) ->
+-spec accept(inet:socket(), module(), listen_opts(), atom()) -> no_return().
+accept(ListenSocket, Module, Opts, Sup) ->
Interval = proplists:get_value(accept_interval, Opts, 0),
- accept(ListenSocket, Module, Opts, Interval).
+ accept(ListenSocket, Module, Opts, Sup, Interval).
-accept(ListenSocket, Module, Opts, Interval) ->
+-spec accept(inet:socket(), module(), listen_opts(), atom(), non_neg_integer()) -> no_return().
+accept(ListenSocket, Module, Opts, Sup, Interval) ->
NewInterval = check_rate_limit(Interval),
case gen_tcp:accept(ListenSocket) of
{ok, Socket} ->
- case {inet:sockname(Socket), inet:peername(Socket)} of
- {{ok, {Addr, Port}}, {ok, {PAddr, PPort}}} ->
- Receiver = case xmpp_socket:start(Module,
- gen_tcp, Socket, Opts) of
- {ok, RecvPid} -> RecvPid;
- _ -> none
- end,
- ?INFO_MSG("(~p) Accepted connection ~s:~p -> ~s:~p",
- [Receiver,
- ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
- PPort, inet_parse:ntoa(Addr), Port]);
+ case proplists:get_value(use_proxy_protocol, Opts, false) of
+ true ->
+ case proxy_protocol:decode(gen_tcp, Socket, 10000) of
+ {error, Err} ->
+ ?ERROR_MSG("(~w) Proxy protocol parsing failed: ~s",
+ [ListenSocket, inet:format_error(Err)]),
+ gen_tcp:close(Socket);
+ {{Addr, Port}, {PAddr, PPort}} = SP ->
+ Opts2 = [{sock_peer_name, SP} | Opts],
+ Receiver = case start_connection(Module, Socket, Opts2, Sup) of
+ {ok, RecvPid} ->
+ RecvPid;
+ _ ->
+ gen_tcp:close(Socket),
+ none
+ end,
+ ?INFO_MSG("(~p) Accepted proxied connection ~s:~p -> ~s:~p",
+ [Receiver,
+ ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
+ PPort, inet_parse:ntoa(Addr), Port])
+ end;
_ ->
- gen_tcp:close(Socket)
+ case {inet:sockname(Socket), inet:peername(Socket)} of
+ {{ok, {Addr, Port}}, {ok, {PAddr, PPort}}} ->
+ Receiver = case start_connection(Module, Socket, Opts, Sup) of
+ {ok, RecvPid} ->
+ RecvPid;
+ _ ->
+ gen_tcp:close(Socket),
+ none
+ end,
+ ?INFO_MSG("(~p) Accepted connection ~s:~p -> ~s:~p",
+ [Receiver,
+ ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
+ PPort, inet_parse:ntoa(Addr), Port]);
+ _ ->
+ gen_tcp:close(Socket)
+ end
end,
- accept(ListenSocket, Module, Opts, NewInterval);
+ accept(ListenSocket, Module, Opts, Sup, NewInterval);
{error, Reason} ->
?ERROR_MSG("(~w) Failed TCP accept: ~s",
- [ListenSocket, inet:format_error(Reason)]),
- accept(ListenSocket, Module, Opts, NewInterval)
+ [ListenSocket, inet:format_error(Reason)]),
+ accept(ListenSocket, Module, Opts, Sup, NewInterval)
end.
+-spec udp_recv(inet:socket(), module(), listen_opts()) -> no_return().
udp_recv(Socket, Module, Opts) ->
case gen_udp:recv(Socket, 0) of
{ok, {Addr, Port, Packet}} ->
@@ -287,9 +269,35 @@ udp_recv(Socket, Module, Opts) ->
throw({error, Reason})
end.
-%% @spec (Port, Module, Opts) -> {ok, Pid} | {error, Error}
-start_listener(Port, Module, Opts) ->
- case start_listener2(Port, Module, Opts) of
+-spec start_connection(module(), inet:socket(), listen_opts(), atom()) ->
+ {ok, pid()} | {error, any()} | ignore.
+start_connection(Module, Socket, Opts, Sup) ->
+ Res = case Sup of
+ undefined -> Module:start({gen_tcp, Socket}, Opts);
+ _ -> supervisor:start_child(Sup, [{gen_tcp, Socket}, Opts])
+ end,
+ case Res of
+ {ok, Pid} ->
+ case gen_tcp:controlling_process(Socket, Pid) of
+ ok ->
+ Module:accept(Pid),
+ {ok, Pid};
+ Err ->
+ exit(Pid, kill),
+ Err
+ end;
+ Err ->
+ Err
+ end.
+
+-spec start_listener(endpoint(), module(), listen_opts()) ->
+ {ok, pid()} | {error, any()}.
+start_listener(EndPoint, Module, Opts) ->
+ %% It is only required to start the supervisor in some cases.
+ %% But it doesn't hurt to attempt to start it for any listener.
+ %% So, it's normal (and harmless) that in most cases this
+ %% call returns: {error, {already_started, pid()}}
+ case start_listener_sup(EndPoint, Module, Opts) of
{ok, _Pid} = R -> R;
{error, {{'EXIT', {undef, [{M, _F, _A}|_]}}, _} = Error} ->
?ERROR_MSG("Error starting the ejabberd listener: ~p.~n"
@@ -302,33 +310,34 @@ start_listener(Port, Module, Opts) ->
{error, Error}
end.
-%% @spec (Port, Module, Opts) -> {ok, Pid} | {error, Error}
-start_listener2(Port, Module, Opts) ->
- %% It is only required to start the supervisor in some cases.
- %% But it doesn't hurt to attempt to start it for any listener.
- %% So, it's normal (and harmless) that in most cases this call returns: {error, {already_started, pid()}}
- start_listener_sup(Port, Module, Opts).
-
-start_module_sup(_Port, Module) ->
- Proc1 = gen_mod:get_module_proc(<<"sup">>, Module),
- ChildSpec1 =
- {Proc1,
- {ejabberd_tmp_sup, start_link, [Proc1, Module]},
- permanent,
- infinity,
- supervisor,
- [ejabberd_tmp_sup]},
- supervisor:start_child(ejabberd_sup, ChildSpec1).
-
-start_listener_sup(Port, Module, Opts) ->
- ChildSpec = {Port,
- {?MODULE, start, [Port, Module, Opts]},
+-spec start_module_sup(module(), [proplists:property()]) -> atom().
+start_module_sup(Module, Opts) ->
+ case proplists:get_value(supervisor, Opts, true) of
+ true ->
+ Proc = list_to_atom(atom_to_list(Module) ++ "_sup"),
+ ChildSpec = {Proc, {ejabberd_tmp_sup, start_link, [Proc, Module]},
+ permanent,
+ infinity,
+ supervisor,
+ [ejabberd_tmp_sup]},
+ supervisor:start_child(ejabberd_sup, ChildSpec),
+ Proc;
+ false ->
+ undefined
+ end.
+
+-spec start_listener_sup(endpoint(), module(), listen_opts()) ->
+ {ok, pid()} | {error, any()}.
+start_listener_sup(EndPoint, Module, Opts) ->
+ ChildSpec = {EndPoint,
+ {?MODULE, start, [EndPoint, Module, Opts]},
transient,
brutal_kill,
worker,
[?MODULE]},
supervisor:start_child(?MODULE, ChildSpec).
+-spec stop_listeners() -> ok.
stop_listeners() ->
Ports = ejabberd_config:get_option(listen, []),
lists:foreach(
@@ -337,96 +346,78 @@ stop_listeners() ->
end,
Ports).
-stop_listener({_, _, Transport} = PortIP, Module) ->
- case supervisor:terminate_child(?MODULE, PortIP) of
+-spec stop_listener(endpoint(), module()) -> ok | {error, any()}.
+stop_listener({_, _, Transport} = EndPoint, Module) ->
+ case supervisor:terminate_child(?MODULE, EndPoint) of
ok ->
?INFO_MSG("Stop accepting ~s connections at ~s for ~p",
[case Transport of udp -> "UDP"; tcp -> "TCP" end,
- format_portip(PortIP), Module]),
- ets:delete(?MODULE, PortIP),
- supervisor:delete_child(?MODULE, PortIP);
+ format_endpoint(EndPoint), Module]),
+ ets:delete(?MODULE, EndPoint),
+ supervisor:delete_child(?MODULE, EndPoint);
Err ->
Err
end.
-add_listener(PortIP, Module, Opts) ->
- {Port, IPT, _, Proto, _} = parse_listener_portip(PortIP, Opts),
- PortIP1 = {Port, IPT, Proto},
- case start_listener(PortIP1, Module, Opts) of
+-spec add_listener(endpoint(), module(), listen_opts()) -> ok | {error, any()}.
+add_listener(EndPoint, Module, Opts) ->
+ case start_listener(EndPoint, Module, Opts) of
{ok, _Pid} ->
ok;
{error, {already_started, _Pid}} ->
- {error, {already_started, PortIP}};
+ {error, {already_started, EndPoint}};
{error, Error} ->
{error, Error}
end.
-delete_listener(PortIP, Module) ->
- delete_listener(PortIP, Module, []).
-
-%% @spec (PortIP, Module, Opts) -> ok
-%% where
-%% PortIP = {Port, IPT | IPS}
-%% Port = integer()
-%% IPT = tuple()
-%% IPS = string()
-%% Module = atom()
-%% Opts = [term()]
-delete_listener(PortIP, Module, Opts) ->
- {Port, IPT, _, Proto, _} = parse_listener_portip(PortIP, Opts),
- PortIP1 = {Port, IPT, Proto},
- stop_listener(PortIP1, Module).
+-spec delete_listener(endpoint(), module()) -> ok | {error, any()}.
+delete_listener(EndPoint, Module) ->
+ stop_listener(EndPoint, Module).
+-spec config_reloaded() -> ok.
config_reloaded() ->
New = ejabberd_config:get_option(listen, []),
Old = ets:tab2list(?MODULE),
lists:foreach(
- fun({PortIP, Module, _Opts}) ->
- case lists:keyfind(PortIP, 1, New) of
+ fun({EndPoint, Module, _Opts}) ->
+ case lists:keyfind(EndPoint, 1, New) of
false ->
- stop_listener(PortIP, Module);
+ stop_listener(EndPoint, Module);
_ ->
ok
end
end, Old),
lists:foreach(
- fun({PortIP, Module, Opts}) ->
- case lists:keyfind(PortIP, 1, Old) of
+ fun({EndPoint, Module, Opts}) ->
+ case lists:keyfind(EndPoint, 1, Old) of
{_, Module, Opts} ->
ok;
{_, OldModule, _} ->
- stop_listener(PortIP, OldModule),
- ets:insert(?MODULE, {PortIP, Module, Opts}),
- start_listener(PortIP, Module, Opts);
+ stop_listener(EndPoint, OldModule),
+ ets:insert(?MODULE, {EndPoint, Module, Opts}),
+ start_listener(EndPoint, Module, Opts);
false ->
- ets:insert(?MODULE, {PortIP, Module, Opts}),
- start_listener(PortIP, Module, Opts)
+ ets:insert(?MODULE, {EndPoint, Module, Opts}),
+ start_listener(EndPoint, Module, Opts)
end
end, New).
-%%%
-%%% Check options
-%%%
-get_proto(Opts) ->
- case proplists:get_value(proto, Opts) of
- undefined ->
- tcp;
- Proto ->
- normalize_proto(Proto)
- end.
-
-normalize_proto(tcp) -> tcp;
-normalize_proto(udp) -> udp;
-normalize_proto(UnknownProto) ->
- ?WARNING_MSG("There is a problem in the configuration: "
- "~p is an unknown IP protocol. Using tcp as fallback",
- [UnknownProto]),
- tcp.
+-spec get_certfiles() -> [binary()].
+get_certfiles() ->
+ lists:filtermap(
+ fun({_, _, Opts}) ->
+ case proplists:get_value(certfile, Opts) of
+ undefined -> false;
+ Cert -> {true, Cert}
+ end
+ end, ets:tab2list(?MODULE)).
-report_socket_error(Reason, PortIP, Module) ->
+-spec report_socket_error(inet:posix(), endpoint(), module()) -> ok.
+report_socket_error(Reason, EndPoint, Module) ->
?ERROR_MSG("Failed to open socket at ~s for ~s: ~s",
- [format_portip(PortIP), Module, format_error(Reason)]).
+ [format_endpoint(EndPoint), Module, format_error(Reason)]).
+-spec format_error(inet:posix()) -> string().
format_error(Reason) ->
case inet:format_error(Reason) of
"unknown POSIX error" ->
@@ -435,13 +426,15 @@ format_error(Reason) ->
ReasonStr
end.
-format_portip({Port, IP, _Transport}) ->
+-spec format_endpoint(endpoint()) -> string().
+format_endpoint({Port, IP, _Transport}) ->
IPStr = case tuple_size(IP) of
4 -> inet:ntoa(IP);
8 -> "[" ++ inet:ntoa(IP) ++ "]"
end,
IPStr ++ ":" ++ integer_to_list(Port).
+-spec check_rate_limit(non_neg_integer()) -> non_neg_integer().
check_rate_limit(Interval) ->
NewInterval = receive
{rate_limit, AcceptInterval} ->
@@ -467,11 +460,6 @@ check_rate_limit(Interval) ->
end,
NewInterval.
--define(IS_CHAR(C), (is_integer(C) and (C >= 0) and (C =< 255))).
--define(IS_UINT(U), (is_integer(U) and (U >= 0) and (U =< 65535))).
--define(IS_PORT(P), (is_integer(P) and (P > 0) and (P =< 65535))).
--define(IS_TRANSPORT(T), ((T == tcp) or (T == udp))).
-
transform_option({{Port, IP, Transport}, Mod, Opts}) ->
IPStr = if is_tuple(IP) ->
list_to_binary(inet_parse:ntoa(IP));
@@ -501,7 +489,7 @@ transform_option({{Port, IP, Transport}, Mod, Opts}) ->
end,
IPOpt ++ TransportOpt ++ [{port, Port}, {module, Mod} | Opts2];
transform_option({{Port, Transport}, Mod, Opts})
- when ?IS_TRANSPORT(Transport) ->
+ when Transport == tcp orelse Transport == udp ->
transform_option({{Port, all_zero_ip(Opts), Transport}, Mod, Opts});
transform_option({{Port, IP}, Mod, Opts}) ->
transform_option({{Port, IP, tcp}, Mod, Opts});
@@ -518,113 +506,269 @@ transform_options({listen, LOpts}, Opts) ->
transform_options(Opt, Opts) ->
[Opt|Opts].
-known_listen_options(Module) ->
- try Module:listen_options() of
- Opts -> [element(1, Opt) || Opt <- Opts]
- catch _:undef ->
- Module:listen_opt_type('')
+-spec validate_cfg(list()) -> [listener()].
+validate_cfg(Listeners) ->
+ Listeners1 = lists:map(fun validate_opts/1, Listeners),
+ Listeners2 = lists:keysort(1, Listeners1),
+ check_overlapping_listeners(Listeners2).
+
+-spec validate_module(module()) -> ok.
+validate_module(Mod) ->
+ case code:ensure_loaded(Mod) of
+ {module, Mod} ->
+ lists:foreach(
+ fun({Fun, Arity}) ->
+ case erlang:function_exported(Mod, Fun, Arity) of
+ true -> ok;
+ false ->
+ ?ERROR_MSG("Failed to load listening module ~s, "
+ "because it doesn't export ~s/~B callback. "
+ "The module is either not a listening module "
+ "or it is a third-party module which "
+ "requires update",
+ [Mod, Fun, Arity]),
+ erlang:error(badarg)
+ end
+ end, [{start, 2}, {start_link, 2},
+ {accept, 1}, {listen_options, 0}]);
+ _ ->
+ ?ERROR_MSG("Failed to load unknown listening module ~s: "
+ "make sure there is no typo and ~s.beam "
+ "exists inside either ~s or ~s directory",
+ [Mod, Mod,
+ filename:dirname(code:which(?MODULE)),
+ ext_mod:modules_dir()]),
+ erlang:error(badarg)
end.
--spec validate_module_options(module(), [{atom(), any()}]) -> [{atom(), any()}].
-validate_module_options(Module, Opts) ->
- try known_listen_options(Module) of
- _ ->
- maybe_start_zlib(Opts),
- lists:filtermap(
- fun({Opt, Val}) ->
- case validate_module_option(Module, Opt, Val) of
- {ok, NewVal} -> {true, {Opt, NewVal}};
- error -> false
- end
- end, Opts)
- catch _:undef ->
- ?WARNING_MSG("module '~s' doesn't export listen_opt_type/1",
- [Module]),
- Opts
+-spec validate_opts(listen_opts()) -> listener().
+validate_opts(Opts) ->
+ case lists:keyfind(module, 1, Opts) of
+ {_, Mod} ->
+ validate_module(Mod),
+ Opts1 = validate_opts(Mod, Opts),
+ {Opts2, Opts3} = lists:partition(
+ fun({port, _}) -> true;
+ ({transport, _}) -> true;
+ ({module, _}) -> true;
+ (_) -> false
+ end, Opts1),
+ Port = proplists:get_value(port, Opts2),
+ Transport = proplists:get_value(transport, Opts2, tcp),
+ IP = proplists:get_value(ip, Opts3, all_zero_ip(Opts3)),
+ {{Port, IP, Transport}, Mod, Opts3};
+ false ->
+ ?ERROR_MSG("Missing required listening option: module", []),
+ erlang:error(badarg)
end.
--spec validate_module_option(module(), atom(), any()) -> {ok, any()} | error.
-validate_module_option(Module, Opt, Val) ->
- case Module:listen_opt_type(Opt) of
- VFun when is_function(VFun) ->
- try VFun(Val) of
- NewVal -> {ok, NewVal}
- catch {invalid_syntax, Error} ->
- ?ERROR_MSG("ignoring listen option '~s' with "
- "invalid value: ~p: ~s",
- [Opt, Val, Error]),
- error;
- _:_ ->
- ?ERROR_MSG("ignoring listen option '~s' with "
- "invalid value: ~p",
- [Opt, Val]),
- error
- end;
+-spec validate_opts(module(), listen_opts()) -> listen_opts().
+validate_opts(Mod, Opts) ->
+ Defaults = listen_options() ++ Mod:listen_options(),
+ {Opts1, Defaults1} =
+ lists:mapfoldl(
+ fun({Opt, Val} = OptVal, Defs) ->
+ case proplists:is_defined(Opt, Defaults) of
+ true ->
+ NewOptVal = case lists:member(OptVal, Defaults) of
+ true -> [];
+ false -> [validate_module_opt(Mod, Opt, Val)]
+ end,
+ {NewOptVal, proplists:delete(Opt, Defs)};
+ false ->
+ ?ERROR_MSG("Unknown listening option '~s' of "
+ "module ~s; available options are: ~s",
+ [Opt, Mod,
+ misc:join_atoms(
+ proplists:get_keys(Defaults),
+ <<", ">>)]),
+ erlang:error(badarg)
+ end
+ end, Defaults, Opts),
+ case lists:filter(fun is_atom/1, Defaults1) of
[] ->
- ?ERROR_MSG("unknown listen option '~s' for '~s' will be likely "
- "ignored because the listening module doesn't have "
- "any options", [Opt, Module]),
- {ok, Val};
- KnownOpts when is_list(KnownOpts) ->
- ?ERROR_MSG("unknown listen option '~s' for '~s' will be likely "
- "ignored, available options are: ~s",
- [Opt, Module, misc:join_atoms(KnownOpts, <<", ">>)]),
- {ok, Val}
+ lists:flatten(Opts1);
+ MissingRequiredOpts ->
+ ?ERROR_MSG("Missing required listening option(s): ~s",
+ [misc:join_atoms(MissingRequiredOpts, <<", ">>)]),
+ erlang:error(badarg)
end.
--type transport() :: udp | tcp.
--type port_ip_transport() :: inet:port_number() |
- {inet:port_number(), transport()} |
- {inet:port_number(), inet:ip_address()} |
- {inet:port_number(), inet:ip_address(),
- transport()}.
--spec validate_cfg(list()) -> [{port_ip_transport(), module(), list()}].
-
-validate_cfg(L) ->
- lists:map(
- fun(LOpts) ->
- lists:foldl(
- fun({port, Port}, {{_, IP, T}, Mod, Opts}) ->
- true = ?IS_PORT(Port),
- {{Port, IP, T}, Mod, Opts};
- ({ip, IP}, {{Port, _, T}, Mod, Opts}) ->
- {{Port, prepare_ip(IP), T}, Mod, Opts};
- ({transport, T}, {{Port, IP, _}, Mod, Opts}) ->
- true = ?IS_TRANSPORT(T),
- {{Port, IP, T}, Mod, Opts};
- ({module, Mod}, {Port, _, Opts}) ->
- {Port, Mod, Opts};
- (Opt, {Port, Mod, Opts}) ->
- {Port, Mod, [Opt|Opts]}
- end, {{5222, all_zero_ip(LOpts), tcp}, ejabberd_c2s, []}, LOpts)
- end, L).
-
-prepare_ip({A, B, C, D} = IP)
- when ?IS_CHAR(A) and ?IS_CHAR(B) and ?IS_CHAR(C) and ?IS_CHAR(D) ->
- IP;
-prepare_ip({A, B, C, D, E, F, G, H} = IP)
- when ?IS_UINT(A) and ?IS_UINT(B) and ?IS_UINT(C) and ?IS_UINT(D)
- and ?IS_UINT(E) and ?IS_UINT(F) and ?IS_UINT(G) and ?IS_UINT(H) ->
- IP;
-prepare_ip(IP) when is_list(IP) ->
- {ok, Addr} = inet_parse:address(IP),
- Addr;
-prepare_ip(IP) when is_binary(IP) ->
- prepare_ip(binary_to_list(IP)).
+-spec validate_module_opt(module(), atom(), any()) -> {atom(), any()}.
+validate_module_opt(Module, Opt, Val) ->
+ VFun = try Module:listen_opt_type(Opt)
+ catch _:_ -> listen_opt_type(Opt)
+ end,
+ try {Opt, VFun(Val)}
+ catch _:R when R /= undef ->
+ ?ERROR_MSG("Invalid value of listening option ~s: ~s",
+ [Opt, misc:format_val({yaml, Val})]),
+ erlang:error(badarg)
+ end.
+-spec all_zero_ip(listen_opts()) -> inet:ip_address().
all_zero_ip(Opts) ->
case proplists:get_bool(inet6, Opts) of
true -> {0,0,0,0,0,0,0,0};
false -> {0,0,0,0}
end.
-maybe_start_zlib(Opts) ->
- case proplists:get_bool(zlib, Opts) of
- true ->
- ejabberd:start_app(ezlib);
- false ->
- ok
- end.
+-spec check_overlapping_listeners([listener()]) -> [listener()].
+check_overlapping_listeners(Listeners) ->
+ lists:foldl(
+ fun({{Port, IP, Transport} = Key, _, _}, Acc) ->
+ case lists:member(Key, Acc) of
+ true ->
+ ?ERROR_MSG("Overlapping listeners found at ~s",
+ [format_endpoint(Key)]),
+ erlang:error(badarg);
+ false ->
+ ZeroIP = case size(IP) of
+ 8 -> {0,0,0,0,0,0,0,0};
+ 4 -> {0,0,0,0}
+ end,
+ Key1 = {Port, ZeroIP, Transport},
+ case lists:member(Key1, Acc) of
+ true ->
+ ?ERROR_MSG(
+ "Overlapping listeners found at ~s and ~s",
+ [format_endpoint(Key), format_endpoint(Key1)]),
+ erlang:error(badarg);
+ false ->
+ [Key|Acc]
+ end
+ end
+ end, [], Listeners),
+ Listeners.
+
+listen_opt_type(port) ->
+ fun(I) when is_integer(I), I>0, I<65536 -> I end;
+listen_opt_type(module) ->
+ fun(A) when is_atom(A) -> A end;
+listen_opt_type(ip) ->
+ fun(S) ->
+ {ok, Addr} = inet_parse:address(binary_to_list(S)),
+ Addr
+ end;
+listen_opt_type(transport) ->
+ fun(tcp) -> tcp;
+ (udp) -> udp
+ end;
+listen_opt_type(accept_interval) ->
+ fun(I) when is_integer(I), I>=0 -> I end;
+listen_opt_type(backlog) ->
+ fun(I) when is_integer(I), I>=0 -> I end;
+listen_opt_type(inet) ->
+ fun(B) when is_boolean(B) -> B end;
+listen_opt_type(inet6) ->
+ fun(B) when is_boolean(B) -> B end;
+listen_opt_type(supervisor) ->
+ fun(B) when is_boolean(B) -> B end;
+listen_opt_type(certfile) ->
+ fun(S) ->
+ {ok, File} = ejabberd_pkix:add_certfile(S),
+ File
+ end;
+listen_opt_type(ciphers) -> fun iolist_to_binary/1;
+listen_opt_type(dhfile) -> fun misc:try_read_file/1;
+listen_opt_type(cafile) -> fun ejabberd_pkix:try_certfile/1;
+listen_opt_type(protocol_options) ->
+ fun (Options) -> str:join(Options, <<"|">>) end;
+listen_opt_type(tls_compression) ->
+ fun(B) when is_boolean(B) -> B end;
+listen_opt_type(tls) ->
+ fun(B) when is_boolean(B) -> B end;
+listen_opt_type(max_stanza_size) ->
+ fun(I) when is_integer(I), I>0 -> I;
+ (unlimited) -> infinity;
+ (infinity) -> infinity
+ end;
+listen_opt_type(max_fsm_queue) ->
+ fun(I) when is_integer(I), I>0 -> I end;
+listen_opt_type(shaper) ->
+ fun acl:shaper_rules_validator/1;
+listen_opt_type(access) ->
+ fun acl:access_rules_validator/1;
+listen_opt_type(use_proxy_protocol) ->
+ fun(B) when is_boolean(B) -> B end.
+
+listen_options() ->
+ [module, port,
+ {transport, tcp},
+ {ip, <<"0.0.0.0">>},
+ {inet, true},
+ {inet6, false},
+ {accept_interval, 0},
+ {backlog, 5},
+ {use_proxy_protocol, false},
+ {supervisor, true}].
opt_type(listen) -> fun validate_cfg/1;
opt_type(_) -> [listen].
+
+%%%----------------------------------------------------------------------
+%%% Some legacy code used by ejabberd_web_admin only
+%%%----------------------------------------------------------------------
+parse_listener_portip(PortIP, Opts) ->
+ {IPOpt, Opts2} = strip_ip_option(Opts),
+ {IPVOpt, OptsClean} = case proplists:get_bool(inet6, Opts2) of
+ true -> {inet6, proplists:delete(inet6, Opts2)};
+ false -> {inet, Opts2}
+ end,
+ {Port, IPT, Proto} =
+ case add_proto(PortIP, Opts) of
+ {P, Prot} ->
+ T = get_ip_tuple(IPOpt, IPVOpt),
+ {P, T, Prot};
+ {P, T, Prot} when is_integer(P) and is_tuple(T) ->
+ {P, T, Prot};
+ {P, S, Prot} when is_integer(P) and is_binary(S) ->
+ {ok, T} = inet_parse:address(binary_to_list(S)),
+ {P, T, Prot}
+ end,
+ IPV = case tuple_size(IPT) of
+ 4 -> inet;
+ 8 -> inet6
+ end,
+ {Port, IPT, IPV, Proto, OptsClean}.
+
+add_proto(Port, Opts) when is_integer(Port) ->
+ {Port, get_proto(Opts)};
+add_proto({Port, Proto}, _Opts) when is_atom(Proto) ->
+ {Port, normalize_proto(Proto)};
+add_proto({Port, Addr}, Opts) ->
+ {Port, Addr, get_proto(Opts)};
+add_proto({Port, Addr, Proto}, _Opts) ->
+ {Port, Addr, normalize_proto(Proto)}.
+
+strip_ip_option(Opts) ->
+ {IPL, OptsNoIP} = lists:partition(
+ fun({ip, _}) -> true;
+ (_) -> false
+ end,
+ Opts),
+ case IPL of
+ %% Only the first ip option is considered
+ [{ip, T1} | _] ->
+ {T1, OptsNoIP};
+ [] ->
+ {no_ip_option, OptsNoIP}
+ end.
+
+get_ip_tuple(no_ip_option, inet) ->
+ {0, 0, 0, 0};
+get_ip_tuple(no_ip_option, inet6) ->
+ {0, 0, 0, 0, 0, 0, 0, 0};
+get_ip_tuple(IPOpt, _IPVOpt) ->
+ IPOpt.
+
+get_proto(Opts) ->
+ case proplists:get_value(proto, Opts) of
+ undefined ->
+ tcp;
+ Proto ->
+ normalize_proto(Proto)
+ end.
+
+normalize_proto(udp) -> udp;
+normalize_proto(_) -> tcp.
diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl
index d9ef97129..2a1b532e0 100644
--- a/src/ejabberd_local.erl
+++ b/src/ejabberd_local.erl
@@ -48,6 +48,7 @@
-include("logger.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-include("xmpp.hrl").
+-include("ejabberd_stacktrace.hrl").
-record(state, {}).
@@ -70,10 +71,9 @@ start_link() ->
-spec route(stanza()) -> any().
route(Packet) ->
try do_route(Packet)
- catch E:R ->
- St = erlang:get_stacktrace(),
+ catch ?EX_RULE(E, R, St) ->
?ERROR_MSG("failed to route packet:~n~s~nReason = ~p",
- [xmpp:pp(Packet), {E, {R, St}}])
+ [xmpp:pp(Packet), {E, {R, ?EX_STACK(St)}}])
end.
-spec route_iq(iq(), function()) -> ok.
diff --git a/src/ejabberd_logger.erl b/src/ejabberd_logger.erl
index aac0baf59..c7845be2f 100644
--- a/src/ejabberd_logger.erl
+++ b/src/ejabberd_logger.erl
@@ -220,7 +220,7 @@ set(LogLevel) when is_integer(LogLevel) ->
end,
case LogLevel of
5 -> xmpp:set_config([{debug, true}]);
- _ -> ok
+ _ -> xmpp:set_config([{debug, false}])
end,
{module, lager};
set({_LogLevel, _}) ->
diff --git a/src/ejabberd_mnesia.erl b/src/ejabberd_mnesia.erl
index 48bc6db5c..882f95041 100644
--- a/src/ejabberd_mnesia.erl
+++ b/src/ejabberd_mnesia.erl
@@ -43,6 +43,7 @@
-define(NEED_RESET, [local_content, type]).
-include("logger.hrl").
+-include("ejabberd_stacktrace.hrl").
-record(state, {tables = #{} :: map(),
schema = [] :: [{atom(), [{atom(), any()}]}]}).
@@ -385,8 +386,8 @@ do_transform(OldAttrs, Attrs, Old) ->
transform_fun(Module, Name) ->
fun(Obj) ->
try Module:transform(Obj)
- catch E:R ->
- StackTrace = erlang:get_stacktrace(),
+ catch ?EX_RULE(E, R, St) ->
+ StackTrace = ?EX_STACK(St),
?ERROR_MSG("Failed to transform Mnesia table ~s:~n"
"** Record: ~p~n"
"** Reason: ~p~n"
diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl
index 541fcad8b..b4e412d3e 100644
--- a/src/ejabberd_piefxis.erl
+++ b/src/ejabberd_piefxis.erl
@@ -166,15 +166,10 @@ export_users([], _Server, _Fd) ->
export_user(User, Server, Fd) ->
Password = ejabberd_auth:get_password_s(User, Server),
LServer = jid:nameprep(Server),
- PasswordFormat = ejabberd_auth:password_format(LServer),
- Pass = case Password of
- {_,_,_,_} ->
- case PasswordFormat of
- scram -> format_scram_password(Password);
- _ -> <<"">>
- end;
- _ -> Password
- end,
+ Pass = case ejabberd_auth:password_format(LServer) of
+ scram -> format_scram_password(Password);
+ _ -> Password
+ end,
Els = get_offline(User, Server) ++
get_vcard(User, Server) ++
get_privacy(User, Server) ++
@@ -186,7 +181,8 @@ export_user(User, Server, Fd) ->
{<<"password">>, Pass}],
children = Els})).
-format_scram_password({StoredKey, ServerKey, Salt, IterationCount}) ->
+format_scram_password(#scram{storedkey = StoredKey, serverkey = ServerKey,
+ salt = Salt, iterationcount = IterationCount}) ->
StoredKeyB64 = base64:encode(StoredKey),
ServerKeyB64 = base64:encode(ServerKey),
SaltB64 = base64:encode(Salt),
diff --git a/src/ejabberd_pkix.erl b/src/ejabberd_pkix.erl
index f8c3483d3..2005ffdfa 100644
--- a/src/ejabberd_pkix.erl
+++ b/src/ejabberd_pkix.erl
@@ -21,179 +21,115 @@
%%%
%%%-------------------------------------------------------------------
-module(ejabberd_pkix).
-
-behaviour(gen_server).
-behaviour(ejabberd_config).
%% API
--export([start_link/0, add_certfile/1, format_error/1, opt_type/1,
- get_certfile/1, try_certfile/1, route_registered/1,
- config_reloaded/0, certs_dir/0, ca_file/0, get_default_certfile/0]).
+-export([start_link/0, opt_type/1]).
+-export([certs_dir/0, ca_file/0]).
+-export([add_certfile/1, try_certfile/1, get_certfile/0, get_certfile/1]).
+%% Hooks
+-export([ejabberd_started/0, config_reloaded/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+ terminate/2, code_change/3, format_status/2]).
--include_lib("public_key/include/public_key.hrl").
-include("logger.hrl").
+-define(CALL_TIMEOUT, timer:minutes(1)).
--record(state, {validate = true :: boolean(),
- paths = [] :: [file:filename()],
- certs = #{} :: map(),
- graph :: digraph:graph(),
- keys = [] :: [public_key:private_key()]}).
+-record(state, {files = sets:new() :: sets:set(filename())}).
-type state() :: #state{}.
--type cert() :: #'OTPCertificate'{}.
--type priv_key() :: public_key:private_key().
--type pub_key() :: #'RSAPublicKey'{} | {integer(), #'Dss-Parms'{}} | #'ECPoint'{}.
--type bad_cert_reason() :: cert_expired | invalid_issuer | invalid_signature |
- name_not_permitted | missing_basic_constraint |
- invalid_key_usage | selfsigned_peer | unknown_sig_algo |
- unknown_ca | missing_priv_key | unknown_key_algo |
- unknown_key_type | encrypted | not_der | not_cert |
- not_pem.
--type cert_error() :: {bad_cert, bad_cert_reason()}.
--export_type([cert_error/0]).
-
--define(CA_CACHE, ca_cache).
+-type filename() :: binary().
%%%===================================================================
%%% API
%%%===================================================================
--spec add_certfile(file:filename())
- -> ok | {error, cert_error() | file:posix()}.
-add_certfile(Path) ->
- gen_server:call(?MODULE, {add_certfile, prep_path(Path)}).
+-spec start_link() -> {ok, pid()} | {error, {already_started, pid()} | term()}.
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
--spec try_certfile(file:filename()) -> binary().
-try_certfile(Path0) ->
+-spec add_certfile(file:filename_all()) -> {ok, filename()} | {error, pkix:error_reason()}.
+add_certfile(Path0) ->
Path = prep_path(Path0),
- case load_certfile(Path) of
- {ok, _, _} -> Path;
- {error, _} -> erlang:error(badarg)
+ try gen_server:call(?MODULE, {add_certfile, Path}, ?CALL_TIMEOUT)
+ catch exit:{noproc, _} ->
+ case add_file(Path) of
+ ok -> {ok, Path};
+ Err -> Err
+ end
end.
-route_registered(Route) ->
- gen_server:call(?MODULE, {route_registered, Route}).
-
--spec format_error(cert_error() | file:posix()) -> string().
-format_error({bad_cert, not_cert}) ->
- "no PEM encoded certificates found";
-format_error({bad_cert, not_pem}) ->
- "failed to decode from PEM format";
-format_error({bad_cert, not_der}) ->
- "failed to decode from DER format";
-format_error({bad_cert, encrypted}) ->
- "encrypted certificate";
-format_error({bad_cert, cert_expired}) ->
- "certificate is no longer valid as its expiration date has passed";
-format_error({bad_cert, invalid_issuer}) ->
- "certificate issuer name does not match the name of the "
- "issuer certificate";
-format_error({bad_cert, invalid_signature}) ->
- "certificate was not signed by its issuer certificate";
-format_error({bad_cert, name_not_permitted}) ->
- "invalid Subject Alternative Name extension";
-format_error({bad_cert, missing_basic_constraint}) ->
- "certificate, required to have the basic constraints extension, "
- "does not have a basic constraints extension";
-format_error({bad_cert, invalid_key_usage}) ->
- "certificate key is used in an invalid way according "
- "to the key-usage extension";
-format_error({bad_cert, selfsigned_peer}) ->
- "self-signed certificate";
-format_error({bad_cert, unknown_sig_algo}) ->
- "certificate is signed using unknown algorithm";
-format_error({bad_cert, unknown_key_algo}) ->
- "unknown private key algorithm";
-format_error({bad_cert, unknown_key_type}) ->
- "private key is of unknown type";
-format_error({bad_cert, unknown_ca}) ->
- "certificate is signed by unknown CA";
-format_error({bad_cert, missing_priv_key}) ->
- "no matching private key found for certificate in the chain";
-format_error({bad_cert, Unknown}) ->
- lists:flatten(io_lib:format("~w", [Unknown]));
-format_error(Why) ->
- case file:format_error(Why) of
- "unknown POSIX error" ->
- atom_to_list(Why);
- Reason ->
- Reason
+-spec try_certfile(file:filename_all()) -> filename().
+try_certfile(Path0) ->
+ Path = prep_path(Path0),
+ case pkix:is_pem_file(Path) of
+ true -> Path;
+ {false, Reason} ->
+ ?ERROR_MSG("Failed to read PEM file ~s: ~s",
+ [Path, pkix:format_error(Reason)]),
+ erlang:error(badarg)
end.
--spec get_certfile(binary()) -> {ok, binary()} | error.
+-spec get_certfile(binary()) -> {ok, filename()} | error.
get_certfile(Domain) ->
case get_certfile_no_default(Domain) of
{ok, Path} ->
{ok, Path};
error ->
- get_default_certfile()
+ get_certfile()
end.
--spec get_certfile_no_default(binary()) -> {ok, binary()} | error.
+-spec get_certfile_no_default(binary()) -> {ok, filename()} | error.
get_certfile_no_default(Domain) ->
case xmpp_idna:domain_utf8_to_ascii(Domain) of
false ->
error;
ASCIIDomain ->
- case ets:lookup(?MODULE, ASCIIDomain) of
- [] ->
- case binary:split(ASCIIDomain, <<".">>, [trim]) of
- [_, Host] ->
- case ets:lookup(?MODULE, <<"*.", Host/binary>>) of
- [{_, Path}|_] ->
- {ok, Path};
- [] ->
- error
- end;
- _ ->
- error
- end;
- [{_, Path}|_] ->
- {ok, Path}
+ case pkix:get_certfile(ASCIIDomain) of
+ error -> error;
+ Ret -> {ok, select_certfile(Ret)}
end
end.
--spec get_default_certfile() -> {ok, binary()} | error.
-get_default_certfile() ->
- case ets:first(?MODULE) of
- '$end_of_table' ->
- error;
- Domain ->
- case ets:lookup(?MODULE, Domain) of
- [{_, Path}|_] ->
- {ok, Path};
- [] ->
- error
- end
+-spec get_certfile() -> {ok, filename()} | error.
+get_certfile() ->
+ case pkix:get_certfile() of
+ error -> error;
+ Ret -> {ok, select_certfile(Ret)}
end.
-start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+-spec ca_file() -> filename() | undefined.
+ca_file() ->
+ ejabberd_config:get_option(ca_file).
+
+-spec certs_dir() -> file:dirname_all().
+certs_dir() ->
+ MnesiaDir = mnesia:system_info(directory),
+ filename:join(MnesiaDir, "certs").
+
+-spec ejabberd_started() -> ok.
+ejabberd_started() ->
+ gen_server:call(?MODULE, ejabberd_started, ?CALL_TIMEOUT).
+-spec config_reloaded() -> ok.
config_reloaded() ->
- case use_cache() of
- true -> init_cache();
- false -> delete_cache()
- end,
- fast_tls:clear_cache(),
- gen_server:call(?MODULE, config_reloaded, 60000).
+ gen_server:call(?MODULE, config_reloaded, ?CALL_TIMEOUT).
opt_type(ca_path) ->
- fun(Path) -> binary_to_list(Path) end;
-opt_type(ca_file) ->
- fun(Path) ->
- binary_to_list(misc:try_read_file(Path))
+ fun(_) ->
+ ?WARNING_MSG("Option 'ca_path' has no effect anymore, "
+ "use 'ca_file' instead", []),
+ undefined
end;
+opt_type(ca_file) ->
+ fun try_certfile/1;
opt_type(certfiles) ->
- fun(CertList) ->
- [binary_to_list(Path) || Path <- CertList]
- end;
+ fun(Paths) -> [iolist_to_binary(Path) || Path <- Paths] end;
opt_type(O) when O == c2s_certfile; O == s2s_certfile; O == domain_certfile ->
- fun(File) ->
- ?WARNING_MSG("option '~s' is deprecated, use 'certfiles' instead", [O]),
- misc:try_read_file(File)
+ fun(Path) ->
+ ?WARNING_MSG("Option '~s' is deprecated, use 'certfiles' instead", [O]),
+ prep_path(Path)
end;
opt_type(_) ->
[ca_path, ca_file, certfiles, c2s_certfile, s2s_certfile, domain_certfile].
@@ -201,740 +137,279 @@ opt_type(_) ->
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
+-spec init([]) -> {ok, state()}.
init([]) ->
process_flag(trap_exit, true),
- ets:new(?MODULE, [named_table, public]),
- ejabberd_hooks:add(route_registered, ?MODULE, route_registered, 50),
- ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 30),
- Validate = case os:type() of
- {win32, _} -> false;
- _ ->
- code:ensure_loaded(public_key),
- erlang:function_exported(
- public_key, short_name_hash, 1)
- end,
- if Validate -> check_ca();
- true -> ok
- end,
- G = digraph:new([acyclic]),
- init_cache(),
- State = #state{validate = Validate, graph = G},
- case filelib:ensure_dir(filename:join(certs_dir(), "foo")) of
- ok ->
- clean_dir(certs_dir()),
- case add_certfiles(State) of
- {ok, State1} ->
- {ok, State1};
- {error, Why} ->
- {stop, Why}
- end;
- {error, Why} ->
- ?CRITICAL_MSG("Failed to create directory ~s: ~s",
- [certs_dir(), file:format_error(Why)]),
- {stop, Why}
+ ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 100),
+ ejabberd_hooks:add(ejabberd_started, ?MODULE, ejabberd_started, 30),
+ case add_files() of
+ {Files, []} ->
+ {ok, #state{files = Files}};
+ {Files, [_|_]} ->
+ case ejabberd:is_loaded() of
+ true ->
+ {ok, #state{files = Files}};
+ false ->
+ del_files(Files),
+ stop_ejabberd()
+ end
end.
-handle_call({add_certfile, Path}, _, State) ->
- case add_certfile(Path, State) of
- {ok, State1} ->
- if State /= State1 ->
- case build_chain_and_check(State1) of
- {ok, State2} ->
- {reply, ok, State2};
- Err ->
- {reply, Err, State1}
- end;
- true ->
- {reply, ok, State1}
- end;
- {Err, State1} ->
- {reply, Err, State1}
+-spec handle_call(term(), {pid(), term()}, state()) ->
+ {reply, ok, state()} | {noreply, state()}.
+handle_call({add_certfile, Path}, _From, State) ->
+ case add_file(Path) of
+ ok ->
+ Files = sets:add_element(Path, State#state.files),
+ {reply, {ok, Path}, State#state{files = Files}};
+ {error, _} = Err ->
+ {reply, Err, State}
end;
-handle_call({route_registered, Host}, _, State) ->
- case add_certfiles(Host, State) of
- {ok, NewState} ->
- case get_certfile_no_default(Host) of
- {ok, _} -> ok;
- error ->
- ?WARNING_MSG("No certificate found matching '~s': strictly "
- "configured clients or servers will reject "
- "connections with this host; obtain "
- "a certificate for this (sub)domain from any "
- "trusted CA such as Let's Encrypt "
- "(www.letsencrypt.org)",
- [Host])
- end,
- {reply, ok, NewState};
- {error, _} ->
- {reply, ok, State}
+handle_call(ejabberd_started, _From, State) ->
+ case commit() of
+ {ok, []} ->
+ check_domain_certfiles(),
+ {reply, ok, State};
+ _ ->
+ stop_ejabberd()
end;
handle_call(config_reloaded, _From, State) ->
- State1 = State#state{paths = [], certs = #{}, keys = []},
- case add_certfiles(State1) of
- {ok, State2} ->
- {reply, ok, State2};
- {error, _} = Err ->
- {reply, Err, State}
+ Old = State#state.files,
+ New = get_certfiles_from_config_options(),
+ del_files(sets:subtract(Old, New)),
+ add_files(New),
+ case commit() of
+ {ok, _} ->
+ check_domain_certfiles(),
+ {reply, ok, State#state{files = New}};
+ error ->
+ {reply, ok, State}
end;
-handle_call(_Request, _From, State) ->
- Reply = ok,
- {reply, Reply, State}.
+handle_call(Request, _From, State) ->
+ ?WARNING_MSG("Unexpected call: ~p", [Request]),
+ {noreply, State}.
-handle_cast(_Msg, State) ->
+-spec handle_cast(term(), state()) -> {noreply, state()}.
+handle_cast(Request, State) ->
+ ?WARNING_MSG("Unexpected cast: ~p", [Request]),
{noreply, State}.
-handle_info(_Info, State) ->
- ?WARNING_MSG("unexpected info: ~p", [_Info]),
+-spec handle_info(term(), state()) -> {noreply, state()}.
+handle_info(Info, State) ->
+ ?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}.
-terminate(_Reason, _State) ->
- ejabberd_hooks:delete(route_registered, ?MODULE, route_registered, 50),
- ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 30),
- clean_dir(certs_dir()).
+-spec terminate(normal | shutdown | {shutdown, term()} | term(),
+ state()) -> any().
+terminate(_Reason, State) ->
+ ejabberd_hooks:delete(ejabberd_started, ?MODULE, ejabberd_started, 30),
+ ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 100),
+ del_files(State#state.files).
+-spec code_change(term() | {down, term()}, state(), term()) -> {ok, state()}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
+-spec format_status(normal | terminate, list()) -> term().
+format_status(_Opt, Status) ->
+ Status.
+
%%%===================================================================
%%% Internal functions
%%%===================================================================
--spec certfiles_from_config_options() -> [atom()].
-certfiles_from_config_options() ->
- [c2s_certfile, s2s_certfile, domain_certfile].
-
--spec get_certfiles_from_config_options(state()) -> [binary()].
-get_certfiles_from_config_options(_State) ->
- Global = case ejabberd_config:get_option(certfiles) of
- undefined ->
- [];
- Paths ->
- lists:flatmap(
- fun(Path) ->
- case wildcard(Path) of
- [] ->
- ?WARNING_MSG(
- "Path ~s is empty, please "
- "make sure ejabberd has "
- "sufficient rights to read it",
- [Path]),
- [];
- Fs ->
- Fs
- end
- end, Paths)
- end,
- Local = lists:flatmap(
- fun(OptHost) ->
- case ejabberd_config:get_option(OptHost) of
- undefined -> [];
- Path -> [Path]
- end
- end, [{Opt, Host}
- || Opt <- certfiles_from_config_options(),
- Host <- ejabberd_config:get_myhosts()]),
- [iolist_to_binary(P) || P <- lists:usort(Local ++ Global)].
-
--spec add_certfiles(state()) -> {ok, state()} |
- {error, cert_error() | file:posix()}.
-add_certfiles(State) ->
- ?DEBUG("Reading certificates", []),
- Paths = get_certfiles_from_config_options(State),
- State1 = lists:foldl(
- fun(Path, Acc) ->
- {_, NewAcc} = add_certfile(Path, Acc),
- NewAcc
- end, State, Paths),
- case build_chain_and_check(State1) of
- ok -> {ok, State1};
- {error, _} = Err -> Err
- end.
-
--spec add_certfiles(binary(), state()) -> {ok, state()} |
- {error, cert_error() | file:posix()}.
-add_certfiles(Host, State) ->
- State1 = lists:foldl(
- fun(Opt, AccState) ->
- case ejabberd_config:get_option({Opt, Host}) of
- undefined -> AccState;
- Path ->
- {_, NewAccState} = add_certfile(Path, AccState),
- NewAccState
- end
- end, State, certfiles_from_config_options()),
- if State /= State1 ->
- case build_chain_and_check(State1) of
- ok -> {ok, State1};
- {error, _} = Err -> Err
- end;
- true ->
- {ok, State}
- end.
-
--spec add_certfile(file:filename_all(), state()) ->
- {ok, state()} | {{error, cert_error() | file:posix()}, state()}.
-add_certfile(Path, State) ->
- case lists:member(Path, State#state.paths) of
- true ->
- {ok, State};
- false ->
- case load_certfile(Path) of
- {ok, Certs, Keys} ->
- NewCerts = lists:foldl(
- fun(Cert, Acc) ->
- maps:put(Cert, Path, Acc)
- end, State#state.certs, Certs),
- {ok, State#state{paths = [Path|State#state.paths],
- certs = NewCerts,
- keys = Keys ++ State#state.keys}};
- {error, Why} = Err ->
- ?ERROR_MSG("failed to read certificate from ~s: ~s",
- [Path, format_error(Why)]),
- {Err, State}
- end
- end.
-
--spec build_chain_and_check(state()) -> ok | {error, cert_error() | file:posix()}.
-build_chain_and_check(State) ->
- CertPaths = get_cert_paths(maps:keys(State#state.certs), State#state.graph),
- case match_cert_keys(CertPaths, State#state.keys) of
- {ok, Chains} ->
- InvalidCerts = validate(CertPaths, State),
- SortedChains = sort_chains(Chains, InvalidCerts),
- store_certs(SortedChains, State);
- {error, Cert, Why} ->
- Path = maps:get(Cert, State#state.certs),
- ?ERROR_MSG("Failed to build certificate chain for ~s: ~s",
- [Path, format_error(Why)]),
- {error, Why}
- end.
-
--spec store_certs([{[cert()], priv_key()}], state()) -> ok | {error, file:posix()}.
-store_certs(Chains, State) ->
- ?DEBUG("Storing certificate chains", []),
- Res = lists:foldl(
- fun(_, {error, _} = Err) ->
- Err;
- ({Certs, Key}, Acc) ->
- case store_cert(Certs, Key, State) of
- {ok, FileDoms} ->
- Acc ++ FileDoms;
- {error, _} = Err ->
- Err
- end
- end, [], Chains),
- case Res of
- {error, Why} ->
- {error, Why};
- FileDomains ->
- ets:delete_all_objects(?MODULE),
- lists:foreach(
- fun({Path, Domain}) ->
- fast_tls:add_certfile(Domain, Path),
- ets:insert(?MODULE, {Domain, Path})
- end, FileDomains)
- end.
-
--spec store_cert([cert()], priv_key(), state()) -> {ok, [{binary(), binary()}]} |
- {error, file:posix()}.
-store_cert(Certs, Key, State) ->
- CertPEMs = public_key:pem_encode(
- lists:map(
- fun(Cert) ->
- Type = element(1, Cert),
- DER = public_key:pkix_encode(Type, Cert, otp),
- {'Certificate', DER, not_encrypted}
- end, Certs)),
- KeyPEM = public_key:pem_encode(
- [{element(1, Key),
- public_key:der_encode(element(1, Key), Key),
- not_encrypted}]),
- PEMs = <<CertPEMs/binary, KeyPEM/binary>>,
- Cert = hd(Certs),
- FileName = filename:join(certs_dir(), str:sha(PEMs)),
- case file:write_file(FileName, PEMs) of
+-spec add_files() -> {sets:set(filename()), [{filename(), pkix:error_reason()}]}.
+add_files() ->
+ Files = get_certfiles_from_config_options(),
+ add_files(sets:to_list(Files), sets:new(), []).
+
+-spec add_files(sets:set(filename())) ->
+ {sets:set(filename()), [{filename(), pkix:error_reason()}]}.
+add_files(Files) ->
+ add_files(sets:to_list(Files), sets:new(), []).
+
+-spec add_files([filename()], sets:set(filename()),
+ [{filename(), pkix:error_reason()}]) ->
+ {sets:set(filename()), [{filename(), pkix:error_reason()}]}.
+add_files([File|Files], Set, Errs) ->
+ case add_file(File) of
ok ->
- file:change_mode(FileName, 8#600),
- case xmpp_stream_pkix:get_cert_domains(Cert) of
- [] ->
- Path = maps:get(Cert, State#state.certs),
- ?WARNING_MSG("Certificate from ~s doesn't define "
- "any domain names", [Path]),
- {ok, [{FileName, <<"">>}]};
- Domains ->
- {ok, [{FileName, Domain} || Domain <- Domains]}
- end;
- {error, Why} = Err ->
- ?ERROR_MSG("Failed to write to ~s: ~s",
- [FileName, file:format_error(Why)]),
- Err
- end.
-
--spec sort_chains([{[cert()], priv_key()}], [cert()]) -> [{[cert()], priv_key()}].
-sort_chains(Chains, InvalidCerts) ->
- lists:sort(
- fun({[Cert1|_], _}, {[Cert2|_], _}) ->
- IsValid1 = not lists:member(Cert1, InvalidCerts),
- IsValid2 = not lists:member(Cert2, InvalidCerts),
- if IsValid1 and not IsValid2 ->
- false;
- IsValid2 and not IsValid1 ->
- true;
- true ->
- compare_expiration_date(Cert1, Cert2)
- end
- end, Chains).
-
-%% Returns true if the first certificate has sooner expiration date
--spec compare_expiration_date(cert(), cert()) -> boolean().
-compare_expiration_date(#'OTPCertificate'{
- tbsCertificate =
- #'OTPTBSCertificate'{
- validity = #'Validity'{notAfter = After1}}},
- #'OTPCertificate'{
- tbsCertificate =
- #'OTPTBSCertificate'{
- validity = #'Validity'{notAfter = After2}}}) ->
- get_timestamp(After1) =< get_timestamp(After2).
-
--spec load_certfile(file:filename_all()) -> {ok, [cert()], [priv_key()]} |
- {error, cert_error() | file:posix()}.
-load_certfile(Path) ->
- try
- {ok, Data} = file:read_file(Path),
- pem_decode(Data)
- catch _:{badmatch, {error, _} = Err} ->
+ Set1 = sets:add_element(File, Set),
+ add_files(Files, Set1, Errs);
+ {error, Reason} ->
+ Errs1 = [{File, Reason}|Errs],
+ add_files(Files, Set, Errs1)
+ end;
+add_files([], Set, Errs) ->
+ {Set, Errs}.
+
+-spec add_file(filename()) -> ok | {error, pkix:error_reason()}.
+add_file(File) ->
+ case pkix:add_file(File) of
+ ok -> ok;
+ {error, Reason} = Err ->
+ ?ERROR_MSG("Failed to read PEM file ~s: ~s",
+ [File, pkix:format_error(Reason)]),
Err
end.
--spec pem_decode(binary()) -> {ok, [cert()], [priv_key()]} |
- {error, cert_error()}.
-pem_decode(Data) ->
- try public_key:pem_decode(Data) of
- PemEntries ->
- case decode_certs(PemEntries) of
- {error, _} = Err ->
- Err;
- Objects ->
- case lists:partition(
- fun(#'OTPCertificate'{}) -> true;
- (_) -> false
- end, Objects) of
- {[], []} ->
- {error, {bad_cert, not_cert}};
- {Certs, PrivKeys} ->
- {ok, Certs, PrivKeys}
- end
- end
- catch E:R ->
- St = erlang:get_stacktrace(),
- ?DEBUG("PEM decoding stacktrace: ~p", [{E, {R, St}}]),
- {error, {bad_cert, not_pem}}
- end.
-
--spec decode_certs([public_key:pem_entry()]) -> [cert() | priv_key()] |
- {error, cert_error()}.
-decode_certs(PemEntries) ->
- try lists:flatmap(
- fun({Tag, Der, Flag}) ->
- decode_cert(Tag, Der, Flag)
- end, PemEntries)
- catch _:{bad_cert, _} = Err ->
- {error, Err};
- E:R ->
- St = erlang:get_stacktrace(),
- ?DEBUG("DER decoding stacktrace: ~p", [{E, {R, St}}]),
- {error, {bad_cert, not_der}}
- end.
-
--spec decode_cert(atom(), binary(), atom()) -> [cert() | priv_key()].
-decode_cert(_, _, Flag) when Flag /= not_encrypted ->
- erlang:error({bad_cert, encrypted});
-decode_cert('Certificate', Der, _) ->
- [public_key:pkix_decode_cert(Der, otp)];
-decode_cert('PrivateKeyInfo', Der, not_encrypted) ->
- case public_key:der_decode('PrivateKeyInfo', Der) of
- #'PrivateKeyInfo'{privateKeyAlgorithm =
- #'PrivateKeyInfo_privateKeyAlgorithm'{
- algorithm = Algo},
- privateKey = Key} ->
- KeyBin = iolist_to_binary(Key),
- case Algo of
- ?'rsaEncryption' ->
- [public_key:der_decode('RSAPrivateKey', KeyBin)];
- ?'id-dsa' ->
- [public_key:der_decode('DSAPrivateKey', KeyBin)];
- ?'id-ecPublicKey' ->
- [public_key:der_decode('ECPrivateKey', KeyBin)];
- _ ->
- erlang:error({bad_cert, unknown_key_algo})
- end;
- #'RSAPrivateKey'{} = Key -> [Key];
- #'DSAPrivateKey'{} = Key -> [Key];
- #'ECPrivateKey'{} = Key -> [Key];
- _ -> erlang:error({bad_cert, unknown_key_type})
- end;
-decode_cert(Tag, Der, _) when Tag == 'RSAPrivateKey';
- Tag == 'DSAPrivateKey';
- Tag == 'ECPrivateKey' ->
- [public_key:der_decode(Tag, Der)];
-decode_cert(_, _, _) ->
- [].
-
--spec validate([{path, [cert()]}], state()) -> [cert()].
-validate(Paths, #state{validate = true} = State) ->
- ?DEBUG("Validating certificates", []),
- {ok, Re} = re:compile("^[a-f0-9]+\\.[0-9]+$", [unicode]),
- Hashes = case file:list_dir(ca_dir()) of
- {ok, Files} ->
- lists:foldl(
- fun(File, Acc) ->
- try re:run(File, Re) of
- {match, _} ->
- [Hash|_] = string:tokens(File, "."),
- Path = filename:join(ca_dir(), File),
- dict:append(Hash, Path, Acc);
- nomatch ->
- Acc
- catch _:badarg ->
- ?ERROR_MSG("Regexp failure on ~w", [File]),
- Acc
- end
- end, dict:new(), Files);
- {error, Why} ->
- ?ERROR_MSG("Failed to list directory ~s: ~s",
- [ca_dir(), file:format_error(Why)]),
- dict:new()
- end,
- lists:filtermap(
- fun({path, Path}) ->
- case validate_path(Path, Hashes) of
- ok ->
- false;
- {error, Cert, Reason} ->
- File = maps:get(Cert, State#state.certs),
- ?WARNING_MSG("Failed to validate certificate from ~s: ~s",
- [File, format_error(Reason)]),
- {true, Cert}
- end
- end, Paths);
-validate(_, _) ->
- [].
-
--spec validate_path([cert()], dict:dict()) -> ok | {error, cert(), cert_error()}.
-validate_path([Cert|_] = Certs, Cache) ->
- case find_local_issuer(Cert, Cache) of
- {ok, IssuerCert} ->
- try public_key:pkix_path_validation(IssuerCert, Certs, []) of
- {ok, _} ->
- ok;
- {error, Reason} ->
- {error, Cert, Reason}
- catch error:function_clause ->
- case erlang:get_stacktrace() of
- [{public_key, pkix_sign_types, _, _}|_] ->
- {error, Cert, {bad_cert, unknown_sig_algo}};
- ST ->
- %% Bug in public_key application
- erlang:raise(error, function_clause, ST)
- end
- end;
- {error, Reason} ->
- case public_key:pkix_is_self_signed(Cert) of
- true ->
- {error, Cert, {bad_cert, selfsigned_peer}};
- false ->
- {error, Cert, Reason}
- end
+-spec del_files(sets:set(filename())) -> ok.
+del_files(Files) ->
+ lists:foreach(fun pkix:del_file/1, sets:to_list(Files)).
+
+-spec commit() -> {ok, [{filename(), pkix:error_reason()}]} | error.
+commit() ->
+ Opts = case ca_file() of
+ undefined -> [];
+ CAFile -> [{cafile, CAFile}]
+ end,
+ case pkix:commit(certs_dir(), Opts) of
+ {ok, Errors, Warnings, CAError} ->
+ log_errors(Errors),
+ log_cafile_error(CAError),
+ log_warnings(Warnings),
+ fast_tls_add_certfiles(),
+ {ok, Errors};
+ {error, File, Reason} ->
+ ?CRITICAL_MSG("Failed to write to ~s: ~s",
+ [File, file:format_error(Reason)]),
+ error
end.
--spec ca_dir() -> string().
-ca_dir() ->
- ejabberd_config:get_option(ca_path, "/etc/ssl/certs").
-
--spec ca_file() -> string() | undefined.
-ca_file() ->
- ejabberd_config:get_option(ca_file).
-
--spec certs_dir() -> string().
-certs_dir() ->
- MnesiaDir = mnesia:system_info(directory),
- filename:join(MnesiaDir, "certs").
+-spec check_domain_certfiles() -> ok.
+check_domain_certfiles() ->
+ Hosts = ejabberd_config:get_myhosts(),
+ Routes = ejabberd_router:get_all_routes(),
+ check_domain_certfiles(Hosts ++ Routes).
--spec clean_dir(file:filename_all()) -> ok.
-clean_dir(Dir) ->
- ?DEBUG("Cleaning directory ~s", [Dir]),
- Files = wildcard(filename:join(Dir, "*")),
+-spec check_domain_certfiles([binary()]) -> ok.
+check_domain_certfiles(Hosts) ->
lists:foreach(
- fun(Path) ->
- case filelib:is_file(Path) of
- true ->
- file:delete(Path);
- false ->
+ fun(Host) ->
+ case get_certfile_no_default(Host) of
+ error ->
+ ?WARNING_MSG("No certificate found matching '~s': strictly "
+ "configured clients or servers will reject "
+ "connections with this host; obtain "
+ "a certificate for this (sub)domain from any "
+ "trusted CA such as Let's Encrypt "
+ "(www.letsencrypt.org)",
+ [Host]);
+ _ ->
ok
end
- end, Files).
-
--spec check_ca() -> ok.
-check_ca() ->
- CAFile = ca_file(),
- case wildcard(filename:join(ca_dir(), "*.0")) of
- [] when CAFile == undefined ->
- Hint = "configuring 'ca_path' or 'ca_file' options might help",
- case file:list_dir(ca_dir()) of
- {error, Why} ->
- ?WARNING_MSG("failed to read CA directory ~s: ~s; ~s",
- [ca_dir(), file:format_error(Why), Hint]);
- {ok, _} ->
- ?WARNING_MSG("CA directory ~s doesn't contain "
- "hashed certificate files; ~s",
- [ca_dir(), Hint])
- end;
- _ ->
- ok
- end.
+ end, Hosts).
--spec find_local_issuer(cert(), dict:dict()) -> {ok, cert()} |
- {error, {bad_cert, unknown_ca}}.
-find_local_issuer(Cert, Hashes) ->
- case find_issuer_in_dir(Cert, Hashes) of
- {ok, IssuerCert} ->
- {ok, IssuerCert};
- {error, Reason} ->
- case ca_file() of
- undefined -> {error, Reason};
- CAFile -> find_issuer_in_file(Cert, CAFile)
- end
- end.
+-spec deprecated_options() -> [atom()].
+deprecated_options() ->
+ [c2s_certfile, s2s_certfile, domain_certfile].
--spec find_issuer_in_dir(cert(), dict:dict())
- -> {{ok, cert()} | {error, {bad_cert, unknown_ca}}, dict:dict()}.
-find_issuer_in_dir(Cert, Cache) ->
- {ok, {_, IssuerID}} = public_key:pkix_issuer_id(Cert, self),
- Hash = short_name_hash(IssuerID),
- Files = case dict:find(Hash, Cache) of
- {ok, L} -> L;
- error -> []
- end,
- lists:foldl(
- fun(_, {ok, _IssuerCert} = Acc) ->
- Acc;
- (Path, Err) ->
- case read_ca_file(Path) of
- {ok, [IssuerCert|_]} ->
- case public_key:pkix_is_issuer(Cert, IssuerCert) of
- true ->
- {ok, IssuerCert};
- false ->
- Err
- end;
- error ->
- Err
- end
- end, {error, {bad_cert, unknown_ca}}, Files).
-
--spec find_issuer_in_file(cert(), file:filename_all() | undefined)
- -> {ok, cert()} | {error, {bad_cert, unknown_ca}}.
-find_issuer_in_file(_Cert, undefined) ->
- {error, {bad_cert, unknown_ca}};
-find_issuer_in_file(Cert, CAFile) ->
- case read_ca_file(CAFile) of
- {ok, IssuerCerts} ->
+-spec global_certfiles() -> sets:set(filename()).
+global_certfiles() ->
+ case ejabberd_config:get_option(certfiles) of
+ undefined ->
+ sets:new();
+ Paths ->
lists:foldl(
- fun(_, {ok, _} = Res) ->
- Res;
- (IssuerCert, Err) ->
- case public_key:pkix_is_issuer(Cert, IssuerCert) of
- true -> {ok, IssuerCert};
- false -> Err
- end
- end, {error, {bad_cert, unknown_ca}}, IssuerCerts);
- error ->
- {error, {bad_cert, unknown_ca}}
- end.
-
--spec read_ca_file(file:filename_all()) -> {ok, [cert()]} | error.
-read_ca_file(Path) ->
- case use_cache() of
- true ->
- ets_cache:lookup(?CA_CACHE, Path,
- fun() -> do_read_ca_file(Path) end);
- false ->
- do_read_ca_file(Path)
- end.
-
--spec do_read_ca_file(file:filename_all()) -> {ok, [cert()]} | error.
-do_read_ca_file(Path) ->
- try
- {ok, Data} = file:read_file(Path),
- {ok, IssuerCerts, _} = pem_decode(Data),
- {ok, IssuerCerts}
- catch _:{badmatch, {error, Why}} ->
- ?ERROR_MSG("Failed to read CA certificate "
- "from \"~s\": ~s",
- [Path, format_error(Why)]),
- error
- end.
-
--spec match_cert_keys([{path, [cert()]}], [priv_key()])
- -> {ok, [{cert(), priv_key()}]} | {error, {bad_cert, missing_priv_key}}.
-match_cert_keys(CertPaths, PrivKeys) ->
- ?DEBUG("Finding matched certificate keys", []),
- KeyPairs = [{pubkey_from_privkey(PrivKey), PrivKey} || PrivKey <- PrivKeys],
- match_cert_keys(CertPaths, KeyPairs, []).
-
--spec match_cert_keys([{path, [cert()]}], [{pub_key(), priv_key()}],
- [{cert(), priv_key()}])
- -> {ok, [{[cert()], priv_key()}]} | {error, cert(), {bad_cert, missing_priv_key}}.
-match_cert_keys([{path, Certs}|CertPaths], KeyPairs, Result) ->
- [Cert|_] = RevCerts = lists:reverse(Certs),
- PubKey = pubkey_from_cert(Cert),
- case lists:keyfind(PubKey, 1, KeyPairs) of
- false ->
- {error, Cert, {bad_cert, missing_priv_key}};
- {_, PrivKey} ->
- match_cert_keys(CertPaths, KeyPairs, [{RevCerts, PrivKey}|Result])
- end;
-match_cert_keys([], _, Result) ->
- {ok, Result}.
-
--spec pubkey_from_cert(cert()) -> pub_key().
-pubkey_from_cert(Cert) ->
- TBSCert = Cert#'OTPCertificate'.tbsCertificate,
- PubKeyInfo = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
- SubjPubKey = PubKeyInfo#'OTPSubjectPublicKeyInfo'.subjectPublicKey,
- case PubKeyInfo#'OTPSubjectPublicKeyInfo'.algorithm of
- #'PublicKeyAlgorithm'{
- algorithm = ?rsaEncryption} ->
- SubjPubKey;
- #'PublicKeyAlgorithm'{
- algorithm = ?'id-dsa',
- parameters = {params, DSSParams}} ->
- {SubjPubKey, DSSParams};
- #'PublicKeyAlgorithm'{
- algorithm = ?'id-ecPublicKey'} ->
- SubjPubKey
+ fun(Path, Acc) ->
+ Files = wildcard(Path),
+ lists:foldl(fun sets:add_element/2, Acc, Files)
+ end, sets:new(), Paths)
end.
--spec pubkey_from_privkey(priv_key()) -> pub_key().
-pubkey_from_privkey(#'RSAPrivateKey'{modulus = Modulus,
- publicExponent = Exp}) ->
- #'RSAPublicKey'{modulus = Modulus,
- publicExponent = Exp};
-pubkey_from_privkey(#'DSAPrivateKey'{p = P, q = Q, g = G, y = Y}) ->
- {Y, #'Dss-Parms'{p = P, q = Q, g = G}};
-pubkey_from_privkey(#'ECPrivateKey'{publicKey = Key}) ->
- #'ECPoint'{point = Key}.
-
--spec get_cert_paths([cert()], digraph:graph()) -> [{path, [cert()]}].
-get_cert_paths(Certs, G) ->
- ?DEBUG("Building certificates graph", []),
- {NewCerts, OldCerts} =
- lists:partition(
- fun(Cert) ->
- case digraph:vertex(G, Cert) of
- false ->
- digraph:add_vertex(G, Cert),
- true;
- {_, _} ->
- false
- end
- end, Certs),
- add_edges(G, NewCerts, OldCerts),
- add_edges(G, OldCerts, NewCerts),
- add_edges(G, NewCerts, NewCerts),
- lists:flatmap(
- fun(Cert) ->
- case digraph:in_degree(G, Cert) of
- 0 ->
- get_cert_path(G, [Cert]);
- _ ->
- []
+-spec local_certfiles() -> sets:set(filename()).
+local_certfiles() ->
+ Opts = [{Opt, Host} || Opt <- deprecated_options(),
+ Host <- ejabberd_config:get_myhosts()],
+ lists:foldl(
+ fun(OptHost, Acc) ->
+ case ejabberd_config:get_option(OptHost) of
+ undefined -> Acc;
+ Path -> sets:add_element(Path, Acc)
end
- end, Certs).
+ end, sets:new(), Opts).
-add_edges(G, [Cert1|T], L) ->
- case public_key:pkix_is_self_signed(Cert1) of
- true ->
- ok;
- false ->
- lists:foreach(
- fun(Cert2) when Cert1 /= Cert2 ->
- case public_key:pkix_is_issuer(Cert1, Cert2) of
- true ->
- digraph:add_edge(G, Cert1, Cert2);
- false ->
- ok
- end;
- (_) ->
- ok
- end, L)
- end,
- add_edges(G, T, L);
-add_edges(_, [], _) ->
- ok.
+-spec get_certfiles_from_config_options() -> sets:set(filename()).
+get_certfiles_from_config_options() ->
+ Global = global_certfiles(),
+ Local = local_certfiles(),
+ Listen = sets:from_list(ejabberd_listener:get_certfiles()),
+ sets:union([Global, Local, Listen]).
-get_cert_path(G, [Root|_] = Acc) ->
- case digraph:out_edges(G, Root) of
- [] ->
- [{path, Acc}];
- Es ->
- lists:flatmap(
- fun(E) ->
- {_, _, V, _} = digraph:edge(G, E),
- get_cert_path(G, [V|Acc])
- end, Es)
- end.
-
--spec prep_path(file:filename()) -> binary().
+-spec prep_path(file:filename_all()) -> filename().
prep_path(Path0) ->
case filename:pathtype(Path0) of
relative ->
- {ok, CWD} = file:get_cwd(),
- iolist_to_binary(filename:join(CWD, Path0));
+ case file:get_cwd() of
+ {ok, CWD} ->
+ unicode:characters_to_binary(filename:join(CWD, Path0));
+ {error, Reason} ->
+ ?WARNING_MSG("Failed to get current directory name: ~s",
+ [file:format_error(Reason)]),
+ unicode:characters_to_binary(Path0)
+ end;
_ ->
- iolist_to_binary(Path0)
+ unicode:characters_to_binary(Path0)
end.
--ifdef(SHORT_NAME_HASH).
-short_name_hash(IssuerID) ->
- public_key:short_name_hash(IssuerID).
--else.
-short_name_hash(_) ->
- "".
--endif.
-
--spec get_timestamp({utcTime | generalTime, string()}) -> string().
-get_timestamp({utcTime, [Y1,Y2|T]}) ->
- case list_to_integer([Y1,Y2]) of
- N when N >= 50 -> [$1,$9,Y1,Y2|T];
- _ -> [$2,$0,Y1,Y2|T]
- end;
-get_timestamp({generalTime, TS}) ->
- TS.
+-spec stop_ejabberd() -> no_return().
+stop_ejabberd() ->
+ ?CRITICAL_MSG("ejabberd initialization was aborted due to "
+ "invalid certificates configuration", []),
+ ejabberd:halt().
+-spec wildcard(file:filename_all()) -> [filename()].
wildcard(Path) when is_binary(Path) ->
wildcard(binary_to_list(Path));
wildcard(Path) ->
- filelib:wildcard(Path).
-
--spec use_cache() -> boolean().
-use_cache() ->
- ejabberd_config:use_cache(global).
-
--spec init_cache() -> ok.
-init_cache() ->
- ets_cache:new(?CA_CACHE, cache_opts()).
-
--spec delete_cache() -> ok.
-delete_cache() ->
- ets_cache:delete(?CA_CACHE).
-
--spec cache_opts() -> [proplists:property()].
-cache_opts() ->
- MaxSize = ejabberd_config:cache_size(global),
- CacheMissed = ejabberd_config:cache_missed(global),
- LifeTime = case ejabberd_config:cache_life_time(global) of
- infinity -> infinity;
- I -> timer:seconds(I)
- end,
- [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
+ case filelib:wildcard(Path) of
+ [] ->
+ ?WARNING_MSG("Path ~s is empty, please make sure ejabberd has "
+ "sufficient rights to read it", [Path]),
+ [];
+ Files ->
+ [prep_path(File) || File <- Files]
+ end.
+
+-spec select_certfile({filename() | undefined,
+ filename() | undefined,
+ filename() | undefined}) -> filename().
+select_certfile({EC, _, _}) when EC /= undefined -> EC;
+select_certfile({_, RSA, _}) when RSA /= undefined -> RSA;
+select_certfile({_, _, DSA}) when DSA /= undefined -> DSA.
+
+-spec fast_tls_add_certfiles() -> ok.
+fast_tls_add_certfiles() ->
+ lists:foreach(
+ fun({Domain, Files}) ->
+ fast_tls:add_certfile(Domain, select_certfile(Files))
+ end, pkix:get_certfiles()),
+ fast_tls:clear_cache().
+
+reason_to_fmt({invalid_cert, _, _}) ->
+ "Invalid certificate in ~s: ~s";
+reason_to_fmt(_) ->
+ "Failed to read PEM file ~s: ~s".
+
+-spec log_warnings([{filename(), pkix:error_reason()}]) -> ok.
+log_warnings(Warnings) ->
+ lists:foreach(
+ fun({File, Reason}) ->
+ ?WARNING_MSG(reason_to_fmt(Reason),
+ [File, pkix:format_error(Reason)])
+ end, Warnings).
+
+-spec log_errors([{filename(), pkix:error_reason()}]) -> ok.
+log_errors(Errors) ->
+ lists:foreach(
+ fun({File, Reason}) ->
+ ?ERROR_MSG(reason_to_fmt(Reason),
+ [File, pkix:format_error(Reason)])
+ end, Errors).
+
+-spec log_cafile_error({filename(), pkix:error_reason()} | undefined) -> ok.
+log_cafile_error({File, Reason}) ->
+ ?CRITICAL_MSG("Failed to read CA certitificates from ~s: ~s. "
+ "Try to change/set option 'ca_file'",
+ [File, pkix:format_error(Reason)]);
+log_cafile_error(_) ->
+ ok.
diff --git a/src/ejabberd_rdbms.erl b/src/ejabberd_rdbms.erl
index d2c338548..2b69258e5 100644
--- a/src/ejabberd_rdbms.erl
+++ b/src/ejabberd_rdbms.erl
@@ -71,7 +71,7 @@ get_spec(Host) ->
-spec config_reloaded() -> ok.
config_reloaded() ->
- lists:foreach(fun start_host/1, ejabberd_config:get_myhosts()).
+ lists:foreach(fun reload_host/1, ejabberd_config:get_myhosts()).
-spec start_host(binary()) -> ok.
start_host(Host) ->
@@ -96,6 +96,10 @@ stop_host(Host) ->
supervisor:delete_child(?MODULE, SupName),
ok.
+-spec reload_host(binary()) -> ok.
+reload_host(Host) ->
+ ejabberd_sql_sup:reload(Host).
+
%% Returns {true, App} if we have configured sql for the given host
needs_sql(Host) ->
LHost = jid:nameprep(Host),
diff --git a/src/ejabberd_redis.erl b/src/ejabberd_redis.erl
index 857bece0e..ac4421588 100644
--- a/src/ejabberd_redis.erl
+++ b/src/ejabberd_redis.erl
@@ -50,6 +50,7 @@
-define(CALL_TIMEOUT, 60*1000). %% 60 seconds
-include("logger.hrl").
+-include("ejabberd_stacktrace.hrl").
-record(state, {connection :: pid() | undefined,
num :: pos_integer(),
@@ -106,9 +107,9 @@ multi(F) ->
{error, _} = Err -> Err;
Result -> get_result(Result)
end
- catch E:R ->
+ catch ?EX_RULE(E, R, St) ->
erlang:erase(?TR_STACK),
- erlang:raise(E, R, erlang:get_stacktrace())
+ erlang:raise(E, R, ?EX_STACK(St))
end;
_ ->
erlang:error(nested_transaction)
diff --git a/src/ejabberd_regexp.erl b/src/ejabberd_regexp.erl
index 12564e710..284797529 100644
--- a/src/ejabberd_regexp.erl
+++ b/src/ejabberd_regexp.erl
@@ -36,7 +36,7 @@ exec({ReM, ReF, ReA}, {RgM, RgF, RgA}) ->
-spec run(binary(), binary()) -> match | nomatch | {error, any()}.
run(String, Regexp) ->
- case exec({re, run, [String, Regexp, [{capture, none}]]},
+ case exec({re, run, [String, Regexp, [{capture, none}, unicode]]},
{regexp, first_match, [binary_to_list(String),
binary_to_list(Regexp)]})
of
diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl
index bd9a87ce2..edfcf932a 100644
--- a/src/ejabberd_router.erl
+++ b/src/ejabberd_router.erl
@@ -71,6 +71,7 @@
-include("logger.hrl").
-include("ejabberd_router.hrl").
-include("xmpp.hrl").
+-include("ejabberd_stacktrace.hrl").
-callback init() -> any().
-callback register_route(binary(), binary(), local_hint(),
@@ -90,10 +91,9 @@ start_link() ->
-spec route(stanza()) -> ok.
route(Packet) ->
try do_route(Packet)
- catch E:R ->
- St = erlang:get_stacktrace(),
+ catch ?EX_RULE(E, R, St) ->
?ERROR_MSG("failed to route packet:~n~s~nReason = ~p",
- [xmpp:pp(Packet), {E, {R, St}}])
+ [xmpp:pp(Packet), {E, {R, ?EX_STACK(St)}}])
end.
-spec route(jid(), jid(), xmlel() | stanza()) -> ok.
diff --git a/src/ejabberd_router_sql.erl b/src/ejabberd_router_sql.erl
index edf06dfe0..fa6579ce4 100644
--- a/src/ejabberd_router_sql.erl
+++ b/src/ejabberd_router_sql.erl
@@ -32,6 +32,7 @@
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
-include("ejabberd_router.hrl").
+-include("ejabberd_stacktrace.hrl").
%%%===================================================================
%%% API
@@ -121,12 +122,11 @@ row_to_route(Domain, {ServerHost, NodeS, PidS, LocalHintS} = Row) ->
local_hint = dec_local_hint(LocalHintS)}]
catch _:{bad_node, _} ->
[];
- E:R ->
- St = erlang:get_stacktrace(),
+ ?EX_RULE(E, R, St) ->
?ERROR_MSG("failed to decode row from 'route' table:~n"
"Row = ~p~n"
"Domain = ~s~n"
"Reason = ~p",
- [Row, Domain, {E, {R, St}}]),
+ [Row, Domain, {E, {R, ?EX_STACK(St)}}]),
[]
end.
diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl
index 126700f10..b721ca1e7 100644
--- a/src/ejabberd_s2s.erl
+++ b/src/ejabberd_s2s.erl
@@ -55,12 +55,10 @@
transform_options/1, opt_type/1]).
-include("logger.hrl").
-
-include("xmpp.hrl").
-
-include("ejabberd_commands.hrl").
-
-include_lib("public_key/include/public_key.hrl").
+-include("ejabberd_stacktrace.hrl").
-define(PKIXEXPLICIT, 'OTP-PUB-KEY').
@@ -94,10 +92,9 @@ start_link() ->
route(Packet) ->
try do_route(Packet)
- catch E:R ->
- St = erlang:get_stacktrace(),
+ catch ?EX_RULE(E, R, St) ->
?ERROR_MSG("failed to route packet:~n~s~nReason = ~p",
- [xmpp:pp(Packet), {E, {R, St}}])
+ [xmpp:pp(Packet), {E, {R, ?EX_STACK(St)}}])
end.
clean_temporarily_blocked_table() ->
@@ -738,7 +735,12 @@ opt_type(s2s_use_starttls) ->
required_trusted
end;
opt_type(s2s_zlib) ->
- fun(B) when is_boolean(B) -> B end;
+ fun(true) ->
+ ejabberd:start_app(ezlib),
+ true;
+ (false) ->
+ false
+ end;
opt_type(s2s_timeout) ->
fun(I) when is_integer(I), I >= 0 -> timer:seconds(I);
(infinity) -> infinity;
diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl
index 62227984e..3ceb9c058 100644
--- a/src/ejabberd_s2s_in.erl
+++ b/src/ejabberd_s2s_in.erl
@@ -21,12 +21,10 @@
%%%-------------------------------------------------------------------
-module(ejabberd_s2s_in).
-behaviour(xmpp_stream_in).
--behaviour(xmpp_socket).
+-behaviour(ejabberd_listener).
-%% xmpp_socket callbacks
--export([start/2, start_link/2, socket_type/0]).
%% ejabberd_listener callbacks
--export([listen_opt_type/1]).
+-export([start/2, start_link/2, accept/1, listen_opt_type/1, listen_options/0]).
%% xmpp_stream_in callbacks
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
@@ -53,16 +51,8 @@
%%% API
%%%===================================================================
start(SockData, Opts) ->
- case proplists:get_value(supervisor, Opts, true) of
- true ->
- case supervisor:start_child(ejabberd_s2s_in_sup, [SockData, Opts]) of
- {ok, undefined} -> ignore;
- Res -> Res
- end;
- _ ->
- xmpp_stream_in:start(?MODULE, [SockData, Opts],
- ejabberd_config:fsm_limit_opts(Opts))
- end.
+ xmpp_stream_in:start(?MODULE, [SockData, Opts],
+ ejabberd_config:fsm_limit_opts(Opts)).
start_link(SockData, Opts) ->
xmpp_stream_in:start_link(?MODULE, [SockData, Opts],
@@ -77,8 +67,8 @@ close(Ref, Reason) ->
stop(Ref) ->
xmpp_stream_in:stop(Ref).
-socket_type() ->
- xml_stream.
+accept(Ref) ->
+ xmpp_stream_in:accept(Ref).
-spec send(pid(), xmpp_element()) -> ok;
(state(), xmpp_element()) -> state().
@@ -145,16 +135,16 @@ process_closed(#{server := LServer} = State, Reason) ->
%%%===================================================================
%%% xmpp_stream_in callbacks
%%%===================================================================
-tls_options(#{tls_options := TLSOpts, server_host := LServer}) ->
+tls_options(#{tls_options := TLSOpts, lserver := LServer}) ->
ejabberd_s2s:tls_options(LServer, TLSOpts).
-tls_required(#{server_host := LServer}) ->
+tls_required(#{lserver := LServer}) ->
ejabberd_s2s:tls_required(LServer).
-tls_enabled(#{server_host := LServer}) ->
+tls_enabled(#{lserver := LServer}) ->
ejabberd_s2s:tls_enabled(LServer).
-compress_methods(#{server_host := LServer}) ->
+compress_methods(#{lserver := LServer}) ->
case ejabberd_s2s:zlib_enabled(LServer) of
true -> [<<"zlib">>];
false -> []
@@ -191,23 +181,23 @@ handle_auth_success(RServer, Mech, _AuthModule,
?INFO_MSG("(~s) Accepted inbound s2s ~s authentication ~s -> ~s (~s)",
[xmpp_socket:pp(Socket), Mech, RServer, LServer,
ejabberd_config:may_hide_data(misc:ip_to_list(IP))]),
- State1 = case ejabberd_s2s:allow_host(ServerHost, RServer) of
+ State1 = case ejabberd_s2s:allow_host(LServer, RServer) of
true ->
AuthDomains1 = sets:add_element(RServer, AuthDomains),
State0 = change_shaper(State, RServer),
State0#{auth_domains => AuthDomains1};
false ->
State
- end,
+ end,
ejabberd_hooks:run_fold(s2s_in_auth_result, ServerHost, State1, [true, RServer]).
handle_auth_failure(RServer, Mech, Reason,
#{socket := Socket, ip := IP,
server_host := ServerHost,
lserver := LServer} = State) ->
- ?INFO_MSG("(~s) Failed inbound s2s ~s authentication ~s -> ~s (~s): ~s",
- [xmpp_socket:pp(Socket), Mech, RServer, LServer,
- ejabberd_config:may_hide_data(misc:ip_to_list(IP)), Reason]),
+ ?WARNING_MSG("(~s) Failed inbound s2s ~s authentication ~s -> ~s (~s): ~s",
+ [xmpp_socket:pp(Socket), Mech, RServer, LServer,
+ ejabberd_config:may_hide_data(misc:ip_to_list(IP)), Reason]),
ejabberd_hooks:run_fold(s2s_in_auth_result,
ServerHost, State, [false, RServer]).
@@ -231,7 +221,7 @@ handle_authenticated_packet(Pkt0, #{ip := {IP, _}} = State) ->
case Pkt1 of
drop -> ok;
_ -> ejabberd_router:route(Pkt1)
- end,
+ end,
State2;
{error, Err} ->
send(State, Err)
@@ -259,9 +249,9 @@ init([State, Opts]) ->
(_) -> false
end, Opts),
TLSOpts2 = case proplists:get_bool(tls_compression, Opts) of
- false -> [compression_none | TLSOpts1];
- true -> TLSOpts1
- end,
+ false -> [compression_none | TLSOpts1];
+ true -> TLSOpts1
+ end,
Timeout = ejabberd_config:negotiation_timeout(),
State1 = State#{tls_options => TLSOpts2,
auth_domains => sets:new(),
@@ -337,7 +327,7 @@ check_to(#jid{lserver = LServer}, _State) ->
ejabberd_router:is_my_route(LServer).
-spec set_idle_timeout(state()) -> state().
-set_idle_timeout(#{server_host := LServer,
+set_idle_timeout(#{lserver := LServer,
established := true} = State) ->
Timeout = ejabberd_s2s:get_idle_timeout(LServer),
xmpp_stream_in:set_timeout(State, Timeout);
@@ -350,36 +340,22 @@ change_shaper(#{shaper := ShaperName, server_host := ServerHost} = State,
Shaper = acl:match_rule(ServerHost, ShaperName, jid:make(RServer)),
xmpp_stream_in:change_shaper(State, ejabberd_shaper:new(Shaper)).
--spec listen_opt_type(atom()) -> fun((any()) -> any()) | [atom()].
-listen_opt_type(shaper) -> fun acl:shaper_rules_validator/1;
listen_opt_type(certfile = Opt) ->
fun(S) ->
?WARNING_MSG("Listening option '~s' for ~s is deprecated, use "
"'certfiles' global option instead", [Opt, ?MODULE]),
- ejabberd_pkix:add_certfile(S),
- iolist_to_binary(S)
- end;
-listen_opt_type(ciphers) -> ejabberd_s2s:opt_type(s2s_ciphers);
-listen_opt_type(dhfile) -> ejabberd_s2s:opt_type(s2s_dhfile);
-listen_opt_type(cafile) -> ejabberd_s2s:opt_type(s2s_cafile);
-listen_opt_type(protocol_options) -> ejabberd_s2s:opt_type(s2s_protocol_options);
-listen_opt_type(tls_compression) -> ejabberd_s2s:opt_type(s2s_tls_compression);
-listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(supervisor) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(max_stanza_size) ->
- fun(I) when is_integer(I), I>0 -> I;
- (unlimited) -> infinity;
- (infinity) -> infinity
- end;
-listen_opt_type(max_fsm_queue) ->
- fun(I) when is_integer(I), I>0 -> I end;
-listen_opt_type(inet) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(inet6) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(backlog) ->
- fun(I) when is_integer(I), I>0 -> I end;
-listen_opt_type(accept_interval) ->
- fun(I) when is_integer(I), I>=0 -> I end;
-listen_opt_type(_) ->
- [shaper, certfile, ciphers, dhfile, cafile, protocol_options,
- tls_compression, tls, max_fsm_queue, backlog, inet, inet6,
- accept_interval].
+ {ok, File} = ejabberd_pkix:add_certfile(S),
+ File
+ end.
+
+listen_options() ->
+ [{shaper, none},
+ {certfile, undefined},
+ {ciphers, undefined},
+ {dhfile, undefined},
+ {cafile, undefined},
+ {protocol_options, undefined},
+ {tls, false},
+ {tls_compression, false},
+ {max_stanza_size, infinity},
+ {max_fsm_queue, 5000}].
diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl
index 3c40d805a..db8f3d0a3 100644
--- a/src/ejabberd_s2s_out.erl
+++ b/src/ejabberd_s2s_out.erl
@@ -138,9 +138,9 @@ host_down(Host) ->
process_auth_result(#{server := LServer, remote_server := RServer} = State,
{false, Reason}) ->
Delay = get_delay(),
- ?INFO_MSG("Failed to establish outbound s2s connection ~s -> ~s: "
- "authentication failed; bouncing for ~p seconds",
- [LServer, RServer, Delay]),
+ ?WARNING_MSG("Failed to establish outbound s2s connection ~s -> ~s: "
+ "authentication failed; bouncing for ~p seconds",
+ [LServer, RServer, Delay]),
State1 = State#{on_route => bounce, stop_reason => Reason},
State2 = close(State1),
State3 = bounce_queue(State2),
@@ -157,9 +157,9 @@ process_closed(#{server := LServer, remote_server := RServer,
process_closed(#{server := LServer, remote_server := RServer} = State,
Reason) ->
Delay = get_delay(),
- ?INFO_MSG("Failed to establish outbound s2s connection ~s -> ~s: ~s; "
- "bouncing for ~p seconds",
- [LServer, RServer, format_error(Reason), Delay]),
+ ?WARNING_MSG("Failed to establish outbound s2s connection ~s -> ~s: ~s; "
+ "bouncing for ~p seconds",
+ [LServer, RServer, format_error(Reason), Delay]),
State1 = State#{on_route => bounce},
State2 = bounce_queue(State1),
xmpp_stream_out:set_timeout(State2, timer:seconds(Delay)).
@@ -223,10 +223,10 @@ handle_auth_failure(Mech, Reason,
remote_server := RServer,
server_host := ServerHost,
server := LServer} = State) ->
- ?INFO_MSG("(~s) Failed outbound s2s ~s authentication ~s -> ~s (~s): ~s",
- [xmpp_socket:pp(Socket), Mech, LServer, RServer,
- ejabberd_config:may_hide_data(misc:ip_to_list(IP)),
- xmpp_stream_out:format_error(Reason)]),
+ ?WARNING_MSG("(~s) Failed outbound s2s ~s authentication ~s -> ~s (~s): ~s",
+ [xmpp_socket:pp(Socket), Mech, LServer, RServer,
+ ejabberd_config:may_hide_data(misc:ip_to_list(IP)),
+ xmpp_stream_out:format_error(Reason)]),
ejabberd_hooks:run_fold(s2s_out_auth_result, ServerHost, State, [{false, Reason}]).
handle_packet(Pkt, #{server_host := ServerHost} = State) ->
diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl
index c39a023ab..0f40822b7 100644
--- a/src/ejabberd_service.erl
+++ b/src/ejabberd_service.erl
@@ -21,20 +21,19 @@
%%%-------------------------------------------------------------------
-module(ejabberd_service).
-behaviour(xmpp_stream_in).
--behaviour(xmpp_socket).
+-behaviour(ejabberd_listener).
-protocol({xep, 114, '1.6'}).
-%% xmpp_socket callbacks
--export([start/2, start_link/2, socket_type/0, close/1, close/2]).
%% ejabberd_listener callbacks
--export([listen_opt_type/1, transform_listen_option/2]).
+-export([start/2, start_link/2, accept/1]).
+-export([listen_opt_type/1, listen_options/0, transform_listen_option/2]).
%% xmpp_stream_in callbacks
-export([init/1, handle_info/2, terminate/2, code_change/3]).
-export([handle_stream_start/2, handle_auth_success/4, handle_auth_failure/4,
handle_authenticated_packet/2, get_password_fun/1, tls_options/1]).
%% API
--export([send/2]).
+-export([send/2, close/1, close/2]).
-include("xmpp.hrl").
-include("logger.hrl").
@@ -53,8 +52,8 @@ start_link(SockData, Opts) ->
xmpp_stream_in:start_link(?MODULE, [SockData, Opts],
ejabberd_config:fsm_limit_opts(Opts)).
-socket_type() ->
- xml_stream.
+accept(Ref) ->
+ xmpp_stream_in:accept(Ref).
-spec send(pid(), xmpp_element()) -> ok;
(state(), xmpp_element()) -> state().
@@ -79,7 +78,8 @@ tls_options(#{tls_options := TLSOptions}) ->
init([State, Opts]) ->
Access = proplists:get_value(access, Opts, all),
- Shaper = proplists:get_value(shaper_rule, Opts, none),
+ Shaper = proplists:get_value(shaper, Opts,
+ proplists:get_value(shaper_rule, Opts, none)),
GlobalPassword = proplists:get_value(password, Opts, random_password()),
HostOpts = proplists:get_value(hosts, Opts, [{global, GlobalPassword}]),
HostOpts1 = lists:map(
@@ -146,10 +146,10 @@ get_password_fun(#{remote_server := RemoteServer,
{ok, Password} ->
{Password, undefined};
error ->
- ?INFO_MSG("(~s) Domain ~s is unconfigured for "
- "external component from ~s",
- [xmpp_socket:pp(Socket), RemoteServer,
- ejabberd_config:may_hide_data(misc:ip_to_list(IP))]),
+ ?WARNING_MSG("(~s) Domain ~s is unconfigured for "
+ "external component from ~s",
+ [xmpp_socket:pp(Socket), RemoteServer,
+ ejabberd_config:may_hide_data(misc:ip_to_list(IP))]),
{false, undefined}
end
end.
@@ -177,11 +177,11 @@ handle_auth_success(_, Mech, _,
handle_auth_failure(_, Mech, Reason,
#{remote_server := RemoteServer,
socket := Socket, ip := IP} = State) ->
- ?INFO_MSG("(~s) Failed external component ~s authentication "
- "for ~s from ~s: ~s",
- [xmpp_socket:pp(Socket), Mech, RemoteServer,
- ejabberd_config:may_hide_data(misc:ip_to_list(IP)),
- Reason]),
+ ?WARNING_MSG("(~s) Failed external component ~s authentication "
+ "for ~s from ~s: ~s",
+ [xmpp_socket:pp(Socket), Mech, RemoteServer,
+ ejabberd_config:may_hide_data(misc:ip_to_list(IP)),
+ Reason]),
State.
handle_authenticated_packet(Pkt0, #{ip := {IP, _}, lang := Lang} = State)
@@ -281,21 +281,12 @@ transform_listen_option({host, Host, Os}, Opts) ->
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
--spec listen_opt_type(atom()) -> fun((any()) -> any()) | [atom()].
-listen_opt_type(access) -> fun acl:access_rules_validator/1;
-listen_opt_type(shaper_rule) -> fun acl:shaper_rules_validator/1;
-listen_opt_type(certfile) ->
- fun(S) ->
- ejabberd_pkix:add_certfile(S),
- iolist_to_binary(S)
+listen_opt_type(shaper_rule) ->
+ fun(V) ->
+ ?WARNING_MSG("Listening option 'shaper_rule' of module ~s "
+ "is renamed to 'shaper'", [?MODULE]),
+ acl:shaper_rules_validator(V)
end;
-listen_opt_type(ciphers) -> fun iolist_to_binary/1;
-listen_opt_type(dhfile) -> fun misc:try_read_file/1;
-listen_opt_type(cafile) -> fun misc:try_read_file/1;
-listen_opt_type(protocol_options) ->
- fun(Options) -> str:join(Options, <<"|">>) end;
-listen_opt_type(tls_compression) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(check_from) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(password) -> fun iolist_to_binary/1;
listen_opt_type(hosts) ->
@@ -310,21 +301,22 @@ listen_opt_type(hosts) ->
end, HostOpts)
end;
listen_opt_type(global_routes) ->
- fun(B) when is_boolean(B) -> B end;
-listen_opt_type(max_stanza_size) ->
- fun(I) when is_integer(I) -> I;
- (unlimited) -> infinity;
- (infinity) -> infinity
- end;
-listen_opt_type(max_fsm_queue) ->
- fun(I) when is_integer(I), I>0 -> I end;
-listen_opt_type(inet) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(inet6) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(backlog) ->
- fun(I) when is_integer(I), I>0 -> I end;
-listen_opt_type(accept_interval) ->
- fun(I) when is_integer(I), I>=0 -> I end;
-listen_opt_type(_) ->
- [access, shaper_rule, certfile, ciphers, dhfile, cafile, tls,
- protocol_options, tls_compression, password, hosts, check_from,
- max_fsm_queue, global_routes, backlog, inet, inet6, accept_interval].
+ fun(B) when is_boolean(B) -> B end.
+
+listen_options() ->
+ [{access, all},
+ {shaper, none},
+ {shaper_rule, none},
+ {certfile, undefined},
+ {ciphers, undefined},
+ {dhfile, undefined},
+ {cafile, undefined},
+ {protocol_options, undefined},
+ {tls, false},
+ {tls_compression, false},
+ {max_stanza_size, infinity},
+ {max_fsm_queue, 5000},
+ {password, undefined},
+ {hosts, []},
+ {check_from, true},
+ {global_routes, true}].
diff --git a/src/ejabberd_sip.erl b/src/ejabberd_sip.erl
index e49fb4841..f9061edbe 100644
--- a/src/ejabberd_sip.erl
+++ b/src/ejabberd_sip.erl
@@ -24,25 +24,28 @@
%%%-------------------------------------------------------------------
-module(ejabberd_sip).
+-behaviour(ejabberd_listener).
-ifndef(SIP).
-include("logger.hrl").
--export([socket_type/0, start/2, listen_opt_type/1]).
-log_error() ->
- ?CRITICAL_MSG("ejabberd is not compiled with SIP support", []).
-socket_type() ->
- log_error(),
- raw.
-listen_opt_type(_) ->
- log_error(),
- [].
+-export([accept/1, start/2, start_link/2, listen_options/0]).
+fail() ->
+ ?CRITICAL_MSG("Listening module ~s is not available: "
+ "ejabberd is not compiled with SIP support",
+ [?MODULE]),
+ erlang:error(sip_not_compiled).
+accept(_) ->
+ fail().
+listen_options() ->
+ fail().
start(_, _) ->
- log_error(),
- {error, sip_not_compiled}.
+ fail().
+start_link(_, _) ->
+ fail().
-else.
%% API
-export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
- socket_type/0, listen_opt_type/1]).
+ start_link/2, accept/1, listen_options/0]).
%%%===================================================================
@@ -62,8 +65,11 @@ udp_recv(Sock, Addr, Port, Data, Opts) ->
start(Opaque, Opts) ->
esip_socket:start(Opaque, Opts).
-socket_type() ->
- raw.
+start_link({gen_tcp, Sock}, Opts) ->
+ esip_socket:start_link(Sock, Opts).
+
+accept(_) ->
+ ok.
set_certfile(Opts) ->
case lists:keymember(certfile, 1, Opts) of
@@ -83,19 +89,9 @@ set_certfile(Opts) ->
end
end.
-listen_opt_type(certfile) ->
- fun(S) ->
- %% We cannot deprecate the option for now:
- %% I think SIP clients are too stupid to set SNI
- ejabberd_pkix:add_certfile(S),
- iolist_to_binary(S)
- end;
-listen_opt_type(tls) ->
- fun(B) when is_boolean(B) -> B end;
-listen_opt_type(accept_interval) ->
- fun(I) when is_integer(I), I>=0 -> I end;
-listen_opt_type(_) ->
- [tls, certfile, accept_interval].
+listen_options() ->
+ [{tls, false},
+ {certfile, undefined}].
%%%===================================================================
%%% Internal functions
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index d9e211656..873c32da0 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -47,11 +47,9 @@
disconnect_removed_user/2,
get_user_resources/2,
get_user_present_resources/2,
- set_presence/7,
- unset_presence/6,
+ set_presence/6,
+ unset_presence/5,
close_session_unset_presence/5,
- set_offline_info/5,
- get_offline_info/4,
dirty_get_sessions_list/0,
dirty_get_my_sessions_list/0,
get_vh_session_list/1,
@@ -68,6 +66,8 @@
get_session_sids/2,
get_user_info/2,
get_user_info/3,
+ set_user_info/5,
+ del_user_info/4,
get_user_ip/3,
get_max_user_sessions/2,
get_all_pids/0,
@@ -78,8 +78,7 @@
host_down/1,
make_sid/0,
clean_cache/1,
- config_reloaded/0,
- is_online/1
+ config_reloaded/0
]).
-export([init/1, handle_call/3, handle_cast/2,
@@ -91,6 +90,7 @@
-include("ejabberd_commands.hrl").
-include("ejabberd_sm.hrl").
+-include("ejabberd_stacktrace.hrl").
-callback init() -> ok | {error, any()}.
-callback set_session(#session{}) -> ok | {error, any()}.
@@ -141,11 +141,10 @@ route(Packet) ->
?DEBUG("hook dropped stanza:~n~s", [xmpp:pp(Packet)]);
Packet1 ->
try do_route(Packet1), ok
- catch E:R ->
- St = erlang:get_stacktrace(),
+ catch ?EX_RULE(E, R, St) ->
?ERROR_MSG("failed to route packet:~n~s~nReason = ~p",
[xmpp:pp(Packet1),
- {E, {R, St}}])
+ {E, {R, ?EX_STACK(St)}}])
end
end.
@@ -211,14 +210,14 @@ get_user_resources(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
- Ss = online(get_sessions(Mod, LUser, LServer)),
+ Ss = get_sessions(Mod, LUser, LServer),
[element(3, S#session.usr) || S <- clean_session_list(Ss)].
-spec get_user_present_resources(binary(), binary()) -> [tuple()].
get_user_present_resources(LUser, LServer) ->
Mod = get_sm_backend(LServer),
- Ss = online(get_sessions(Mod, LUser, LServer)),
+ Ss = get_sessions(Mod, LUser, LServer),
[{S#session.priority, element(3, S#session.usr)}
|| S <- clean_session_list(Ss), is_integer(S#session.priority)].
@@ -229,7 +228,7 @@ get_user_ip(User, Server, Resource) ->
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
- case online(get_sessions(Mod, LUser, LServer, LResource)) of
+ case get_sessions(Mod, LUser, LServer, LResource) of
[] ->
undefined;
Ss ->
@@ -242,7 +241,7 @@ get_user_info(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
- Ss = online(get_sessions(Mod, LUser, LServer)),
+ Ss = get_sessions(Mod, LUser, LServer),
[{LResource, [{node, node(Pid)}, {ts, Ts}, {pid, Pid},
{priority, Priority} | Info]}
|| #session{usr = {_, _, LResource},
@@ -257,7 +256,7 @@ get_user_info(User, Server, Resource) ->
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
- case online(get_sessions(Mod, LUser, LServer, LResource)) of
+ case get_sessions(Mod, LUser, LServer, LResource) of
[] ->
offline;
Ss ->
@@ -269,27 +268,87 @@ get_user_info(User, Server, Resource) ->
|Session#session.info]
end.
+-spec set_user_info(binary(), binary(), binary(), atom(), term()) -> ok | {error, any()}.
+set_user_info(User, Server, Resource, Key, Val) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LResource = jid:resourceprep(Resource),
+ Mod = get_sm_backend(LServer),
+ case get_sessions(Mod, LUser, LServer, LResource) of
+ [] -> {error, notfound};
+ Ss ->
+ lists:foldl(
+ fun(#session{sid = {_, Pid},
+ info = Info} = Session, _) when Pid == self() ->
+ Info1 = lists:keystore(Key, 1, Info, {Key, Val}),
+ set_session(Session#session{info = Info1});
+ (_, Acc) ->
+ Acc
+ end, {error, not_owner}, Ss)
+ end.
+
+-spec del_user_info(binary(), binary(), binary(), atom()) -> ok | {error, any()}.
+del_user_info(User, Server, Resource, Key) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LResource = jid:resourceprep(Resource),
+ Mod = get_sm_backend(LServer),
+ case get_sessions(Mod, LUser, LServer, LResource) of
+ [] -> {error, notfound};
+ Ss ->
+ lists:foldl(
+ fun(#session{sid = {_, Pid},
+ info = Info} = Session, _) when Pid == self() ->
+ Info1 = lists:keydelete(Key, 1, Info),
+ set_session(Session#session{info = Info1});
+ (_, Acc) ->
+ Acc
+ end, {error, not_owner}, Ss)
+ end.
+
-spec set_presence(sid(), binary(), binary(), binary(),
- prio(), presence(), info()) -> ok.
+ prio(), presence()) -> ok | {error, notfound}.
-set_presence(SID, User, Server, Resource, Priority,
- Presence, Info) ->
- set_session(SID, User, Server, Resource, Priority,
- Info),
- ejabberd_hooks:run(set_presence_hook,
- jid:nameprep(Server),
- [User, Server, Resource, Presence]).
+set_presence(SID, User, Server, Resource, Priority, Presence) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LResource = jid:resourceprep(Resource),
+ Mod = get_sm_backend(LServer),
+ case get_sessions(Mod, LUser, LServer, LResource) of
+ [] -> {error, notfound};
+ Ss ->
+ case lists:keyfind(SID, #session.sid, Ss) of
+ #session{info = Info} ->
+ set_session(SID, User, Server, Resource, Priority, Info),
+ ejabberd_hooks:run(set_presence_hook,
+ LServer,
+ [User, Server, Resource, Presence]);
+ false ->
+ {error, notfound}
+ end
+ end.
-spec unset_presence(sid(), binary(), binary(),
- binary(), binary(), info()) -> ok.
+ binary(), binary()) -> ok | {error, notfound}.
-unset_presence(SID, User, Server, Resource, Status,
- Info) ->
- set_session(SID, User, Server, Resource, undefined,
- Info),
- ejabberd_hooks:run(unset_presence_hook,
- jid:nameprep(Server),
- [User, Server, Resource, Status]).
+unset_presence(SID, User, Server, Resource, Status) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LResource = jid:resourceprep(Resource),
+ Mod = get_sm_backend(LServer),
+ case get_sessions(Mod, LUser, LServer, LResource) of
+ [] -> {error, notfound};
+ Ss ->
+ case lists:keyfind(SID, #session.sid, Ss) of
+ #session{info = Info} ->
+ set_session(SID, User, Server, Resource, undefined, Info),
+ ejabberd_hooks:run(unset_presence_hook,
+ LServer,
+ [User, Server, Resource, Status]);
+ false ->
+ {error, notfound}
+ end
+ end.
-spec close_session_unset_presence(sid(), binary(), binary(),
binary(), binary()) -> ok.
@@ -316,7 +375,7 @@ get_session_sid(User, Server, Resource) ->
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
- case online(get_sessions(Mod, LUser, LServer, LResource)) of
+ case get_sessions(Mod, LUser, LServer, LResource) of
[] ->
none;
Ss ->
@@ -330,43 +389,15 @@ get_session_sids(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
- Sessions = online(get_sessions(Mod, LUser, LServer)),
+ Sessions = get_sessions(Mod, LUser, LServer),
[SID || #session{sid = SID} <- Sessions].
--spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok.
-
-set_offline_info(SID, User, Server, Resource, Info) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
- LResource = jid:resourceprep(Resource),
- set_session(SID, LUser, LServer, LResource, undefined, [offline | Info]).
-
--spec get_offline_info(erlang:timestamp(), binary(), binary(),
- binary()) -> none | info().
-
-get_offline_info(Time, User, Server, Resource) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
- LResource = jid:resourceprep(Resource),
- Mod = get_sm_backend(LServer),
- case get_sessions(Mod, LUser, LServer, LResource) of
- [#session{sid = {Time, _}, info = Info}] ->
- case proplists:get_bool(offline, Info) of
- true ->
- Info;
- false ->
- none
- end;
- _ ->
- none
- end.
-
-spec dirty_get_sessions_list() -> [ljid()].
dirty_get_sessions_list() ->
lists:flatmap(
fun(Mod) ->
- [S#session.usr || S <- online(get_sessions(Mod))]
+ [S#session.usr || S <- get_sessions(Mod)]
end, get_sm_backends()).
-spec dirty_get_my_sessions_list() -> [#session{}].
@@ -374,7 +405,7 @@ dirty_get_sessions_list() ->
dirty_get_my_sessions_list() ->
lists:flatmap(
fun(Mod) ->
- [S || S <- online(get_sessions(Mod)),
+ [S || S <- get_sessions(Mod),
node(element(2, S#session.sid)) == node()]
end, get_sm_backends()).
@@ -383,14 +414,14 @@ dirty_get_my_sessions_list() ->
get_vh_session_list(Server) ->
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
- [S#session.usr || S <- online(get_sessions(Mod, LServer))].
+ [S#session.usr || S <- get_sessions(Mod, LServer)].
-spec get_all_pids() -> [pid()].
get_all_pids() ->
lists:flatmap(
fun(Mod) ->
- [element(2, S#session.sid) || S <- online(get_sessions(Mod))]
+ [element(2, S#session.sid) || S <- get_sessions(Mod)]
end, get_sm_backends()).
-spec get_vh_session_number(binary()) -> non_neg_integer().
@@ -398,7 +429,7 @@ get_all_pids() ->
get_vh_session_number(Server) ->
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
- length(online(get_sessions(Mod, LServer))).
+ length(get_sessions(Mod, LServer)).
%% Why the hell do we have so many similar kicks?
c2s_handle_info(#{lang := Lang} = State, replaced) ->
@@ -514,9 +545,13 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
LResource = jid:resourceprep(Resource),
US = {LUser, LServer},
USR = {LUser, LServer, LResource},
+ set_session(#session{sid = SID, usr = USR, us = US,
+ priority = Priority, info = Info}).
+
+-spec set_session(#session{}) -> ok | {error, any()}.
+set_session(#session{us = {LUser, LServer}} = Session) ->
Mod = get_sm_backend(LServer),
- case Mod:set_session(#session{sid = SID, usr = USR, us = US,
- priority = Priority, info = Info}) of
+ case Mod:set_session(Session) of
ok ->
case use_cache(Mod, LServer) of
true ->
@@ -579,16 +614,6 @@ delete_session(Mod, #session{usr = {LUser, LServer, _}} = Session) ->
ok
end.
--spec online([#session{}]) -> [#session{}].
-
-online(Sessions) ->
- lists:filter(fun is_online/1, Sessions).
-
--spec is_online(#session{}) -> boolean().
-
-is_online(#session{info = Info}) ->
- not proplists:get_bool(offline, Info).
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec do_route(jid(), term()) -> any().
do_route(#jid{lresource = <<"">>} = To, Term) ->
@@ -600,7 +625,7 @@ do_route(To, Term) ->
?DEBUG("broadcasting ~p to ~s", [Term, jid:encode(To)]),
{U, S, R} = jid:tolower(To),
Mod = get_sm_backend(S),
- case online(get_sessions(Mod, U, S, R)) of
+ case get_sessions(Mod, U, S, R) of
[] ->
?DEBUG("dropping broadcast to unavailable resourse: ~p", [Term]);
Ss ->
@@ -631,7 +656,7 @@ do_route(#presence{to = To, type = T} = Packet)
ejabberd_c2s:route(Pid, {route, Packet1});
(_) ->
ok
- end, online(get_sessions(Mod, LUser, LServer)));
+ end, get_sessions(Mod, LUser, LServer));
false ->
ok
end;
@@ -660,7 +685,7 @@ do_route(Packet) ->
To = xmpp:get_to(Packet),
{LUser, LServer, LResource} = jid:tolower(To),
Mod = get_sm_backend(LServer),
- case online(get_sessions(Mod, LUser, LServer, LResource)) of
+ case get_sessions(Mod, LUser, LServer, LResource) of
[] ->
case Packet of
#message{type = T} when T == chat; T == normal ->
@@ -708,8 +733,8 @@ route_message(#message{to = To, type = Type} = Packet) ->
(P >= 0) and (Type == headline) ->
LResource = jid:resourceprep(R),
Mod = get_sm_backend(LServer),
- case online(get_sessions(Mod, LUser, LServer,
- LResource)) of
+ case get_sessions(Mod, LUser, LServer,
+ LResource) of
[] ->
ok; % Race condition
Ss ->
@@ -780,13 +805,9 @@ check_for_sessions_to_replace(User, Server, Resource) ->
check_existing_resources(LUser, LServer, LResource) ->
Mod = get_sm_backend(LServer),
Ss = get_sessions(Mod, LUser, LServer, LResource),
- {OnlineSs, OfflineSs} = lists:partition(fun is_online/1, Ss),
- lists:foreach(fun(S) ->
- delete_session(Mod, S)
- end, OfflineSs),
- if OnlineSs == [] -> ok;
+ if Ss == [] -> ok;
true ->
- SIDs = [SID || #session{sid = SID} <- OnlineSs],
+ SIDs = [SID || #session{sid = SID} <- Ss],
MaxSID = lists:max(SIDs),
lists:foreach(fun ({_, Pid} = S) when S /= MaxSID ->
ejabberd_c2s:route(Pid, replaced);
@@ -806,22 +827,17 @@ get_resource_sessions(User, Server, Resource) ->
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
- [S#session.sid || S <- online(get_sessions(Mod, LUser, LServer, LResource))].
+ [S#session.sid || S <- get_sessions(Mod, LUser, LServer, LResource)].
-spec check_max_sessions(binary(), binary()) -> ok | replaced.
check_max_sessions(LUser, LServer) ->
Mod = get_sm_backend(LServer),
Ss = get_sessions(Mod, LUser, LServer),
- {OnlineSs, OfflineSs} = lists:partition(fun is_online/1, Ss),
MaxSessions = get_max_user_sessions(LUser, LServer),
- if length(OnlineSs) =< MaxSessions -> ok;
+ if length(Ss) =< MaxSessions -> ok;
true ->
- #session{sid = {_, Pid}} = lists:min(OnlineSs),
+ #session{sid = {_, Pid}} = lists:min(Ss),
ejabberd_c2s:route(Pid, replaced)
- end,
- if length(OfflineSs) =< MaxSessions -> ok;
- true ->
- delete_session(Mod, lists:min(OfflineSs))
end.
%% Get the user_max_session setting
@@ -843,7 +859,7 @@ get_max_user_sessions(LUser, Host) ->
force_update_presence({LUser, LServer}) ->
Mod = get_sm_backend(LServer),
- Ss = online(get_sessions(Mod, LUser, LServer)),
+ Ss = get_sessions(Mod, LUser, LServer),
lists:foreach(fun (#session{sid = {_, Pid}}) ->
ejabberd_c2s:resend_presence(Pid)
end,
diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl
index e6c5c0212..5e35e344c 100644
--- a/src/ejabberd_sql.erl
+++ b/src/ejabberd_sql.erl
@@ -37,12 +37,12 @@
sql_query_t/1,
sql_transaction/2,
sql_bloc/2,
- abort/1,
- restart/1,
- use_new_schema/0,
- sql_query_to_iolist/1,
+ abort/1,
+ restart/1,
+ use_new_schema/0,
+ sql_query_to_iolist/1,
escape/1,
- standard_escape/1,
+ standard_escape/1,
escape_like/1,
escape_like_arg/1,
escape_like_arg_circumflex/1,
@@ -55,7 +55,8 @@
freetds_config/0,
odbcinst_config/0,
init_mssql/1,
- keep_alive/2]).
+ keep_alive/2,
+ to_list/2]).
%% gen_fsm callbacks
-export([init/1, handle_event/3, handle_sync_event/4,
@@ -68,6 +69,7 @@
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
+-include("ejabberd_stacktrace.hrl").
-record(state,
{db_ref = self() :: pid(),
@@ -136,7 +138,7 @@ start_link(Host, StartInterval) ->
-spec sql_query(binary(), sql_query()) -> sql_query_result().
sql_query(Host, Query) ->
- check_error(sql_call(Host, {sql_query, Query}), Query).
+ sql_call(Host, {sql_query, Query}).
%% SQL transaction based on a list of queries
%% This function automatically
@@ -172,10 +174,16 @@ sql_call(Host, Msg) ->
end.
keep_alive(Host, PID) ->
- sync_send_event(PID,
+ case sync_send_event(PID,
{sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
p1_time_compat:monotonic_time(milli_seconds)},
- query_timeout(Host)).
+ query_timeout(Host)) of
+ {selected,_,[[<<"1">>]]} ->
+ ok;
+ _Err ->
+ ?ERROR_MSG("keep alive query failed, closing connection: ~p", [_Err]),
+ sync_send_event(PID, force_timeout, query_timeout(Host))
+ end.
sync_send_event(Pid, Msg, Timeout) ->
try p1_fsm:sync_send_event(Pid, Msg, Timeout)
@@ -252,6 +260,10 @@ to_bool(true) -> true;
to_bool(1) -> true;
to_bool(_) -> false.
+to_list(EscapeFun, Val) ->
+ Escaped = lists:join(<<",">>, lists:map(EscapeFun, Val)),
+ [<<"(">>, Escaped, <<")">>].
+
encode_term(Term) ->
escape(list_to_binary(
erl_prettypr:format(erl_syntax:abstract(Term),
@@ -335,10 +347,10 @@ connecting(connect, #state{host = Host} = State) ->
State2 = get_db_version(State1),
{next_state, session_established, State2};
{error, Reason} ->
- ?INFO_MSG("~p connection failed:~n** Reason: ~p~n** "
- "Retry after: ~p seconds",
- [State#state.db_type, Reason,
- State#state.start_interval div 1000]),
+ ?WARNING_MSG("~p connection failed:~n** Reason: ~p~n** "
+ "Retry after: ~p seconds",
+ [State#state.db_type, Reason,
+ State#state.start_interval div 1000]),
p1_fsm:send_event_after(State#state.start_interval,
connect),
{next_state, connecting, State}
@@ -389,6 +401,8 @@ session_established(Request, {Who, _Ref}, State) ->
session_established({sql_cmd, Command, From, Timestamp},
State) ->
run_sql_cmd(Command, From, State, Timestamp);
+session_established(force_timeout, State) ->
+ {stop, timeout, State};
session_established(Event, State) ->
?WARNING_MSG("unexpected event in 'session_established': ~p",
[Event]),
@@ -503,24 +517,26 @@ outer_transaction(F, NRestarts, _Reason) ->
end,
sql_query_internal([<<"begin;">>]),
put(?NESTING_KEY, PreviousNestingLevel + 1),
- Result = (catch F()),
- put(?NESTING_KEY, PreviousNestingLevel),
- case Result of
- {aborted, Reason} when NRestarts > 0 ->
- sql_query_internal([<<"rollback;">>]),
- outer_transaction(F, NRestarts - 1, Reason);
- {aborted, Reason} when NRestarts =:= 0 ->
- ?ERROR_MSG("SQL transaction restarts exceeded~n** "
- "Restarts: ~p~n** Last abort reason: "
- "~p~n** Stacktrace: ~p~n** When State "
- "== ~p",
- [?MAX_TRANSACTION_RESTARTS, Reason,
- erlang:get_stacktrace(), get(?STATE_KEY)]),
- sql_query_internal([<<"rollback;">>]),
- {aborted, Reason};
- {'EXIT', Reason} ->
- sql_query_internal([<<"rollback;">>]), {aborted, Reason};
- Res -> sql_query_internal([<<"commit;">>]), {atomic, Res}
+ try F() of
+ Res ->
+ sql_query_internal([<<"commit;">>]),
+ {atomic, Res}
+ catch
+ ?EX_RULE(throw, {aborted, Reason}, _) when NRestarts > 0 ->
+ sql_query_internal([<<"rollback;">>]),
+ outer_transaction(F, NRestarts - 1, Reason);
+ ?EX_RULE(throw, {aborted, Reason}, Stack) when NRestarts =:= 0 ->
+ ?ERROR_MSG("SQL transaction restarts exceeded~n** "
+ "Restarts: ~p~n** Last abort reason: "
+ "~p~n** Stacktrace: ~p~n** When State "
+ "== ~p",
+ [?MAX_TRANSACTION_RESTARTS, Reason,
+ ?EX_STACK(Stack), get(?STATE_KEY)]),
+ sql_query_internal([<<"rollback;">>]),
+ {aborted, Reason};
+ ?EX_RULE(exit, Reason, _) ->
+ sql_query_internal([<<"rollback;">>]),
+ {aborted, Reason}
end.
execute_bloc(F) ->
@@ -586,17 +602,12 @@ sql_query_internal(#sql_query{} = Query) ->
{error, <<"killed">>};
exit:{normal, _} ->
{error, <<"terminated unexpectedly">>};
- Class:Reason ->
- ST = erlang:get_stacktrace(),
+ ?EX_RULE(Class, Reason, Stack) ->
?ERROR_MSG("Internal error while processing SQL query: ~p",
- [{Class, Reason, ST}]),
+ [{Class, Reason, ?EX_STACK(Stack)}]),
{error, <<"internal error">>}
end,
- case Res of
- {error, <<"No SQL-driver information available.">>} ->
- {updated, 0};
- _Else -> Res
- end;
+ check_error(Res, Query);
sql_query_internal(F) when is_function(F) ->
case catch execute_fun(F) of
{'EXIT', Reason} -> {error, Reason};
@@ -621,17 +632,12 @@ sql_query_internal(Query) ->
[Query], self(),
[{timeout, QueryTimeout - 1000},
{result_type, binary}])),
- %% ?INFO_MSG("MySQL, Received result~n~p~n", [R]),
R;
sqlite ->
Host = State#state.host,
sqlite_to_odbc(Host, sqlite3:sql_exec(sqlite_db(Host), Query))
end,
- case Res of
- {error, <<"No SQL-driver information available.">>} ->
- {updated, 0};
- _Else -> Res
- end.
+ check_error(Res, Query).
select_sql_query(Queries, State) ->
select_sql_query(
@@ -733,12 +739,11 @@ sql_query_format_res({selected, _, Rows}, SQLQuery) ->
try
[(SQLQuery#sql_query.format_res)(Row)]
catch
- Class:Reason ->
- ST = erlang:get_stacktrace(),
+ ?EX_RULE(Class, Reason, Stack) ->
?ERROR_MSG("Error while processing "
"SQL query result: ~p~n"
"row: ~p",
- [{Class, Reason, ST}, Row]),
+ [{Class, Reason, ?EX_STACK(Stack)}, Row]),
[]
end
end, Rows),
@@ -750,14 +755,23 @@ sql_query_to_iolist(SQLQuery) ->
generic_sql_query_format(SQLQuery).
%% Generate the OTP callback return tuple depending on the driver result.
-abort_on_driver_error({error, <<"query timed out">>} =
- Reply,
+abort_on_driver_error({error,
+ <<"query timed out">>} = Reply,
From) ->
p1_fsm:reply(From, Reply),
{stop, timeout, get(?STATE_KEY)};
abort_on_driver_error({error,
- <<"Failed sending data on socket", _/binary>>} =
- Reply,
+ <<"Failed sending data on socket", _/binary>>} = Reply,
+ From) ->
+ p1_fsm:reply(From, Reply),
+ {stop, closed, get(?STATE_KEY)};
+abort_on_driver_error({error,
+ <<"SQL connection failed">>} = Reply,
+ From) ->
+ p1_fsm:reply(From, Reply),
+ {stop, timeout, get(?STATE_KEY)};
+abort_on_driver_error({error,
+ <<"Communication link failure">>} = Reply,
From) ->
p1_fsm:reply(From, Reply),
{stop, closed, get(?STATE_KEY)};
@@ -773,6 +787,7 @@ odbc_connect(SQLServer, Timeout) ->
ejabberd:start_app(odbc),
odbc:connect(binary_to_list(SQLServer),
[{scrollable_cursors, off},
+ {extended_errors, on},
{tuple_row, off},
{timeout, Timeout},
{binary_strings, on}]).
@@ -1099,20 +1114,45 @@ query_timeout(LServer) ->
timer:seconds(
ejabberd_config:get_option({sql_query_timeout, LServer}, 60)).
+%% ***IMPORTANT*** This error format requires extended_errors turned on.
+extended_error({"08S01", _, Reason}) ->
+ % TCP Provider: The specified network name is no longer available
+ ?DEBUG("ODBC Link Failure: ~s", [Reason]),
+ <<"Communication link failure">>;
+extended_error({"08001", _, Reason}) ->
+ % Login timeout expired
+ ?DEBUG("ODBC Connect Timeout: ~s", [Reason]),
+ <<"SQL connection failed">>;
+extended_error({"IMC01", _, Reason}) ->
+ % The connection is broken and recovery is not possible
+ ?DEBUG("ODBC Link Failure: ~s", [Reason]),
+ <<"Communication link failure">>;
+extended_error({"IMC06", _, Reason}) ->
+ % The connection is broken and recovery is not possible
+ ?DEBUG("ODBC Link Failure: ~s", [Reason]),
+ <<"Communication link failure">>;
+extended_error({Code, _, Reason}) ->
+ ?DEBUG("ODBC Error ~s: ~s", [Code, Reason]),
+ iolist_to_binary(Reason);
+extended_error(Error) ->
+ Error.
+
check_error({error, Why} = Err, _Query) when Why == killed ->
Err;
-check_error({error, Why} = Err, #sql_query{} = Query) ->
+check_error({error, Why}, #sql_query{} = Query) ->
+ Err = extended_error(Why),
?ERROR_MSG("SQL query '~s' at ~p failed: ~p",
- [Query#sql_query.hash, Query#sql_query.loc, Why]),
- Err;
-check_error({error, Why} = Err, Query) ->
+ [Query#sql_query.hash, Query#sql_query.loc, Err]),
+ {error, Err};
+check_error({error, Why}, Query) ->
+ Err = extended_error(Why),
case catch iolist_to_binary(Query) of
SQuery when is_binary(SQuery) ->
- ?ERROR_MSG("SQL query '~s' failed: ~p", [SQuery, Why]);
+ ?ERROR_MSG("SQL query '~s' failed: ~p", [SQuery, Err]);
_ ->
- ?ERROR_MSG("SQL query ~p failed: ~p", [Query, Why])
+ ?ERROR_MSG("SQL query ~p failed: ~p", [Query, Err])
end,
- Err;
+ {error, Err};
check_error(Result, _Query) ->
Result.
@@ -1128,7 +1168,7 @@ opt_type(sql_username) -> fun iolist_to_binary/1;
opt_type(sql_ssl) -> fun(B) when is_boolean(B) -> B end;
opt_type(sql_ssl_verify) -> fun(B) when is_boolean(B) -> B end;
opt_type(sql_ssl_certfile) -> fun ejabberd_pkix:try_certfile/1;
-opt_type(sql_ssl_cafile) -> fun misc:try_read_file/1;
+opt_type(sql_ssl_cafile) -> fun ejabberd_pkix:try_certfile/1;
opt_type(sql_query_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(sql_connect_timeout) ->
diff --git a/src/ejabberd_sql_pt.erl b/src/ejabberd_sql_pt.erl
index eb7905bf0..1f6134d07 100644
--- a/src/ejabberd_sql_pt.erl
+++ b/src/ejabberd_sql_pt.erl
@@ -306,6 +306,20 @@ parse1([$%, $( | S], Acc, State) ->
false ->
append_string("0=0", State3)
end;
+ {list, InternalType} ->
+ Convert = erl_syntax:application(
+ erl_syntax:atom(ejabberd_sql),
+ erl_syntax:atom(to_list),
+ [erl_syntax:record_access(
+ erl_syntax:variable(?ESCAPE_VAR),
+ erl_syntax:atom(?ESCAPE_RECORD),
+ erl_syntax:atom(InternalType)),
+ erl_syntax:variable(Name)]),
+ State2#state{'query' = [{var, Var} | State2#state.'query'],
+ args = [Convert | State2#state.args],
+ params = [Var | State2#state.params],
+ param_pos = State2#state.param_pos + 1,
+ used_vars = [Name | State2#state.used_vars]};
_ ->
Convert =
erl_syntax:application(
@@ -335,6 +349,19 @@ parse_name(S, IsArg, State) ->
parse_name([], _Acc, _Depth, _IsArg, State) ->
throw({error, State#state.loc,
"expected ')', found end of string"});
+parse_name([$), $l, T | S], Acc, 0, true, State) ->
+ Type = case T of
+ $d -> {list, integer};
+ $s -> {list, string};
+ $b -> {list, boolean};
+ _ ->
+ throw({error, State#state.loc,
+ ["unknown type specifier 'l", T, "'"]})
+ end,
+ {lists:reverse(Acc), Type, S, State};
+parse_name([$), $l, T | _], _Acc, 0, false, State) ->
+ throw({error, State#state.loc,
+ ["list type 'l", T, "' is not allowed for outputs"]});
parse_name([$), T | S], Acc, 0, IsArg, State) ->
Type =
case T of
diff --git a/src/ejabberd_sql_sup.erl b/src/ejabberd_sql_sup.erl
index b6315c175..ee889bd21 100644
--- a/src/ejabberd_sql_sup.erl
+++ b/src/ejabberd_sql_sup.erl
@@ -31,21 +31,19 @@
-export([start_link/1, init/1, add_pid/2, remove_pid/2,
get_pids/1, get_random_pid/1, transform_options/1,
- opt_type/1]).
+ reload/1, opt_type/1]).
-include("logger.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
-define(PGSQL_PORT, 5432).
-
-define(MYSQL_PORT, 3306).
-
-define(DEFAULT_POOL_SIZE, 10).
-
-define(DEFAULT_SQL_START_INTERVAL, 30).
-
-define(CONNECT_TIMEOUT, 500).
--record(sql_pool, {host, pid}).
+-record(sql_pool, {host :: binary(),
+ pid :: pid()}).
start_link(Host) ->
ejabberd_mnesia:create(?MODULE, sql_pool,
@@ -59,9 +57,6 @@ start_link(Host) ->
?MODULE, [Host]).
init([Host]) ->
- StartInterval = ejabberd_config:get_option(
- {sql_start_interval, Host},
- ?DEFAULT_SQL_START_INTERVAL),
Type = ejabberd_config:get_option({sql_type, Host}, odbc),
PoolSize = get_pool_size(Type, Host),
case Type of
@@ -72,16 +67,37 @@ init([Host]) ->
_ ->
ok
end,
+ {ok, {{one_for_one, PoolSize * 10, 1},
+ [child_spec(I, Host) || I <- lists:seq(1, PoolSize)]}}.
- {ok,
- {{one_for_one, PoolSize * 10, 1},
- lists:map(fun (I) ->
- {I,
- {ejabberd_sql, start_link,
- [Host, StartInterval * 1000]},
- transient, 2000, worker, [?MODULE]}
- end,
- lists:seq(1, PoolSize))}}.
+reload(Host) ->
+ Type = ejabberd_config:get_option({sql_type, Host}, odbc),
+ NewPoolSize = get_pool_size(Type, Host),
+ OldPoolSize = ets:select_count(
+ sql_pool,
+ ets:fun2ms(
+ fun(#sql_pool{host = H}) when H == Host ->
+ true
+ end)),
+ reload(Host, NewPoolSize, OldPoolSize).
+
+reload(Host, NewPoolSize, OldPoolSize) ->
+ Sup = gen_mod:get_module_proc(Host, ?MODULE),
+ if NewPoolSize == OldPoolSize ->
+ ok;
+ NewPoolSize > OldPoolSize ->
+ lists:foreach(
+ fun(I) ->
+ Spec = child_spec(I, Host),
+ supervisor:start_child(Sup, Spec)
+ end, lists:seq(OldPoolSize+1, NewPoolSize));
+ OldPoolSize > NewPoolSize ->
+ lists:foreach(
+ fun(I) ->
+ supervisor:terminate_child(Sup, I),
+ supervisor:delete_child(Sup, I)
+ end, lists:seq(NewPoolSize+1, OldPoolSize))
+ end.
get_pids(Host) ->
Rs = mnesia:dirty_read(sql_pool, Host),
@@ -123,6 +139,13 @@ get_pool_size(SQLType, Host) ->
end,
PoolSize.
+child_spec(I, Host) ->
+ StartInterval = ejabberd_config:get_option(
+ {sql_start_interval, Host},
+ ?DEFAULT_SQL_START_INTERVAL),
+ {I, {ejabberd_sql, start_link, [Host, timer:seconds(StartInterval)]},
+ transient, 2000, worker, [?MODULE]}.
+
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
@@ -167,7 +190,7 @@ check_sqlite_db(Host) ->
ok
end;
{error, Reason} ->
- ?INFO_MSG("Failed open sqlite database, reason ~p", [Reason])
+ ?WARNING_MSG("Failed open sqlite database, reason ~p", [Reason])
end.
create_sqlite_tables(DB) ->
@@ -180,8 +203,8 @@ create_sqlite_tables(DB) ->
[ok = sqlite3:sql_exec(DB, Q) || Q <- Qs],
ok = sqlite3:sql_exec(DB, "commit");
{error, Reason} ->
- ?INFO_MSG("Failed to read SQLite schema file: ~s",
- [file:format_error(Reason)])
+ ?WARNING_MSG("Failed to read SQLite schema file: ~s",
+ [file:format_error(Reason)])
end.
read_lines(Fd, File, Acc) ->
diff --git a/src/ejabberd_stun.erl b/src/ejabberd_stun.erl
index 53ecd5cc1..1e00c85f5 100644
--- a/src/ejabberd_stun.erl
+++ b/src/ejabberd_stun.erl
@@ -24,27 +24,29 @@
%%%-------------------------------------------------------------------
-module(ejabberd_stun).
-
+-behaviour(ejabberd_listener).
-protocol({rfc, 5766}).
-protocol({xep, 176, '1.0'}).
-ifndef(STUN).
-include("logger.hrl").
--export([socket_type/0, start/2, listen_opt_type/1]).
-log_error() ->
- ?CRITICAL_MSG("ejabberd is not compiled with STUN/TURN support", []).
-socket_type() ->
- log_error(),
- raw.
-listen_opt_type(_) ->
- log_error(),
- [].
+-export([accept/1, start/2, start_link/2, listen_options/0]).
+fail() ->
+ ?CRITICAL_MSG("Listening module ~s is not available: "
+ "ejabberd is not compiled with STUN/TURN support",
+ [?MODULE]),
+ erlang:error(stun_not_compiled).
+accept(_) ->
+ fail().
+listen_options() ->
+ fail().
start(_, _) ->
- log_error(),
- {error, sip_not_compiled}.
+ fail().
+start_link(_, _) ->
+ fail().
-else.
-export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
- socket_type/0, listen_opt_type/1]).
+ start_link/2, accept/1, listen_opt_type/1, listen_options/0]).
-include("logger.hrl").
@@ -65,8 +67,11 @@ udp_recv(Socket, Addr, Port, Packet, Opts) ->
start(Opaque, Opts) ->
stun:start(Opaque, Opts).
-socket_type() ->
- raw.
+start_link({gen_tcp, Sock}, Opts) ->
+ stun:start_link(Sock, Opts).
+
+accept(_Pid) ->
+ ok.
%%%===================================================================
%%% Internal functions
@@ -138,27 +143,16 @@ listen_opt_type(turn_ip) ->
{ok, Addr} = inet_parse:ipv4_address(binary_to_list(S)),
Addr
end;
-listen_opt_type(shaper) ->
- fun acl:shaper_rules_validator/1;
listen_opt_type(auth_type) ->
fun(anonymous) -> anonymous;
(user) -> user
end;
listen_opt_type(auth_realm) ->
fun iolist_to_binary/1;
-listen_opt_type(tls) ->
- fun(B) when is_boolean(B) -> B end;
-listen_opt_type(certfile) ->
- fun(S) ->
- %% We cannot deprecate the option for now:
- %% I think STUN/TURN clients are too stupid to set SNI
- ejabberd_pkix:add_certfile(S),
- iolist_to_binary(S)
- end;
listen_opt_type(turn_min_port) ->
- fun(P) when is_integer(P), P > 0, P =< 65535 -> P end;
+ fun(P) when is_integer(P), P > 1024, P < 65536 -> P end;
listen_opt_type(turn_max_port) ->
- fun(P) when is_integer(P), P > 0, P =< 65535 -> P end;
+ fun(P) when is_integer(P), P > 1024, P < 65536 -> P end;
listen_opt_type(turn_max_allocations) ->
fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity;
@@ -170,13 +164,19 @@ listen_opt_type(turn_max_permissions) ->
(infinity) -> infinity
end;
listen_opt_type(server_name) ->
- fun iolist_to_binary/1;
-listen_opt_type(backlog) ->
- fun(I) when is_integer(I), I>0 -> I end;
-listen_opt_type(accept_interval) ->
- fun(I) when is_integer(I), I>=0 -> I end;
-listen_opt_type(_) ->
- [shaper, auth_type, auth_realm, tls, certfile, turn_min_port,
- turn_max_port, turn_max_allocations, turn_max_permissions,
- server_name, backlog, accept_interval].
+ fun iolist_to_binary/1.
+
+listen_options() ->
+ [{shaper, none},
+ {use_turn, false},
+ {turn_ip, undefined},
+ {auth_type, user},
+ {auth_realm, undefined},
+ {tls, false},
+ {certfile, undefined},
+ {turn_min_port, 49152},
+ {turn_max_port, 65535},
+ {turn_max_allocations, 10},
+ {turn_max_permissions, 10},
+ {server_name, <<"ejabberd">>}].
-endif.
diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl
index b509cdfc0..86a14c78a 100644
--- a/src/ejabberd_sup.erl
+++ b/src/ejabberd_sup.erl
@@ -44,9 +44,9 @@ init([]) ->
worker(ejabberd_ctl),
worker(ejabberd_commands),
worker(ejabberd_admin),
+ supervisor(ejabberd_listener),
worker(ejabberd_pkix),
worker(ejabberd_acme),
- supervisor(ejabberd_listener),
worker(ejabberd_s2s),
simple_supervisor(ejabberd_s2s_in),
simple_supervisor(ejabberd_s2s_out),
diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl
index c7bded027..03b11c9f6 100644
--- a/src/ejabberd_web_admin.erl
+++ b/src/ejabberd_web_admin.erl
@@ -166,7 +166,7 @@ process([<<"doc">>, LocalFile], _Request) ->
"documentation with the environment variable "
"EJABBERD_DOC_PATH. Check the ejabberd "
"Guide for more information.">>,
- ?INFO_MSG("Problem '~p' accessing the local Guide file ~s", [Error, Help]),
+ ?WARNING_MSG("Problem '~p' accessing the local Guide file ~s", [Error, Help]),
case Error of
eacces -> {403, [], <<"Forbidden", Help/binary>>};
enoent -> {307, [{<<"Location">>, <<"http://docs.ejabberd.im/admin/guide/configuration/">>}], <<"Not found", Help/binary>>};
@@ -1873,7 +1873,7 @@ get_node(Host, Node, [<<"modules">>], Query, Lang)
Modules, Query)
of
submitted -> ok;
- {'EXIT', Reason} -> ?INFO_MSG("~p~n", [Reason]), error;
+ {'EXIT', Reason} -> ?ERROR_MSG("~p~n", [Reason]), error;
_ -> nothing
end,
NewModules = lists:sort(ejabberd_cluster:call(Node, gen_mod,
diff --git a/src/ejabberd_xmlrpc.erl b/src/ejabberd_xmlrpc.erl
index 68a774d4b..11da7f369 100644
--- a/src/ejabberd_xmlrpc.erl
+++ b/src/ejabberd_xmlrpc.erl
@@ -31,11 +31,12 @@
%%% TODO: commands strings should be strings without ~n
-module(ejabberd_xmlrpc).
+-behaviour(ejabberd_listener).
-author('badlop@process-one.net').
--export([start/2, handler/2, process/2, socket_type/0,
- transform_listen_option/2, listen_opt_type/1]).
+-export([start/2, start_link/2, handler/2, process/2, accept/1,
+ transform_listen_option/2, listen_opt_type/1, listen_options/0]).
-include("logger.hrl").
-include("ejabberd_http.hrl").
@@ -190,7 +191,11 @@
start({gen_tcp = _SockMod, Socket}, Opts) ->
ejabberd_http:start({gen_tcp, Socket}, [{xmlrpc, true}|Opts]).
-socket_type() -> raw.
+start_link({gen_tcp = _SockMod, Socket}, Opts) ->
+ ejabberd_http:start_link({gen_tcp, Socket}, [{xmlrpc, true}|Opts]).
+
+accept(Pid) ->
+ ejabberd_http:accept(Pid).
%% -----------------------------
%% HTTP interface
@@ -575,17 +580,7 @@ listen_opt_type(access_commands) ->
{<<"ejabberd_xmlrpc compatibility shim">>,
{[?MODULE], [{access, Ac}], Commands}}
end, lists:flatten(Opts))
- end;
-listen_opt_type(maxsessions) ->
- fun(I) when is_integer(I), I>0 -> I end;
-listen_opt_type(timeout) ->
- fun(I) when is_integer(I), I>0 -> I end;
-listen_opt_type(inet) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(inet6) -> fun(B) when is_boolean(B) -> B end;
-listen_opt_type(backlog) ->
- fun(I) when is_integer(I), I>0 -> I end;
-listen_opt_type(accept_interval) ->
- fun(I) when is_integer(I), I>=0 -> I end;
-listen_opt_type(_) ->
- [access_commands, maxsessions, timeout, backlog, inet, inet6,
- accept_interval].
+ end.
+
+listen_options() ->
+ [{access_commands, []}].
diff --git a/src/eldap_pool.erl b/src/eldap_pool.erl
index 647ff02e2..3e8e35b84 100644
--- a/src/eldap_pool.erl
+++ b/src/eldap_pool.erl
@@ -57,7 +57,7 @@ start_link(Name, Hosts, Backups, Port, Rootdn, Passwd,
of
{ok, Pid} -> pg2:join(PoolName, Pid);
Err ->
- ?INFO_MSG("Err = ~p", [Err]),
+ ?ERROR_MSG("Err = ~p", [Err]),
error
end
end,
diff --git a/src/gen_iq_handler.erl b/src/gen_iq_handler.erl
index f6a6744fd..9e7b56406 100644
--- a/src/gen_iq_handler.erl
+++ b/src/gen_iq_handler.erl
@@ -40,6 +40,7 @@
-include("logger.hrl").
-include("xmpp.hrl").
-include("translate.hrl").
+-include("ejabberd_stacktrace.hrl").
-type component() :: ejabberd_sm | ejabberd_local.
@@ -113,10 +114,9 @@ process_iq(_Host, Module, Function, IQ) ->
ejabberd_router:route(ResIQ);
ignore ->
ok
- catch E:R ->
- St = erlang:get_stacktrace(),
+ catch ?EX_RULE(E, R, St) ->
?ERROR_MSG("failed to process iq:~n~s~nReason = ~p",
- [xmpp:pp(IQ), {E, {R, St}}]),
+ [xmpp:pp(IQ), {E, {R, ?EX_STACK(St)}}]),
Txt = <<"Module failed to handle the query">>,
Err = xmpp:err_internal_server_error(Txt, IQ#iq.lang),
ejabberd_router:route_error(IQ, Err)
diff --git a/src/gen_mod.erl b/src/gen_mod.erl
index c15b50443..d225beeff 100644
--- a/src/gen_mod.erl
+++ b/src/gen_mod.erl
@@ -58,6 +58,7 @@
-include("logger.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
+-include("ejabberd_stacktrace.hrl").
-record(ejabberd_module,
{module_host = {undefined, <<"">>} :: {atom(), binary()},
@@ -67,7 +68,7 @@
-type opts() :: [{atom(), any()}].
-type db_type() :: atom().
--callback start(binary(), opts()) -> ok | {ok, pid()}.
+-callback start(binary(), opts()) -> ok | {ok, pid()} | {error, term()}.
-callback stop(binary()) -> any().
-callback reload(binary(), opts(), opts()) -> ok | {ok, pid()}.
-callback mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
@@ -217,8 +218,8 @@ start_module(Host, Module, Opts0, Order, NeedValidation) ->
{ok, Pid} when is_pid(Pid) -> {ok, Pid};
Err -> erlang:error({bad_return, Module, Err})
end
- catch Class:Reason ->
- StackTrace = erlang:get_stacktrace(),
+ catch ?EX_RULE(Class, Reason, Stack) ->
+ StackTrace = ?EX_STACK(Stack),
ets:delete(ejabberd_modules, {Module, Host}),
ErrorText = format_module_error(
Module, start, 2,
@@ -282,8 +283,8 @@ reload_module(Host, Module, NewOpts, OldOpts, Order) ->
{ok, Pid} when is_pid(Pid) -> {ok, Pid};
Err -> erlang:error({bad_return, Module, Err})
end
- catch Class:Reason ->
- StackTrace = erlang:get_stacktrace(),
+ catch ?EX_RULE(Class, Reason, Stack) ->
+ StackTrace = ?EX_STACK(Stack),
ErrorText = format_module_error(
Module, reload, 3,
NewOpts, Class, Reason,
@@ -548,12 +549,12 @@ validate_opts(Host, Module, Opts0) ->
_:{invalid_option, Opt, Val} ->
ErrTxt = io_lib:format("Invalid value for option '~s' of "
"module ~s: ~s",
- [Opt, Module, misc:format_val(Val)]),
+ [Opt, Module, misc:format_val({yaml, Val})]),
module_error(ErrTxt);
_:{invalid_option, Opt, Val, Reason} ->
ErrTxt = io_lib:format("Invalid value for option '~s' of "
"module ~s (~s): ~s",
- [Opt, Module, Reason, misc:format_val(Val)]),
+ [Opt, Module, Reason, misc:format_val({yaml, Val})]),
module_error(ErrTxt);
_:{unknown_option, Opt, []} ->
ErrTxt = io_lib:format("Unknown option '~s' of module '~s': "
@@ -620,7 +621,7 @@ validate_opt(Opt, Val, VFun) ->
NewVal -> [{Opt, NewVal}]
catch {invalid_syntax, Error} ->
err_invalid_option(Opt, Val, Error);
- _:_ ->
+ _:R when R /= undef ->
err_invalid_option(Opt, Val)
end.
@@ -730,6 +731,9 @@ format_module_error(Module, Fun, Arity, Opts, Class, Reason, St) ->
"it doesn't export ~s/~B callback: "
"is it really an ejabberd module?",
[Fun, Module, Fun, Arity]);
+ {error, {bad_return, Module, {error, _} = Err}} ->
+ io_lib:format("Failed to ~s module ~s: ~s",
+ [Fun, Module, misc:format_val(Err)]);
{error, {bad_return, Module, Ret}} ->
io_lib:format("Module ~s returned unexpected value from ~s/~B:~n"
"** Error: ~p~n"
diff --git a/src/jd2ejd.erl b/src/jd2ejd.erl
index 35ebeab8d..394a5a471 100644
--- a/src/jd2ejd.erl
+++ b/src/jd2ejd.erl
@@ -111,7 +111,6 @@ process_xdb(User, Server,
xdb_data(_User, _Server, {xmlcdata, _CData}) -> ok;
xdb_data(User, Server, #xmlel{attrs = Attrs} = El) ->
From = jid:make(User, Server),
- LUser = From#jid.luser,
LServer = From#jid.lserver,
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_AUTH ->
@@ -142,7 +141,7 @@ xdb_data(User, Server, #xmlel{attrs = Attrs} = El) ->
(_) -> true
end, Attrs),
catch mod_private:set_data(
- LUser, LServer,
+ From,
[{XMLNS, El#xmlel{attrs = NewAttrs}}]);
_ ->
?DEBUG("jd2ejd: Unknown namespace \"~s\"~n", [XMLNS])
diff --git a/src/misc.erl b/src/misc.erl
index 5ef71c41b..5aa281565 100644
--- a/src/misc.erl
+++ b/src/misc.erl
@@ -28,7 +28,9 @@
-module(misc).
%% API
--export([tolower/1, term_to_base64/1, base64_to_term/1, ip_to_list/1,
+-export([add_delay_info/3, add_delay_info/4,
+ unwrap_carbon/1, unwrap_mucsub_message/1, is_standalone_chat_state/1,
+ tolower/1, term_to_base64/1, base64_to_term/1, ip_to_list/1,
hex_to_bin/1, hex_to_base64/1, url_encode/1, expand_keyword/3,
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
l2i/1, i2l/1, i2l/2, expr_to_term/1, term_to_expr/1,
@@ -44,11 +46,81 @@
{encode_base64, 1}]).
-include("logger.hrl").
+-include("xmpp.hrl").
-include_lib("kernel/include/file.hrl").
%%%===================================================================
%%% API
%%%===================================================================
+-spec add_delay_info(stanza(), jid(), erlang:timestamp()) -> stanza().
+add_delay_info(Stz, From, Time) ->
+ add_delay_info(Stz, From, Time, <<"">>).
+
+-spec add_delay_info(stanza(), jid(), erlang:timestamp(), binary()) -> stanza().
+add_delay_info(Stz, From, Time, Desc) ->
+ NewDelay = #delay{stamp = Time, from = From, desc = Desc},
+ case xmpp:get_subtag(Stz, #delay{stamp = {0,0,0}}) of
+ #delay{from = OldFrom} when is_record(OldFrom, jid) ->
+ case jid:tolower(From) == jid:tolower(OldFrom) of
+ true ->
+ Stz;
+ false ->
+ xmpp:append_subtags(Stz, [NewDelay])
+ end;
+ _ ->
+ xmpp:append_subtags(Stz, [NewDelay])
+ end.
+
+-spec unwrap_carbon(stanza()) -> xmpp_element().
+unwrap_carbon(#message{} = Msg) ->
+ try
+ case xmpp:get_subtag(Msg, #carbons_sent{forwarded = #forwarded{}}) of
+ #carbons_sent{forwarded = #forwarded{sub_els = [El]}} ->
+ xmpp:decode(El, ?NS_CLIENT, [ignore_els]);
+ _ ->
+ case xmpp:get_subtag(Msg, #carbons_received{
+ forwarded = #forwarded{}}) of
+ #carbons_received{forwarded = #forwarded{sub_els = [El]}} ->
+ xmpp:decode(El, ?NS_CLIENT, [ignore_els]);
+ _ ->
+ Msg
+ end
+ end
+ catch _:{xmpp_codec, _} ->
+ Msg
+ end;
+unwrap_carbon(Stanza) -> Stanza.
+
+-spec unwrap_mucsub_message(xmpp_element()) -> message() | false.
+unwrap_mucsub_message(#message{} = OuterMsg) ->
+ case xmpp:get_subtag(OuterMsg, #ps_event{}) of
+ #ps_event{
+ items = #ps_items{
+ node = Node,
+ items = [
+ #ps_item{
+ sub_els = [#message{} = InnerMsg]} | _]}}
+ when Node == ?NS_MUCSUB_NODES_MESSAGES;
+ Node == ?NS_MUCSUB_NODES_SUBJECT ->
+ InnerMsg;
+ _ ->
+ false
+ end;
+unwrap_mucsub_message(_Packet) ->
+ false.
+
+-spec is_standalone_chat_state(stanza()) -> boolean().
+is_standalone_chat_state(Stanza) ->
+ case unwrap_carbon(Stanza) of
+ #message{body = [], subject = [], sub_els = Els} ->
+ IgnoreNS = [?NS_CHATSTATES, ?NS_DELAY, ?NS_EVENT],
+ Stripped = [El || El <- Els,
+ not lists:member(xmpp:get_ns(El), IgnoreNS)],
+ Stripped == [];
+ _ ->
+ false
+ end.
+
-spec tolower(binary()) -> binary().
tolower(B) ->
iolist_to_binary(tolower_s(binary_to_list(B))).
@@ -204,7 +276,7 @@ compile_exprs(Mod, Exprs) ->
-spec join_atoms([atom()], binary()) -> binary().
join_atoms(Atoms, Sep) ->
- str:join([io_lib:format("~p", [A]) || A <- Atoms], Sep).
+ str:join([io_lib:format("~p", [A]) || A <- lists:sort(Atoms)], Sep).
%% @doc Checks if the file is readable and converts its name to binary.
%% Fails with `badarg` otherwise. The function is intended for usage
@@ -297,15 +369,24 @@ intersection(L1, L2) ->
end, L1).
-spec format_val(any()) -> iodata().
+format_val({yaml, S}) when is_integer(S); is_binary(S); is_atom(S) ->
+ format_val(S);
+format_val({yaml, YAML}) ->
+ S = try fast_yaml:encode(YAML)
+ catch _:_ -> YAML
+ end,
+ format_val(S);
format_val(I) when is_integer(I) ->
integer_to_list(I);
-format_val(S) when is_binary(S) ->
- <<$", S/binary, $">>;
format_val(B) when is_atom(B) ->
erlang:atom_to_binary(B, utf8);
-format_val(YAML) ->
- try [io_lib:nl(), fast_yaml:encode(YAML)]
- catch _:_ -> io_lib:format("~p", [YAML])
+format_val(Term) ->
+ S = try iolist_to_binary(Term)
+ catch _:_ -> list_to_binary(io_lib:format("~p", [Term]))
+ end,
+ case binary:match(S, <<"\n">>) of
+ nomatch -> S;
+ _ -> [io_lib:nl(), S]
end.
-spec cancel_timer(reference()) -> ok.
diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl
index 1b3b27d38..367196a07 100644
--- a/src/mod_admin_extra.erl
+++ b/src/mod_admin_extra.erl
@@ -560,8 +560,10 @@ get_commands_spec() ->
desc = "Push template roster from file to a user",
longdesc = "The text file must contain an erlang term: a list "
"of tuples with username, servername, group and nick. Example:\n"
- "[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n"
- " {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].",
+ "[{<<\"user1\">>, <<\"localhost\">>, <<\"Workers\">>, <<\"User 1\">>},\n"
+ " {<<\"user2\">>, <<\"localhost\">>, <<\"Workers\">>, <<\"User 2\">>}].\n"
+ "When using UTF8 character encoding add /utf8 to certain string. Example:\n"
+ "[{<<\"user2\">>, <<\"localhost\">>, <<\"Workers\"/utf8>>, <<\"User 2\"/utf8>>}].",
module = ?MODULE, function = push_roster,
args = [{file, binary}, {user, binary}, {host, binary}],
args_example = [<<"/home/ejabberd/roster.txt">>, <<"user1">>, <<"localhost">>],
@@ -1152,8 +1154,7 @@ set_vcard_content(User, Server, Data, SomeContent) ->
end,
%% Build new vcard
SubEl = {xmlel, <<"vCard">>, [{<<"xmlns">>,<<"vcard-temp">>}], A4},
- mod_vcard:set_vcard(User, jid:nameprep(Server), SubEl),
- ok.
+ mod_vcard:set_vcard(User, jid:nameprep(Server), SubEl).
take_vcard_tel(TelType, [{xmlel, <<"TEL">>, _, SubEls}=OldEl | OldEls], NewEls, Taken) ->
{Taken2, NewEls2} = case lists:keymember(TelType, 2, SubEls) of
@@ -1389,9 +1390,8 @@ private_set(Username, Host, ElementString) ->
private_set2(Username, Host, Xml) ->
NS = fxml:get_tag_attr_s(<<"xmlns">>, Xml),
- mod_private:set_data(jid:nodeprep(Username), jid:nameprep(Host),
- [{NS, Xml}]),
- ok.
+ JID = jid:make(Username, Host),
+ mod_private:set_data(JID, [{NS, Xml}]).
%%%
%%% Shared Roster Groups
@@ -1450,12 +1450,16 @@ srg_user_del(User, Host, Group, GroupHost) ->
send_message(Type, From, To, Subject, Body) ->
FromJID = jid:decode(From),
ToJID = jid:decode(To),
- Packet = build_packet(Type, Subject, Body),
+ Packet = build_packet(Type, Subject, Body, FromJID, ToJID),
+ State1 = #{jid => FromJID},
+ ejabberd_hooks:run_fold(user_send_packet, FromJID#jid.lserver, {Packet, State1}, []),
ejabberd_router:route(xmpp:set_from_to(Packet, FromJID, ToJID)).
-build_packet(Type, Subject, Body) ->
+build_packet(Type, Subject, Body, FromJID, ToJID) ->
#message{type = misc:binary_to_atom(Type),
body = xmpp:mk_text(Body),
+ from = FromJID,
+ to = ToJID,
id = p1_rand:get_string(),
subject = xmpp:mk_text(Subject)}.
diff --git a/src/mod_avatar.erl b/src/mod_avatar.erl
index cab74bd8b..d5f24e75d 100644
--- a/src/mod_avatar.erl
+++ b/src/mod_avatar.erl
@@ -302,6 +302,10 @@ publish_avatar(#iq{from = JID} = IQ, Meta, MimeType, Data, ItemID) ->
[jid:encode(JID), StanzaErr]),
{stop, StanzaErr}
end;
+ {error, #stanza_error{reason = 'not-acceptable'} = StanzaErr} ->
+ ?WARNING_MSG("Failed to publish avatar data for ~s: ~p",
+ [jid:encode(JID), StanzaErr]),
+ {stop, StanzaErr};
{error, StanzaErr} ->
?ERROR_MSG("Failed to publish avatar data for ~s: ~p",
[jid:encode(JID), StanzaErr]),
diff --git a/src/mod_block_strangers.erl b/src/mod_block_strangers.erl
index 6e9d1097a..0496245b0 100644
--- a/src/mod_block_strangers.erl
+++ b/src/mod_block_strangers.erl
@@ -222,31 +222,11 @@ check_subscription(From, To) ->
end.
sets_bare_member({U, S, <<"">>} = LBJID, Set) ->
- case ?SETS:next(sets_iterator_from(LBJID, Set)) of
+ case ?SETS:next(?SETS:iterator_from(LBJID, Set)) of
{{U, S, _}, _} -> true;
_ -> false
end.
--ifdef(GB_SETS_ITERATOR_FROM).
-sets_iterator_from(Element, Set) ->
- ?SETS:iterator_from(Element, Set).
--else.
-%% Copied from gb_sets.erl
-%% TODO: Remove after dropping R17 support
-sets_iterator_from(S, {_, T}) ->
- iterator_from(S, T, []).
-
-iterator_from(S, {K, _, T}, As) when K < S ->
- iterator_from(S, T, As);
-iterator_from(_, {_, nil, _} = T, As) ->
- [T | As];
-iterator_from(S, {_, L, _} = T, As) ->
- iterator_from(S, L, [T | As]);
-iterator_from(_, nil, As) ->
- As.
--endif.
-
-
depends(_Host, _Opts) ->
[].
diff --git a/src/mod_carboncopy.erl b/src/mod_carboncopy.erl
index 73034ec3f..e1f82e872 100644
--- a/src/mod_carboncopy.erl
+++ b/src/mod_carboncopy.erl
@@ -35,24 +35,18 @@
-export([start/2, stop/1, reload/3]).
-export([user_send_packet/1, user_receive_packet/1,
- iq_handler/1, remove_connection/4, disco_features/5,
- is_carbon_copy/1, mod_opt_type/1, depends/2, clean_cache/1,
+ iq_handler/1, disco_features/5,
+ is_carbon_copy/1, mod_opt_type/1, depends/2,
mod_options/1]).
+-export([c2s_copy_session/2, c2s_session_opened/1, c2s_session_resumed/1]).
+%% For debugging purposes
+-export([list/2]).
-include("logger.hrl").
-include("xmpp.hrl").
--include("mod_carboncopy.hrl").
-type direction() :: sent | received.
-
--callback init(binary(), gen_mod:opts()) -> any().
--callback enable(binary(), binary(), binary(), binary()) -> ok | {error, any()}.
--callback disable(binary(), binary(), binary()) -> ok | {error, any()}.
--callback list(binary(), binary()) -> [{binary(), binary(), node()}].
--callback use_cache(binary()) -> boolean().
--callback cache_nodes(binary()) -> [node()].
-
--optional_callbacks([use_cache/1, cache_nodes/1]).
+-type c2s_state() :: ejabberd_c2s:state().
-spec is_carbon_copy(stanza()) -> boolean().
is_carbon_copy(#message{meta = #{carbon_copy := true}}) ->
@@ -60,16 +54,14 @@ is_carbon_copy(#message{meta = #{carbon_copy := true}}) ->
is_carbon_copy(_) ->
false.
-start(Host, Opts) ->
+start(Host, _Opts) ->
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50),
- Mod = gen_mod:ram_db_mod(Host, ?MODULE),
- init_cache(Mod, Host, Opts),
- Mod:init(Host, Opts),
- clean_cache(),
- ejabberd_hooks:add(unset_presence_hook,Host, ?MODULE, remove_connection, 10),
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
ejabberd_hooks:add(user_send_packet,Host, ?MODULE, user_send_packet, 89),
ejabberd_hooks:add(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
+ ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE, c2s_copy_session, 50),
+ ejabberd_hooks:add(c2s_session_resumed, Host, ?MODULE, c2s_session_resumed, 50),
+ ejabberd_hooks:add(c2s_session_opened, Host, ?MODULE, c2s_session_opened, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2, ?MODULE, iq_handler).
stop(Host) ->
@@ -78,22 +70,12 @@ stop(Host) ->
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
ejabberd_hooks:delete(user_send_packet,Host, ?MODULE, user_send_packet, 89),
ejabberd_hooks:delete(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
- ejabberd_hooks:delete(unset_presence_hook,Host, ?MODULE, remove_connection, 10).
+ ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE, c2s_copy_session, 50),
+ ejabberd_hooks:delete(c2s_session_resumed, Host, ?MODULE, c2s_session_resumed, 50),
+ ejabberd_hooks:delete(c2s_session_opened, Host, ?MODULE, c2s_session_opened, 50).
-reload(Host, NewOpts, OldOpts) ->
- NewMod = gen_mod:ram_db_mod(Host, NewOpts, ?MODULE),
- OldMod = gen_mod:ram_db_mod(Host, OldOpts, ?MODULE),
- if NewMod /= OldMod ->
- NewMod:init(Host, NewOpts);
- true ->
- ok
- end,
- case use_cache(NewMod, Host) of
- true ->
- ets_cache:new(?CARBONCOPY_CACHE, cache_opts(NewOpts));
- false ->
- ok
- end.
+reload(_Host, _NewOpts, _OldOpts) ->
+ ok.
-spec disco_features({error, stanza_error()} | {result, [binary()]} | empty,
jid(), jid(), binary(), binary()) ->
@@ -113,19 +95,13 @@ iq_handler(#iq{type = set, lang = Lang, from = From,
is_record(El, carbons_disable) ->
{U, S, R} = jid:tolower(From),
Result = case El of
- #carbons_enable{} ->
- ?DEBUG("Carbons enabled for user ~s@~s/~s", [U,S,R]),
- enable(S, U, R, ?NS_CARBONS_2);
- #carbons_disable{} ->
- ?DEBUG("Carbons disabled for user ~s@~s/~s", [U,S,R]),
- disable(S, U, R)
+ #carbons_enable{} -> enable(S, U, R, ?NS_CARBONS_2);
+ #carbons_disable{} -> disable(S, U, R)
end,
case Result of
ok ->
- ?DEBUG("carbons IQ result: ok", []),
xmpp:make_iq_result(IQ);
- {error,_Error} ->
- ?ERROR_MSG("Error enabling / disabling carbons: ~p", [Result]),
+ {error, _} ->
Txt = <<"Database failure">>,
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end;
@@ -155,6 +131,29 @@ user_receive_packet({Packet, #{jid := JID} = C2SState}) ->
Pkt -> {Pkt, C2SState}
end.
+-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
+c2s_copy_session(State, #{user := U, server := S, resource := R}) ->
+ case ejabberd_sm:get_user_info(U, S, R) of
+ offline -> State;
+ Info ->
+ case lists:keyfind(carboncopy, 1, Info) of
+ {_, CC} -> State#{carboncopy => CC};
+ false -> State
+ end
+ end.
+
+-spec c2s_session_resumed(c2s_state()) -> c2s_state().
+c2s_session_resumed(#{user := U, server := S, resource := R,
+ carboncopy := CC} = State) ->
+ ejabberd_sm:set_user_info(U, S, R, carboncopy, CC),
+ maps:remove(carboncopy, State);
+c2s_session_resumed(State) ->
+ State.
+
+-spec c2s_session_opened(c2s_state()) -> c2s_state().
+c2s_session_opened(State) ->
+ maps:remove(carboncopy, State).
+
% Modified from original version:
% - 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
@@ -180,12 +179,6 @@ check_and_forward(JID, To, Packet, Direction)->
Packet
end.
--spec remove_connection(binary(), binary(), binary(), binary()) -> ok.
-remove_connection(User, Server, Resource, _Status)->
- disable(Server, User, Resource),
- ok.
-
-
%%% Internal
%% Direction = received | sent <received xmlns='urn:xmpp:carbons:1'/>
-spec send_copies(jid(), jid(), message(), direction()) -> ok.
@@ -248,22 +241,26 @@ build_forward_packet(JID, #message{type = T} = Msg, Sender, Dest, Direction) ->
-spec enable(binary(), binary(), binary(), binary()) -> ok | {error, any()}.
enable(Host, U, R, CC)->
- ?DEBUG("enabling for ~p", [U]),
- Mod = gen_mod:ram_db_mod(Host, ?MODULE),
- case Mod:enable(U, Host, R, CC) of
- ok ->
- delete_cache(Mod, U, Host);
- {error, _} = Err ->
+ ?DEBUG("Enabling carbons for ~s@~s/~s", [U, Host, R]),
+ case ejabberd_sm:set_user_info(U, Host, R, carboncopy, CC) of
+ ok -> ok;
+ {error, Reason} = Err ->
+ ?ERROR_MSG("Failed to disable carbons for ~s@~s/~s: ~p",
+ [U, Host, R, Reason]),
Err
end.
-spec disable(binary(), binary(), binary()) -> ok | {error, any()}.
disable(Host, U, R)->
- ?DEBUG("disabling for ~p", [U]),
- Mod = gen_mod:ram_db_mod(Host, ?MODULE),
- Res = Mod:disable(U, Host, R),
- delete_cache(Mod, U, Host),
- Res.
+ ?DEBUG("Disabling carbons for ~s@~s/~s", [U, Host, R]),
+ case ejabberd_sm:del_user_info(U, Host, R, carboncopy) of
+ ok -> ok;
+ {error, notfound} -> ok;
+ {error, Reason} = Err ->
+ ?ERROR_MSG("Failed to disable carbons for ~s@~s/~s: ~p",
+ [U, Host, R, Reason]),
+ Err
+ end.
-spec complete_packet(jid(), message(), direction()) -> message().
complete_packet(From, #message{from = undefined} = Msg, sent) ->
@@ -291,99 +288,30 @@ is_received_muc_pm(_To, Packet, received) ->
-spec list(binary(), binary()) -> [{Resource :: binary(), Namespace :: binary()}].
list(User, Server) ->
- Mod = gen_mod:ram_db_mod(Server, ?MODULE),
- case use_cache(Mod, Server) of
- true ->
- case ets_cache:lookup(
- ?CARBONCOPY_CACHE, {User, Server},
- fun() ->
- case Mod:list(User, Server) of
- {ok, L} when L /= [] -> {ok, L};
- _ -> error
- end
- end) of
- {ok, L} -> [{Resource, NS} || {Resource, NS, _} <- L];
- error -> []
- end;
- false ->
- case Mod:list(User, Server) of
- {ok, L} -> [{Resource, NS} || {Resource, NS, _} <- L];
- error -> []
- end
- end.
-
--spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
-init_cache(Mod, Host, Opts) ->
- case use_cache(Mod, Host) of
- true ->
- ets_cache:new(?CARBONCOPY_CACHE, cache_opts(Opts));
- false ->
- ets_cache:delete(?CARBONCOPY_CACHE)
- end.
-
--spec cache_opts(gen_mod:opts()) -> [proplists:property()].
-cache_opts(Opts) ->
- MaxSize = gen_mod:get_opt(cache_size, Opts),
- CacheMissed = gen_mod:get_opt(cache_missed, Opts),
- LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of
- infinity -> infinity;
- I -> timer:seconds(I)
- end,
- [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
-
--spec use_cache(module(), binary()) -> boolean().
-use_cache(Mod, Host) ->
- case erlang:function_exported(Mod, use_cache, 1) of
- true -> Mod:use_cache(Host);
- false -> gen_mod:get_module_opt(Host, ?MODULE, use_cache)
- end.
-
--spec cache_nodes(module(), binary()) -> [node()].
-cache_nodes(Mod, Host) ->
- case erlang:function_exported(Mod, cache_nodes, 1) of
- true -> Mod:cache_nodes(Host);
- false -> ejabberd_cluster:get_nodes()
- end.
-
--spec clean_cache(node()) -> non_neg_integer().
-clean_cache(Node) ->
- ets_cache:filter(
- ?CARBONCOPY_CACHE,
- fun(_, error) ->
- false;
- (_, {ok, L}) ->
- not lists:any(fun({_, _, N}) -> N == Node end, L)
- end).
-
--spec clean_cache() -> ok.
-clean_cache() ->
- ejabberd_cluster:eval_everywhere(?MODULE, clean_cache, [node()]).
-
--spec delete_cache(module(), binary(), binary()) -> ok.
-delete_cache(Mod, User, Server) ->
- case use_cache(Mod, Server) of
- true ->
- ets_cache:delete(?CARBONCOPY_CACHE, {User, Server},
- cache_nodes(Mod, Server));
- false ->
- ok
- end.
+ lists:filtermap(
+ fun({Resource, Info}) ->
+ case lists:keyfind(carboncopy, 1, Info) of
+ {_, NS} -> {true, {Resource, NS}};
+ false -> false
+ end
+ end, ejabberd_sm:get_user_info(User, Server)).
depends(_Host, _Opts) ->
[].
-mod_opt_type(ram_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
-mod_opt_type(O) when O == use_cache; O == cache_missed ->
- fun(B) when is_boolean(B) -> B end;
-mod_opt_type(O) when O == cache_size; O == cache_life_time ->
- fun(I) when is_integer(I), I>0 -> I;
- (unlimited) -> infinity;
- (infinity) -> infinity
+mod_opt_type(O) when O == cache_size; O == cache_life_time;
+ O == use_cache; O == cache_missed;
+ O == ram_db_type ->
+ fun(deprecated) -> deprecated;
+ (_) ->
+ ?WARNING_MSG("Option ~s of ~s has no effect anymore "
+ "and will be ingored", [O, ?MODULE]),
+ deprecated
end.
-mod_options(Host) ->
- [{ram_db_type, ejabberd_config:default_ram_db(Host, ?MODULE)},
- {use_cache, ejabberd_config:use_cache(Host)},
- {cache_size, ejabberd_config:cache_size(Host)},
- {cache_missed, ejabberd_config:cache_missed(Host)},
- {cache_life_time, ejabberd_config:cache_life_time(Host)}].
+mod_options(_) ->
+ [{ram_db_type, deprecated},
+ {use_cache, deprecated},
+ {cache_size, deprecated},
+ {cache_missed, deprecated},
+ {cache_life_time, deprecated}].
diff --git a/src/mod_carboncopy_mnesia.erl b/src/mod_carboncopy_mnesia.erl
deleted file mode 100644
index 03bde9897..000000000
--- a/src/mod_carboncopy_mnesia.erl
+++ /dev/null
@@ -1,79 +0,0 @@
-%%%-------------------------------------------------------------------
-%%% File : mod_carboncopy_mnesia.erl
-%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%%
-%%%
-%%% ejabberd, Copyright (C) 2002-2018 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_carboncopy_mnesia).
-
--behaviour(mod_carboncopy).
-
-%% API
--export([init/2, enable/4, disable/3, list/2, use_cache/1]).
-
--include("mod_carboncopy.hrl").
-
-%%%===================================================================
-%%% API
-%%%===================================================================
-init(_Host, _Opts) ->
- Fields = record_info(fields, carboncopy),
- try mnesia:table_info(carboncopy, attributes) of
- Fields ->
- ok;
- _ ->
- %% recreate..
- mnesia:delete_table(carboncopy)
- catch _:_Error ->
- %% probably table don't exist
- ok
- end,
- ejabberd_mnesia:create(?MODULE, carboncopy,
- [{ram_copies, [node()]},
- {attributes, record_info(fields, carboncopy)},
- {type, bag}]).
-
-enable(LUser, LServer, LResource, NS) ->
- mnesia:dirty_write(
- #carboncopy{us = {LUser, LServer},
- resource = LResource,
- version = NS}).
-
-disable(LUser, LServer, LResource) ->
- ToDelete = mnesia:dirty_match_object(
- #carboncopy{us = {LUser, LServer},
- resource = LResource,
- _ = '_'}),
- lists:foreach(fun mnesia:dirty_delete_object/1, ToDelete).
-
-list(LUser, LServer) ->
- {ok, mnesia:dirty_select(
- carboncopy,
- [{#carboncopy{us = {LUser, LServer}, resource = '$2',
- version = '$3', node = '$4'},
- [], [{{'$2','$3','$4'}}]}])}.
-
-use_cache(_LServer) ->
- false.
-
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
diff --git a/src/mod_carboncopy_redis.erl b/src/mod_carboncopy_redis.erl
deleted file mode 100644
index 90b55168e..000000000
--- a/src/mod_carboncopy_redis.erl
+++ /dev/null
@@ -1,176 +0,0 @@
-%%%-------------------------------------------------------------------
-%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% Created : 30 Mar 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%%
-%%%
-%%% ejabberd, Copyright (C) 2002-2018 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_carboncopy_redis).
--behaviour(mod_carboncopy).
--behaviour(gen_server).
-
-%% API
--export([init/2, enable/4, disable/3, list/2, cache_nodes/1]).
-%% gen_server callbacks
--export([init/1, handle_cast/2, handle_call/3, handle_info/2,
- terminate/2, code_change/3, start_link/0]).
-
--include("logger.hrl").
--include("mod_carboncopy.hrl").
-
--define(CARBONCOPY_KEY, <<"ejabberd:carboncopy">>).
-
--record(state, {}).
-
-%%%===================================================================
-%%% API
-%%%===================================================================
-init(_Host, _Opts) ->
- Spec = {?MODULE, {?MODULE, start_link, []},
- transient, 5000, worker, [?MODULE]},
- case supervisor:start_child(ejabberd_backend_sup, Spec) of
- {ok, _Pid} -> ok;
- Err -> Err
- end.
-
--spec start_link() -> {ok, pid()} | {error, any()}.
-start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-
-cache_nodes(_LServer) ->
- [node()].
-
-enable(LUser, LServer, LResource, NS) ->
- USKey = us_key(LUser, LServer),
- NodeKey = node_key(),
- JID = jid:encode({LUser, LServer, LResource}),
- Data = term_to_binary({NS, node()}),
- case ejabberd_redis:multi(
- fun() ->
- ejabberd_redis:hset(USKey, LResource, Data),
- ejabberd_redis:sadd(NodeKey, [JID]),
- ejabberd_redis:publish(
- ?CARBONCOPY_KEY,
- term_to_binary({delete, {LUser, LServer}}))
- end) of
- {ok, _} ->
- ok;
- {error, _} ->
- {error, db_failure}
- end.
-
-disable(LUser, LServer, LResource) ->
- USKey = us_key(LUser, LServer),
- NodeKey = node_key(),
- JID = jid:encode({LUser, LServer, LResource}),
- case ejabberd_redis:multi(
- fun() ->
- ejabberd_redis:hdel(USKey, [LResource]),
- ejabberd_redis:srem(NodeKey, [JID]),
- ejabberd_redis:publish(
- ?CARBONCOPY_KEY,
- term_to_binary({delete, {LUser, LServer}}))
- end) of
- {ok, _} ->
- ok;
- {error, _} ->
- {error, db_failure}
- end.
-
-list(LUser, LServer) ->
- USKey = us_key(LUser, LServer),
- case ejabberd_redis:hgetall(USKey) of
- {ok, Pairs} ->
- {ok, lists:flatmap(
- fun({Resource, Data}) ->
- try
- {NS, Node} = binary_to_term(Data),
- [{Resource, NS, Node}]
- catch _:_ ->
- ?ERROR_MSG("invalid term stored in Redis "
- "(key = ~s): ~p",
- [USKey, Data]),
- []
- end
- end, Pairs)};
- {error, _} ->
- {error, db_failure}
- end.
-
-%%%===================================================================
-%%% gen_server callbacks
-%%%===================================================================
-init([]) ->
- ejabberd_redis:subscribe([?CARBONCOPY_KEY]),
- clean_table(),
- {ok, #state{}}.
-
-handle_call(_Request, _From, State) ->
- Reply = ok,
- {reply, Reply, State}.
-
-handle_cast(_Msg, State) ->
- {noreply, State}.
-
-handle_info({redis_message, ?CARBONCOPY_KEY, Data}, State) ->
- case binary_to_term(Data) of
- {delete, Key} ->
- ets_cache:delete(?CARBONCOPY_CACHE, Key);
- Msg ->
- ?WARNING_MSG("unexpected redis message: ~p", [Msg])
- end,
- {noreply, State};
-handle_info(Info, State) ->
- ?ERROR_MSG("unexpected info: ~p", [Info]),
- {noreply, State}.
-
-terminate(_Reason, _State) ->
- ok.
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
-clean_table() ->
- ?DEBUG("Cleaning Redis 'carboncopy' table...", []),
- NodeKey = node_key(),
- case ejabberd_redis:smembers(NodeKey) of
- {ok, JIDs} ->
- ejabberd_redis:multi(
- fun() ->
- lists:foreach(
- fun(JID) ->
- {U, S, R} = jid:split(jid:decode(JID)),
- USKey = us_key(U, S),
- ejabberd_redis:hdel(USKey, [R])
- end, JIDs)
- end);
- {error, _} ->
- ok
- end,
- ejabberd_redis:del([NodeKey]),
- ok.
-
-us_key(LUser, LServer) ->
- <<"ejabberd:carboncopy:users:", LUser/binary, $@, LServer/binary>>.
-
-node_key() ->
- Node = erlang:atom_to_binary(node(), latin1),
- <<"ejabberd:carboncopy:nodes:", Node/binary>>.
diff --git a/src/mod_carboncopy_riak.erl b/src/mod_carboncopy_riak.erl
deleted file mode 100644
index d6df3008e..000000000
--- a/src/mod_carboncopy_riak.erl
+++ /dev/null
@@ -1,82 +0,0 @@
-%%%-------------------------------------------------------------------
-%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% Created : 15 Apr 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%%
-%%%
-%%% ejabberd, Copyright (C) 2002-2018 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_carboncopy_riak).
--behaviour(mod_carboncopy).
-
-%% API
--export([init/2, enable/4, disable/3, list/2]).
-
--include("logger.hrl").
--include("mod_carboncopy.hrl").
-
-%%%===================================================================
-%%% API
-%%%===================================================================
-init(_Host, _Opts) ->
- clean_table().
-
-enable(LUser, LServer, LResource, NS) ->
- ejabberd_riak:put(#carboncopy{us = {LUser, LServer},
- resource = LResource,
- version = NS},
- carboncopy_schema(),
- [{i, {LUser, LServer, LResource}},
- {'2i', [{<<"us">>, {LUser, LServer}}]}]).
-
-disable(LUser, LServer, LResource) ->
- ejabberd_riak:delete(carboncopy, {LUser, LServer, LResource}).
-
-list(LUser, LServer) ->
- case ejabberd_riak:get_by_index(
- carboncopy, carboncopy_schema(),
- <<"us">>, {LUser, LServer}) of
- {ok, Rs} ->
- {ok, [{Resource, NS, Node}
- || #carboncopy{resource = Resource,
- version = NS,
- node = Node} <- Rs]};
- {error, _} = Err ->
- Err
- end.
-
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
-carboncopy_schema() ->
- {record_info(fields, carboncopy), #carboncopy{}}.
-
-clean_table() ->
- ?DEBUG("Cleaning Riak 'carboncopy' table...", []),
- case ejabberd_riak:get(carboncopy, carboncopy_schema()) of
- {ok, Rs} ->
- lists:foreach(
- fun(#carboncopy{us = {U, S}, resource = R, node = Node})
- when Node == node() ->
- ejabberd_riak:delete(carboncopy, {U, S, R});
- (_) ->
- ok
- end, Rs);
- {error, Reason} = Err ->
- ?ERROR_MSG("Failed to clean Riak 'carboncopy' table: ~p", [Reason]),
- Err
- end.
diff --git a/src/mod_carboncopy_sql.erl b/src/mod_carboncopy_sql.erl
deleted file mode 100644
index 46b6ea806..000000000
--- a/src/mod_carboncopy_sql.erl
+++ /dev/null
@@ -1,91 +0,0 @@
-%%%-------------------------------------------------------------------
-%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% Created : 29 Mar 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%%
-%%%
-%%% ejabberd, Copyright (C) 2002-2018 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_carboncopy_sql).
--behaviour(mod_carboncopy).
-
--compile([{parse_transform, ejabberd_sql_pt}]).
-
-%% API
--export([init/2, enable/4, disable/3, list/2]).
-
--include("logger.hrl").
--include("ejabberd_sql_pt.hrl").
-
-%%%===================================================================
-%%% API
-%%%===================================================================
-init(Host, _Opts) ->
- clean_table(Host).
-
-enable(LUser, LServer, LResource, NS) ->
- NodeS = erlang:atom_to_binary(node(), latin1),
- case ?SQL_UPSERT(LServer, "carboncopy",
- ["!username=%(LUser)s",
- "!server_host=%(LServer)s",
- "!resource=%(LResource)s",
- "namespace=%(NS)s",
- "node=%(NodeS)s"]) of
- ok ->
- ok;
- _Err ->
- {error, db_failure}
- end.
-
-disable(LUser, LServer, LResource) ->
- case ejabberd_sql:sql_query(
- LServer,
- ?SQL("delete from carboncopy where username=%(LUser)s "
- "and %(LServer)H and resource=%(LResource)s")) of
- {updated, _} ->
- ok;
- _Err ->
- {error, db_failure}
- end.
-
-list(LUser, LServer) ->
- case ejabberd_sql:sql_query(
- LServer,
- ?SQL("select @(resource)s, @(namespace)s, @(node)s from carboncopy "
- "where username=%(LUser)s and %(LServer)H")) of
- {selected, Rows} ->
- {ok, [{Resource, NS, binary_to_atom(Node, latin1)}
- || {Resource, NS, Node} <- Rows]};
- _Err ->
- {error, db_failure}
- end.
-
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
-clean_table(LServer) ->
- NodeS = erlang:atom_to_binary(node(), latin1),
- ?DEBUG("Cleaning SQL 'carboncopy' table...", []),
- case ejabberd_sql:sql_query(
- LServer,
- ?SQL("delete from carboncopy where node=%(NodeS)s")) of
- {updated, _} ->
- ok;
- Err ->
- ?ERROR_MSG("failed to clean 'carboncopy' table: ~p", [Err]),
- {error, db_failure}
- end.
diff --git a/src/mod_client_state.erl b/src/mod_client_state.erl
index 99a810fdb..156fde726 100644
--- a/src/mod_client_state.erl
+++ b/src/mod_client_state.erl
@@ -237,7 +237,7 @@ filter_chat_states({#message{meta = #{csi_resend := true}}, _} = Acc) ->
Acc;
filter_chat_states({#message{from = From, to = To} = Msg,
#{csi_state := inactive} = C2SState} = Acc) ->
- case xmpp_util:is_standalone_chat_state(Msg) of
+ case misc:is_standalone_chat_state(Msg) of
true ->
case {From, To} of
{#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}} ->
@@ -352,7 +352,7 @@ flush_stanzas(#{lserver := LServer} = C2SState, Elems) ->
-spec add_delay_info(stanza(), binary(), csi_timestamp()) -> stanza().
add_delay_info(Stanza, LServer, {_Seq, TimeStamp}) ->
- Stanza1 = xmpp_util:add_delay_info(
+ Stanza1 = misc:add_delay_info(
Stanza, jid:make(LServer), TimeStamp,
<<"Client Inactive">>),
xmpp:put_meta(Stanza1, csi_resend, true).
diff --git a/src/mod_fail2ban.erl b/src/mod_fail2ban.erl
index 3e6fec62f..11004efef 100644
--- a/src/mod_fail2ban.erl
+++ b/src/mod_fail2ban.erl
@@ -36,7 +36,11 @@
handle_info/2, terminate/2, code_change/3,
mod_opt_type/1, mod_options/1, depends/2]).
+%% ejabberd command.
+-export([get_commands_spec/0, unban/1]).
+
-include_lib("stdlib/include/ms_transform.hrl").
+-include("ejabberd_commands.hrl").
-include("logger.hrl").
-include("xmpp.hrl").
@@ -101,9 +105,16 @@ c2s_stream_started(#{ip := {Addr, _}} = State, _) ->
start(Host, Opts) ->
catch ets:new(failed_auth, [named_table, public,
{heir, erlang:group_leader(), none}]),
+ ejabberd_commands:register_commands(get_commands_spec()),
gen_mod:start_child(?MODULE, Host, Opts).
stop(Host) ->
+ case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
+ false ->
+ ejabberd_commands:unregister_commands(get_commands_spec());
+ true ->
+ ok
+ end,
gen_mod:stop_child(?MODULE, Host).
reload(_Host, _NewOpts, _OldOpts) ->
@@ -155,6 +166,46 @@ terminate(_Reason, #state{host = Host}) ->
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
+%%--------------------------------------------------------------------
+%% ejabberd command callback.
+%%--------------------------------------------------------------------
+-spec get_commands_spec() -> [ejabberd_commands()].
+get_commands_spec() ->
+ [#ejabberd_commands{name = unban_ip, tags = [accounts],
+ desc = "Remove banned IP addresses from the fail2ban table",
+ longdesc = "Accepts an IP address with a network mask. "
+ "Returns the number of unbanned addresses, or a negative integer if there were any error.",
+ module = ?MODULE, function = unban,
+ args = [{address, binary}],
+ args_example = [<<"::FFFF:127.0.0.1/128">>],
+ args_desc = ["IP address, optionally with network mask."],
+ result_example = 3,
+ result_desc = "Amount of unbanned entries, or negative in case of error.",
+ result = {unbanned, integer}}].
+
+-spec unban(string()) -> integer().
+unban(S) ->
+ case acl:parse_ip_netmask(S) of
+ {ok, Net, Mask} ->
+ unban(Net, Mask);
+ error ->
+ ?WARNING_MSG("Invalid network address when trying to unban: ~p", [S]),
+ -1
+ end.
+
+unban(Net, Mask) ->
+ ets:foldl(
+ fun({Addr, _, _, _}, Acc) ->
+ case acl:ip_matches_mask(Addr, Net, Mask) of
+ true ->
+ ets:delete(failed_auth, Addr),
+ Acc+1;
+ false -> Acc
+ end
+ end,
+ 0,
+ failed_auth).
+
%%%===================================================================
%%% Internal functions
%%%===================================================================
@@ -168,8 +219,8 @@ log_and_disconnect(#{ip := {Addr, _}, lang := Lang} = State, Attempts, UnbanTS)
"from this IP address (~s). The address "
"will be unblocked at ~s UTC">>,
Args = [Attempts, IP, UnbanDate],
- ?INFO_MSG("Connection attempt from blacklisted IP ~s: ~s",
- [IP, io_lib:fwrite(Format, Args)]),
+ ?WARNING_MSG("Connection attempt from blacklisted IP ~s: ~s",
+ [IP, io_lib:fwrite(Format, Args)]),
Err = xmpp:serr_policy_violation({Format, Args}, Lang),
{stop, ejabberd_c2s:send(State, Err)}.
diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl
index 84866d4d0..e6e618bd0 100644
--- a/src/mod_http_api.erl
+++ b/src/mod_http_api.erl
@@ -80,6 +80,7 @@
-include("xmpp.hrl").
-include("logger.hrl").
-include("ejabberd_http.hrl").
+-include("ejabberd_stacktrace.hrl").
-define(DEFAULT_API_VERSION, 0).
@@ -192,9 +193,8 @@ process([Call], #request{method = 'POST', data = Data, ip = IPPort} = Req) ->
_:{error,{_,invalid_json}} = _Err ->
?DEBUG("Bad Request: ~p", [_Err]),
badrequest_response(<<"Invalid JSON input">>);
- _:_Error ->
- St = erlang:get_stacktrace(),
- ?DEBUG("Bad Request: ~p ~p", [_Error, St]),
+ ?EX_RULE(_Class, _Error, Stack) ->
+ ?DEBUG("Bad Request: ~p ~p", [_Error, ?EX_STACK(Stack)]),
badrequest_response()
end;
process([Call], #request{method = 'GET', q = Data, ip = {IP, _}} = Req) ->
@@ -210,9 +210,8 @@ process([Call], #request{method = 'GET', q = Data, ip = {IP, _}} = Req) ->
%% TODO We need to refactor to remove redundant error return formatting
throw:{error, unknown_command} ->
json_format({404, 44, <<"Command not found.">>});
- _:_Error ->
- St = erlang:get_stacktrace(),
- ?DEBUG("Bad Request: ~p ~p", [_Error, St]),
+ ?EX_RULE(_, _Error, Stack) ->
+ ?DEBUG("Bad Request: ~p ~p", [_Error, ?EX_STACK(Stack)]),
badrequest_response()
end;
process([_Call], #request{method = 'OPTIONS', data = <<>>}) ->
@@ -274,20 +273,8 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
case ejabberd_commands:get_command_format(Call, Auth, Version) of
{ArgsSpec, _} when is_list(ArgsSpec) ->
Args2 = [{misc:binary_to_atom(Key), Value} || {Key, Value} <- Args],
- Spec = lists:foldr(
- fun ({Key, binary}, Acc) ->
- [{Key, <<>>}|Acc];
- ({Key, string}, Acc) ->
- [{Key, ""}|Acc];
- ({Key, integer}, Acc) ->
- [{Key, 0}|Acc];
- ({Key, {list, _}}, Acc) ->
- [{Key, []}|Acc];
- ({Key, atom}, Acc) ->
- [{Key, undefined}|Acc]
- end, [], ArgsSpec),
try
- handle2(Call, Auth, match(Args2, Spec), Version)
+ handle2(Call, Auth, Args2, Version)
catch throw:not_found ->
{404, <<"not_found">>};
throw:{not_found, Why} when is_atom(Why) ->
@@ -301,9 +288,9 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
throw:{not_allowed, Msg} ->
{401, iolist_to_binary(Msg)};
throw:{error, account_unprivileged} ->
- {403, 31, <<"Command need to be run with admin privilege.">>};
- throw:{error, access_rules_unauthorized} ->
- {403, 32, <<"AccessRules: Account does not have the right to perform the operation.">>};
+ {403, 31, <<"Command need to be run with admin privilege.">>};
+ throw:{error, access_rules_unauthorized} ->
+ {403, 32, <<"AccessRules: Account does not have the right to perform the operation.">>};
throw:{invalid_parameter, Msg} ->
{400, iolist_to_binary(Msg)};
throw:{error, Why} when is_atom(Why) ->
@@ -314,9 +301,8 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
{400, misc:atom_to_binary(Error)};
throw:Msg when is_list(Msg); is_binary(Msg) ->
{400, iolist_to_binary(Msg)};
- _Error ->
- St = erlang:get_stacktrace(),
- ?ERROR_MSG("REST API Error: ~p ~p", [_Error, St]),
+ ?EX_RULE(Class, Error, Stack) ->
+ ?ERROR_MSG("REST API Error: ~p:~p ~p", [Class, Error, ?EX_STACK(Stack)]),
{500, <<"internal_error">>}
end;
{error, Msg} ->
@@ -337,15 +323,20 @@ handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
format_command_result(Call, Auth, Res, Version)
end.
-get_elem_delete(A, L) ->
+get_elem_delete(A, L, F) ->
case proplists:get_all_values(A, L) of
[Value] -> {Value, proplists:delete(A, L)};
[_, _ | _] ->
%% Crash reporting the error
exit({duplicated_attribute, A, L});
[] ->
- %% Report the error and then force a crash
- exit({attribute_not_found, A, L})
+ case F of
+ {list, _} ->
+ {[], L};
+ _ ->
+ %% Report the error and then force a crash
+ exit({attribute_not_found, A, L})
+ end
end.
format_args(Args, ArgsFormat) ->
@@ -354,7 +345,7 @@ format_args(Args, ArgsFormat) ->
{Args1, Res}) ->
{ArgValue, Args2} =
get_elem_delete(ArgName,
- Args1),
+ Args1, ArgFormat),
Formatted = format_arg(ArgValue,
ArgFormat),
{Args2, Res ++ [Formatted]}
@@ -431,9 +422,6 @@ process_unicode_codepoints(Str) ->
%% internal helpers
%% ----------------
-match(Args, Spec) ->
- [{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec].
-
format_command_result(Cmd, Auth, Result, Version) ->
{_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth, Version),
case {ResultFormat, Result} of
@@ -486,6 +474,9 @@ format_result(Code, {Name, restuple}) ->
format_result(Els, {Name, {list, {_, {tuple, [{_, atom}, _]}} = Fmt}}) ->
{misc:atom_to_binary(Name), {[format_result(El, Fmt) || El <- Els]}};
+format_result(Els, {Name, {list, {_, {tuple, [{name, string}, {value, _}]}} = Fmt}}) ->
+ {misc:atom_to_binary(Name), {[format_result(El, Fmt) || El <- Els]}};
+
format_result(Els, {Name, {list, Def}}) ->
{misc:atom_to_binary(Name), [element(2, format_result(El, Def)) || El <- Els]};
@@ -494,6 +485,11 @@ format_result(Tuple, {_Name, {tuple, [{_, atom}, ValFmt]}}) ->
{_, Val2} = format_result(Val, ValFmt),
{misc:atom_to_binary(Name2), Val2};
+format_result(Tuple, {_Name, {tuple, [{name, string}, {value, _} = ValFmt]}}) ->
+ {Name2, Val} = Tuple,
+ {_, Val2} = format_result(Val, ValFmt),
+ {iolist_to_binary(Name2), Val2};
+
format_result(Tuple, {Name, {tuple, Def}}) ->
Els = lists:zip(tuple_to_list(Tuple), Def),
{misc:atom_to_binary(Name), {[format_result(El, ElDef) || {El, ElDef} <- Els]}};
@@ -504,6 +500,8 @@ format_result(404, {_Name, _}) ->
format_error_result(conflict, Code, Msg) ->
{409, Code, iolist_to_binary(Msg)};
+format_error_result(not_exists, Code, Msg) ->
+ {404, Code, iolist_to_binary(Msg)};
format_error_result(_ErrorAtom, Code, Msg) ->
{500, Code, iolist_to_binary(Msg)}.
diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl
index 7e9816cbe..fe3379f16 100644
--- a/src/mod_http_upload.erl
+++ b/src/mod_http_upload.erl
@@ -122,7 +122,7 @@
%%--------------------------------------------------------------------
%% gen_mod/supervisor callbacks.
%%--------------------------------------------------------------------
--spec start(binary(), gen_mod:opts()) -> {ok, pid()}.
+-spec start(binary(), gen_mod:opts()) -> {ok, pid()} | {error, already_started}.
start(ServerHost, Opts) ->
case gen_mod:get_opt(rm_on_unregister, Opts) of
true ->
@@ -132,7 +132,14 @@ start(ServerHost, Opts) ->
ok
end,
Proc = get_proc_name(ServerHost, ?MODULE),
- gen_mod:start_child(?MODULE, ServerHost, Opts, Proc).
+ case whereis(Proc) of
+ undefined ->
+ gen_mod:start_child(?MODULE, ServerHost, Opts, Proc);
+ _Pid ->
+ ?ERROR_MSG("Multiple virtual hosts can't use a single 'put_url' "
+ "without the @HOST@ keyword", []),
+ {error, already_started}
+ end.
-spec stop(binary()) -> ok | {error, any()}.
stop(ServerHost) ->
@@ -400,11 +407,11 @@ process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
http_response(500)
end;
{error, size_mismatch} ->
- ?INFO_MSG("Rejecting file ~s from ~s for ~s: Unexpected size (~B)",
+ ?WARNING_MSG("Rejecting file ~s from ~s for ~s: Unexpected size (~B)",
[lists:last(Slot), encode_addr(IP), Host, Length]),
http_response(413);
{error, invalid_slot} ->
- ?INFO_MSG("Rejecting file ~s from ~s for ~s: Invalid slot",
+ ?WARNING_MSG("Rejecting file ~s from ~s for ~s: Invalid slot",
[lists:last(Slot), encode_addr(IP), Host]),
http_response(403);
Error ->
@@ -436,19 +443,19 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request)
Headers3 = Headers2 ++ CustomHeaders,
http_response(200, Headers3, {file, Path});
{error, eacces} ->
- ?INFO_MSG("Cannot serve ~s to ~s: Permission denied",
+ ?WARNING_MSG("Cannot serve ~s to ~s: Permission denied",
[Path, encode_addr(IP)]),
http_response(403);
{error, enoent} ->
- ?INFO_MSG("Cannot serve ~s to ~s: No such file",
+ ?WARNING_MSG("Cannot serve ~s to ~s: No such file",
[Path, encode_addr(IP)]),
http_response(404);
{error, eisdir} ->
- ?INFO_MSG("Cannot serve ~s to ~s: Is a directory",
+ ?WARNING_MSG("Cannot serve ~s to ~s: Is a directory",
[Path, encode_addr(IP)]),
http_response(404);
{error, Error} ->
- ?INFO_MSG("Cannot serve ~s to ~s: ~s",
+ ?WARNING_MSG("Cannot serve ~s to ~s: ~s",
[Path, encode_addr(IP), format_error(Error)]),
http_response(500)
end;
@@ -565,7 +572,7 @@ create_slot(#state{service_url = undefined, max_size = MaxSize},
when MaxSize /= infinity,
Size > MaxSize ->
Text = {<<"File larger than ~w bytes">>, [MaxSize]},
- ?INFO_MSG("Rejecting file ~s from ~s (too large: ~B bytes)",
+ ?WARNING_MSG("Rejecting file ~s from ~s (too large: ~B bytes)",
[File, jid:encode(JID), Size]),
Error = xmpp:err_not_acceptable(Text, Lang),
Els = xmpp:get_els(Error),
@@ -622,15 +629,15 @@ create_slot(#state{service_url = ServiceURL},
{error, xmpp:err_service_unavailable(Txt, Lang)}
end;
{ok, {402, _Body}} ->
- ?INFO_MSG("Got status code 402 for ~s from <~s>",
+ ?WARNING_MSG("Got status code 402 for ~s from <~s>",
[jid:encode(JID), ServiceURL]),
{error, xmpp:err_resource_constraint()};
{ok, {403, _Body}} ->
- ?INFO_MSG("Got status code 403 for ~s from <~s>",
+ ?WARNING_MSG("Got status code 403 for ~s from <~s>",
[jid:encode(JID), ServiceURL]),
{error, xmpp:err_not_allowed()};
{ok, {413, _Body}} ->
- ?INFO_MSG("Got status code 413 for ~s from <~s>",
+ ?WARNING_MSG("Got status code 413 for ~s from <~s>",
[jid:encode(JID), ServiceURL]),
{error, xmpp:err_not_acceptable()};
{ok, {Code, _Body}} ->
diff --git a/src/mod_mam.erl b/src/mod_mam.erl
index 2a43322c8..beba98906 100644
--- a/src/mod_mam.erl
+++ b/src/mod_mam.erl
@@ -52,6 +52,7 @@
-define(MAX_PAGE_SIZE, 250).
-type c2s_state() :: ejabberd_c2s:state().
+-type count() :: non_neg_integer() | undefined.
-callback init(binary(), gen_mod:opts()) -> any().
-callback remove_user(binary(), binary()) -> any().
@@ -63,10 +64,11 @@
-callback store(xmlel(), binary(), {binary(), binary()}, chat | groupchat,
jid(), binary(), recv | send, integer()) -> ok | any().
-callback write_prefs(binary(), binary(), #archive_prefs{}, binary()) -> ok | any().
--callback get_prefs(binary(), binary()) -> {ok, #archive_prefs{}} | error.
+-callback get_prefs(binary(), binary()) -> {ok, #archive_prefs{}} | error | {error, db_failure}.
-callback select(binary(), jid(), jid(), mam_query:result(),
#rsm_set{} | undefined, chat | groupchat) ->
- {[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()}.
+ {[{binary(), non_neg_integer(), xmlel()}], boolean(), count()} |
+ {error, db_failure}.
-callback use_cache(binary()) -> boolean().
-callback cache_nodes(binary()) -> [node()].
-callback remove_from_archive(binary(), binary(), jid() | none) -> ok | {error, any()}.
@@ -89,42 +91,45 @@ start(Host, Opts) ->
ok
end,
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
- Mod:init(Host, Opts),
- init_cache(Mod, Host, Opts),
- register_iq_handlers(Host),
- ejabberd_hooks:add(sm_receive_packet, Host, ?MODULE,
- sm_receive_packet, 50),
- ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
- user_receive_packet, 88),
- ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
- user_send_packet, 88),
- ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
- user_send_packet_strip_tag, 500),
- ejabberd_hooks:add(offline_message_hook, Host, ?MODULE,
- offline_message, 50),
- ejabberd_hooks:add(muc_filter_message, Host, ?MODULE,
- muc_filter_message, 50),
- ejabberd_hooks:add(muc_process_iq, Host, ?MODULE,
- muc_process_iq, 50),
- ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
- disco_sm_features, 50),
- ejabberd_hooks:add(remove_user, Host, ?MODULE,
- remove_user, 50),
- ejabberd_hooks:add(remove_room, Host, ?MODULE,
- remove_room, 50),
- ejabberd_hooks:add(get_room_config, Host, ?MODULE,
- get_room_config, 50),
- ejabberd_hooks:add(set_room_option, Host, ?MODULE,
- set_room_option, 50),
- case gen_mod:get_opt(assume_mam_usage, Opts) of
- true ->
- ejabberd_hooks:add(message_is_archived, Host, ?MODULE,
- message_is_archived, 50);
- false ->
- ok
- end,
- ejabberd_commands:register_commands(get_commands_spec()),
- ok.
+ case Mod:init(Host, Opts) of
+ ok ->
+ init_cache(Mod, Host, Opts),
+ register_iq_handlers(Host),
+ ejabberd_hooks:add(sm_receive_packet, Host, ?MODULE,
+ sm_receive_packet, 50),
+ ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
+ user_receive_packet, 88),
+ ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
+ user_send_packet, 88),
+ ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
+ user_send_packet_strip_tag, 500),
+ ejabberd_hooks:add(offline_message_hook, Host, ?MODULE,
+ offline_message, 50),
+ ejabberd_hooks:add(muc_filter_message, Host, ?MODULE,
+ muc_filter_message, 50),
+ ejabberd_hooks:add(muc_process_iq, Host, ?MODULE,
+ muc_process_iq, 50),
+ ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
+ disco_sm_features, 50),
+ ejabberd_hooks:add(remove_user, Host, ?MODULE,
+ remove_user, 50),
+ ejabberd_hooks:add(remove_room, Host, ?MODULE,
+ remove_room, 50),
+ ejabberd_hooks:add(get_room_config, Host, ?MODULE,
+ get_room_config, 50),
+ ejabberd_hooks:add(set_room_option, Host, ?MODULE,
+ set_room_option, 50),
+ case gen_mod:get_opt(assume_mam_usage, Opts) of
+ true ->
+ ejabberd_hooks:add(message_is_archived, Host, ?MODULE,
+ message_is_archived, 50);
+ false ->
+ ok
+ end,
+ ejabberd_commands:register_commands(get_commands_spec());
+ Err ->
+ Err
+ end.
use_cache(Mod, Host) ->
case erlang:function_exported(Mod, use_cache, 2) of
@@ -594,37 +599,48 @@ process_iq(#iq{from = #jid{luser = LUser, lserver = LServer},
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end;
process_iq(#iq{from = #jid{luser = LUser, lserver = LServer},
- to = #jid{lserver = LServer},
+ to = #jid{lserver = LServer}, lang = Lang,
type = get, sub_els = [#mam_prefs{xmlns = NS}]} = IQ) ->
- Prefs = get_prefs(LUser, LServer),
- PrefsEl = prefs_el(Prefs#archive_prefs.default,
- Prefs#archive_prefs.always,
- Prefs#archive_prefs.never,
- NS),
- xmpp:make_iq_result(IQ, PrefsEl);
+ case get_prefs(LUser, LServer) of
+ {ok, Prefs} ->
+ PrefsEl = prefs_el(Prefs#archive_prefs.default,
+ Prefs#archive_prefs.always,
+ Prefs#archive_prefs.never,
+ NS),
+ xmpp:make_iq_result(IQ, PrefsEl);
+ {error, _} ->
+ Txt = <<"Database failure">>,
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
+ end;
process_iq(IQ) ->
xmpp:make_error(IQ, xmpp:err_not_allowed()).
process_iq(LServer, #iq{from = #jid{luser = LUser}, lang = Lang,
sub_els = [SubEl]} = IQ, MsgType) ->
- case MsgType of
- chat ->
- maybe_activate_mam(LUser, LServer);
- {groupchat, _Role, _MUCState} ->
- ok
- end,
- case SubEl of
- #mam_query{rsm = #rsm_set{index = I}} when is_integer(I) ->
- Txt = <<"Unsupported <index/> element">>,
- xmpp:make_error(IQ, xmpp:err_feature_not_implemented(Txt, Lang));
- #mam_query{rsm = RSM, xmlns = NS} ->
- case parse_query(SubEl, Lang) of
- {ok, Query} ->
- NewRSM = limit_max(RSM, NS),
- select_and_send(LServer, Query, NewRSM, IQ, MsgType);
- {error, Err} ->
- xmpp:make_error(IQ, Err)
- end
+ Ret = case MsgType of
+ chat ->
+ maybe_activate_mam(LUser, LServer);
+ {groupchat, _Role, _MUCState} ->
+ ok
+ end,
+ case Ret of
+ ok ->
+ case SubEl of
+ #mam_query{rsm = #rsm_set{index = I}} when is_integer(I) ->
+ Txt = <<"Unsupported <index/> element">>,
+ xmpp:make_error(IQ, xmpp:err_feature_not_implemented(Txt, Lang));
+ #mam_query{rsm = RSM, xmlns = NS} ->
+ case parse_query(SubEl, Lang) of
+ {ok, Query} ->
+ NewRSM = limit_max(RSM, NS),
+ select_and_send(LServer, Query, NewRSM, IQ, MsgType);
+ {error, Err} ->
+ xmpp:make_error(IQ, Err)
+ end
+ end;
+ {error, _} ->
+ Txt = <<"Database failure">>,
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end.
-spec should_archive(message(), binary()) -> boolean().
@@ -648,8 +664,20 @@ should_archive(#message{body = Body, subject = Subject,
none when Type == headline ->
false;
none ->
- xmpp:get_text(Body) /= <<>> orelse
- xmpp:get_text(Subject) /= <<>>
+ case xmpp:get_text(Body) /= <<>> orelse
+ xmpp:get_text(Subject) /= <<>> of
+ true ->
+ true;
+ _ ->
+ case misc:unwrap_mucsub_message(Pkt) of
+ #message{type = groupchat} = Msg ->
+ should_archive(Msg#message{type = chat}, LServer);
+ #message{} = Msg ->
+ should_archive(Msg, LServer);
+ _ ->
+ false
+ end
+ end
end
end;
should_archive(_, _LServer) ->
@@ -806,23 +834,27 @@ may_enter_room(From, MUCState) ->
-spec store_msg(message(), binary(), binary(), jid(), send | recv)
-> ok | pass | any().
store_msg(Pkt, LUser, LServer, Peer, Dir) ->
- Prefs = get_prefs(LUser, LServer),
- case {should_archive_peer(LUser, LServer, Prefs, Peer), Pkt} of
- {true, #message{meta = #{sm_copy := true}}} ->
- ok; % Already stored.
- {true, _} ->
- case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,
- [LUser, LServer, Peer, chat, Dir]) of
- drop ->
- pass;
- Pkt1 ->
- US = {LUser, LServer},
- ID = get_stanza_id(Pkt1),
- El = xmpp:encode(Pkt1),
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:store(El, LServer, US, chat, Peer, <<"">>, Dir, ID)
+ case get_prefs(LUser, LServer) of
+ {ok, Prefs} ->
+ case {should_archive_peer(LUser, LServer, Prefs, Peer), Pkt} of
+ {true, #message{meta = #{sm_copy := true}}} ->
+ ok; % Already stored.
+ {true, _} ->
+ case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,
+ [LUser, LServer, Peer, chat, Dir]) of
+ drop ->
+ pass;
+ Pkt1 ->
+ US = {LUser, LServer},
+ ID = get_stanza_id(Pkt1),
+ El = xmpp:encode(Pkt1),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:store(El, LServer, US, chat, Peer, <<"">>, Dir, ID)
+ end;
+ {false, _} ->
+ pass
end;
- {false, _} ->
+ {error, _} ->
pass
end.
@@ -878,18 +910,20 @@ get_prefs(LUser, LServer) ->
end,
case Res of
{ok, Prefs} ->
- Prefs;
+ {ok, Prefs};
+ {error, _} ->
+ {error, db_failure};
error ->
ActivateOpt = gen_mod:get_module_opt(
LServer, ?MODULE,
request_activates_archiving),
case ActivateOpt of
true ->
- #archive_prefs{us = {LUser, LServer}, default = never};
+ {ok, #archive_prefs{us = {LUser, LServer}, default = never}};
false ->
Default = gen_mod:get_module_opt(
LServer, ?MODULE, default),
- #archive_prefs{us = {LUser, LServer}, default = Default}
+ {ok, #archive_prefs{us = {LUser, LServer}, default = Default}}
end
end.
@@ -918,6 +952,8 @@ maybe_activate_mam(LUser, LServer) ->
case Res of
{ok, _Prefs} ->
ok;
+ {error, _} ->
+ {error, db_failure};
error ->
Default = gen_mod:get_module_opt(
LServer, ?MODULE, default),
@@ -928,15 +964,21 @@ maybe_activate_mam(LUser, LServer) ->
end.
select_and_send(LServer, Query, RSM, #iq{from = From, to = To} = IQ, MsgType) ->
- {Msgs, IsComplete, Count} =
- case MsgType of
- chat ->
- select(LServer, From, From, Query, RSM, MsgType);
- {groupchat, _Role, _MUCState} ->
- select(LServer, From, To, Query, RSM, MsgType)
- end,
- SortedMsgs = lists:keysort(2, Msgs),
- send(SortedMsgs, Count, IsComplete, IQ).
+ Ret = case MsgType of
+ chat ->
+ select(LServer, From, From, Query, RSM, MsgType);
+ {groupchat, _Role, _MUCState} ->
+ select(LServer, From, To, Query, RSM, MsgType)
+ end,
+ case Ret of
+ {Msgs, IsComplete, Count} ->
+ SortedMsgs = lists:keysort(2, Msgs),
+ send(SortedMsgs, Count, IsComplete, IQ);
+ {error, _} ->
+ Txt = <<"Database failure">>,
+ Err = xmpp:err_internal_server_error(Txt, IQ#iq.lang),
+ xmpp:make_error(IQ, Err)
+ end.
select(_LServer, JidRequestor, JidArchive, Query, RSM,
{groupchat, _Role, #state{config = #config{mam = false},
@@ -1033,7 +1075,7 @@ maybe_update_from_to(Pkt, _JidRequestor, _JidArchive, _Peer, chat, _Nick) ->
Pkt.
-spec send([{binary(), integer(), xmlel()}],
- non_neg_integer(), boolean(), iq()) -> iq() | ignore.
+ count(), boolean(), iq()) -> iq() | ignore.
send(Msgs, Count, IsComplete,
#iq{from = From, to = To,
sub_els = [#mam_query{id = QID, xmlns = NS}]} = IQ) ->
@@ -1071,7 +1113,7 @@ send(Msgs, Count, IsComplete,
ignore
end.
--spec make_rsm_out([{binary(), integer(), xmlel()}], non_neg_integer()) -> rsm_set().
+-spec make_rsm_out([{binary(), integer(), xmlel()}], count()) -> rsm_set().
make_rsm_out([], Count) ->
#rsm_set{count = Count};
make_rsm_out([{FirstID, _, _}|_] = Msgs, Count) ->
@@ -1160,7 +1202,7 @@ mod_opt_type(O) when O == cache_life_time; O == cache_size ->
fun (I) when is_integer(I), I > 0 -> I;
(infinity) -> infinity
end;
-mod_opt_type(O) when O == use_cache; O == cache_missed ->
+mod_opt_type(O) when O == use_cache; O == cache_missed; O == compress_xml ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(default) ->
@@ -1175,6 +1217,7 @@ mod_options(Host) ->
[{assume_mam_usage, false},
{default, never},
{request_activates_archiving, false},
+ {compress_xml, false},
{db_type, ejabberd_config:default_db(Host, ?MODULE)},
{use_cache, ejabberd_config:use_cache(Host)},
{cache_size, ejabberd_config:cache_size(Host)},
diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl
index 55154f6bb..553492583 100644
--- a/src/mod_mam_mnesia.erl
+++ b/src/mod_mam_mnesia.erl
@@ -48,13 +48,20 @@
%%% API
%%%===================================================================
init(_Host, _Opts) ->
- ejabberd_mnesia:create(?MODULE, archive_msg,
+ try
+ {atomic, _} = ejabberd_mnesia:create(
+ ?MODULE, archive_msg,
[{disc_only_copies, [node()]},
{type, bag},
{attributes, record_info(fields, archive_msg)}]),
- ejabberd_mnesia:create(?MODULE, archive_prefs,
+ {atomic, _} = ejabberd_mnesia:create(
+ ?MODULE, archive_prefs,
[{disc_only_copies, [node()]},
- {attributes, record_info(fields, archive_prefs)}]).
+ {attributes, record_info(fields, archive_prefs)}]),
+ ok
+ catch _:{badmatch, _} ->
+ {error, db_failure}
+ end.
remove_user(LUser, LServer) ->
US = {LUser, LServer},
diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl
index 37ea8dc6f..1c9b7cea2 100644
--- a/src/mod_mam_sql.erl
+++ b/src/mod_mam_sql.erl
@@ -102,9 +102,18 @@ store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, TS) ->
jid:remove_resource(Peer))),
LPeer = jid:encode(
jid:tolower(Peer)),
- XML = fxml:element_to_binary(Pkt),
Body = fxml:get_subtag_cdata(Pkt, <<"body">>),
SType = misc:atom_to_binary(Type),
+ XML = case gen_mod:get_module_opt(LServer, mod_mam, compress_xml) of
+ true ->
+ J1 = case Type of
+ chat -> jid:encode({LUser, LHost, <<>>});
+ groupchat -> SUser
+ end,
+ xml_compress:encode(Pkt, J1, LPeer);
+ _ ->
+ fxml:element_to_binary(Pkt)
+ end,
case ejabberd_sql:sql_query(
LServer,
?SQL_INSERT(
@@ -192,8 +201,8 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
{lists:flatmap(
fun([TS, XML, PeerBin, Kind, Nick]) ->
case make_archive_el(
- TS, XML, PeerBin, Kind, Nick,
- MsgType, JidRequestor, JidArchive) of
+ jid:encode(JidArchive), TS, XML, PeerBin, Kind, Nick,
+ MsgType, JidRequestor, JidArchive) of
{ok, El} ->
[{TS, binary_to_integer(TS), El}];
{error, _} ->
@@ -399,13 +408,13 @@ get_max_direction_id(RSM) ->
{undefined, undefined, <<>>}
end.
--spec make_archive_el(binary(), binary(), binary(), binary(),
+-spec make_archive_el(binary(), binary(), binary(), binary(), binary(),
binary(), _, jid(), jid()) ->
{ok, xmpp_element()} | {error, invalid_jid |
invalid_timestamp |
invalid_xml}.
-make_archive_el(TS, XML, Peer, Kind, Nick, MsgType, JidRequestor, JidArchive) ->
- case fxml_stream:parse_element(XML) of
+make_archive_el(User, TS, XML, Peer, Kind, Nick, MsgType, JidRequestor, JidArchive) ->
+ case xml_compress:decode(XML, User, Peer) of
#xmlel{} = El ->
try binary_to_integer(TS) of
TSInt ->
diff --git a/src/mod_mix.erl b/src/mod_mix.erl
deleted file mode 100644
index 78e5d0251..000000000
--- a/src/mod_mix.erl
+++ /dev/null
@@ -1,323 +0,0 @@
-%%%-------------------------------------------------------------------
-%%% File : mod_mix.erl
-%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% Created : 2 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%%
-%%%
-%%% ejabberd, Copyright (C) 2002-2018 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_mix).
-
--behaviour(gen_server).
--behaviour(gen_mod).
-
-%% API
--export([start/2, stop/1, process_iq/1,
- disco_items/5, disco_identity/5, disco_info/5,
- disco_features/5, mod_opt_type/1, mod_options/1, depends/2]).
-
-%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
-
--include("logger.hrl").
--include("xmpp.hrl").
-
--define(NODES, [?NS_MIX_NODES_MESSAGES,
- ?NS_MIX_NODES_PRESENCE,
- ?NS_MIX_NODES_PARTICIPANTS,
- ?NS_MIX_NODES_SUBJECT,
- ?NS_MIX_NODES_CONFIG]).
-
--record(state, {server_host :: binary(),
- hosts :: [binary()]}).
-
-%%%===================================================================
-%%% API
-%%%===================================================================
-start(Host, Opts) ->
- gen_mod:start_child(?MODULE, Host, Opts).
-
-stop(Host) ->
- gen_mod:stop_child(?MODULE, Host).
-
--spec disco_features({error, stanza_error()} | {result, [binary()]} | empty,
- jid(), jid(), binary(), binary()) -> {result, [binary()]}.
-disco_features(_Acc, _From, _To, _Node, _Lang) ->
- {result, [?NS_MIX_0]}.
-
-disco_items(_Acc, _From, To, _Node, _Lang) when To#jid.luser /= <<"">> ->
- BareTo = jid:remove_resource(To),
- {result, [#disco_item{jid = BareTo, node = Node} || Node <- ?NODES]};
-disco_items(_Acc, _From, _To, _Node, _Lang) ->
- {result, []}.
-
-disco_identity(Acc, _From, To, _Node, _Lang) when To#jid.luser == <<"">> ->
- Acc ++ [#identity{category = <<"conference">>,
- name = <<"MIX service">>,
- type = <<"text">>}];
-disco_identity(Acc, _From, _To, _Node, _Lang) ->
- Acc ++ [#identity{category = <<"conference">>,
- type = <<"mix">>}].
-
--spec disco_info([xdata()], binary(), module(), binary(), binary()) -> [xdata()];
- ([xdata()], jid(), jid(), binary(), binary()) -> [xdata()].
-disco_info(_Acc, _From, To, _Node, _Lang) when is_atom(To) ->
- [#xdata{type = result,
- fields = [#xdata_field{var = <<"FORM_TYPE">>,
- type = hidden,
- values = [?NS_MIX_SERVICEINFO_0]}]}];
-disco_info(Acc, _From, _To, _Node, _Lang) ->
- Acc.
-
-process_iq(#iq{type = set, from = From, to = To,
- sub_els = [#mix_join{subscribe = SubNodes}]} = IQ) ->
- Nodes = [Node || Node <- SubNodes, lists:member(Node, ?NODES)],
- case subscribe_nodes(From, To, Nodes) of
- {result, _} ->
- case publish_participant(From, To) of
- {result, _} ->
- BareFrom = jid:remove_resource(From),
- xmpp:make_iq_result(
- IQ, #mix_join{jid = BareFrom, subscribe = Nodes});
- {error, Err} ->
- xmpp:make_error(IQ, Err)
- end;
- {error, Err} ->
- xmpp:make_error(IQ, Err)
- end;
-process_iq(#iq{type = set, from = From, to = To,
- sub_els = [#mix_leave{}]} = IQ) ->
- case delete_participant(From, To) of
- {result, _} ->
- case unsubscribe_nodes(From, To, ?NODES) of
- {result, _} ->
- xmpp:make_iq_result(IQ);
- {error, Err} ->
- xmpp:make_error(IQ, Err)
- end;
- {error, Err} ->
- xmpp:make_error(IQ, Err)
- end;
-process_iq(#iq{lang = Lang} = IQ) ->
- Txt = <<"Unsupported MIX query">>,
- xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)).
-
-%%%===================================================================
-%%% gen_server callbacks
-%%%===================================================================
-init([ServerHost, Opts]) ->
- process_flag(trap_exit, true),
- Hosts = gen_mod:get_opt_hosts(ServerHost, Opts),
- lists:foreach(
- fun(Host) ->
- ConfigTab = gen_mod:get_module_proc(Host, config),
- ets:new(ConfigTab, [named_table]),
- ets:insert(ConfigTab, {plugins, [<<"mix">>]}),
- ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100),
- ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100),
- ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100),
- ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100),
- ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100),
- ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
- ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host,
- ?NS_DISCO_ITEMS, mod_disco,
- process_local_iq_items),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host,
- ?NS_DISCO_INFO, mod_disco,
- process_local_iq_info),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- ?NS_DISCO_ITEMS, mod_disco,
- process_local_iq_items),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- ?NS_DISCO_INFO, mod_disco,
- process_local_iq_info),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- ?NS_PUBSUB, mod_pubsub, iq_sm),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- ?NS_MIX_0, ?MODULE, process_iq),
- ejabberd_router:register_route(Host, ServerHost)
- end, Hosts),
- {ok, #state{server_host = ServerHost, hosts = Hosts}}.
-
-handle_call(_Request, _From, State) ->
- Reply = ok,
- {reply, Reply, State}.
-
-handle_cast(_Msg, State) ->
- {noreply, State}.
-
-handle_info({route, Packet}, State) ->
- case catch do_route(State, Packet) of
- {'EXIT', _} = Err ->
- try
- ?ERROR_MSG("failed to route packet:~n~s~nReason: ~p",
- [xmpp:pp(Packet), Err]),
- Error = xmpp:err_internal_server_error(),
- ejabberd_router:route_error(Packet, Error)
- catch _:_ ->
- ok
- end;
- _ ->
- ok
- end,
- {noreply, State};
-handle_info(_Info, State) ->
- {noreply, State}.
-
-terminate(_Reason, #state{hosts = Hosts}) ->
- lists:foreach(
- fun(Host) ->
- ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100),
- ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100),
- ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100),
- ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100),
- ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100),
- ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
- ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0),
- ejabberd_router:unregister_route(Host)
- end, Hosts).
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
-do_route(_State, #iq{} = Packet) ->
- ejabberd_router:process_iq(Packet);
-do_route(_State, #presence{from = From, to = To, type = unavailable})
- when To#jid.luser /= <<"">> ->
- delete_presence(From, To);
-do_route(_State, _Packet) ->
- ok.
-
-subscribe_nodes(From, To, Nodes) ->
- LTo = jid:tolower(jid:remove_resource(To)),
- LFrom = jid:tolower(jid:remove_resource(From)),
- lists:foldl(
- fun(_Node, {error, _} = Err) ->
- Err;
- (Node, {result, _}) ->
- case mod_pubsub:subscribe_node(LTo, Node, From, From, []) of
- {error, _} = Err ->
- case is_item_not_found(Err) of
- true ->
- case mod_pubsub:create_node(
- LTo, To#jid.lserver, Node, LFrom, <<"mix">>) of
- {result, _} ->
- mod_pubsub:subscribe_node(LTo, Node, From, From, []);
- Error ->
- Error
- end;
- false ->
- Err
- end;
- {result, _} = Result ->
- Result
- end
- end, {result, []}, Nodes).
-
-unsubscribe_nodes(From, To, Nodes) ->
- LTo = jid:tolower(jid:remove_resource(To)),
- BareFrom = jid:remove_resource(From),
- lists:foldl(
- fun(_Node, {error, _} = Err) ->
- Err;
- (Node, {result, _} = Result) ->
- case mod_pubsub:unsubscribe_node(LTo, Node, From, BareFrom, <<"">>) of
- {error, _} = Err ->
- case is_not_subscribed(Err) of
- true -> Result;
- _ -> Err
- end;
- {result, _} = Res ->
- Res
- end
- end, {result, []}, Nodes).
-
-publish_participant(From, To) ->
- BareFrom = jid:remove_resource(From),
- LFrom = jid:tolower(BareFrom),
- LTo = jid:tolower(jid:remove_resource(To)),
- Participant = #mix_participant{jid = BareFrom},
- ItemID = str:sha(jid:encode(LFrom)),
- mod_pubsub:publish_item(
- LTo, To#jid.lserver, ?NS_MIX_NODES_PARTICIPANTS,
- From, ItemID, [xmpp:encode(Participant)]).
-
-delete_presence(From, To) ->
- LFrom = jid:tolower(From),
- LTo = jid:tolower(jid:remove_resource(To)),
- case mod_pubsub:get_items(LTo, ?NS_MIX_NODES_PRESENCE) of
- Items when is_list(Items) ->
- lists:foreach(
- fun({pubsub_item, {ItemID, _}, _, {_, LJID}, _})
- when LJID == LFrom ->
- delete_item(From, To, ?NS_MIX_NODES_PRESENCE, ItemID);
- (_) ->
- ok
- end, Items);
- _ ->
- ok
- end.
-
-delete_participant(From, To) ->
- LFrom = jid:tolower(jid:remove_resource(From)),
- ItemID = str:sha(jid:encode(LFrom)),
- delete_presence(From, To),
- delete_item(From, To, ?NS_MIX_NODES_PARTICIPANTS, ItemID).
-
-delete_item(From, To, Node, ItemID) ->
- LTo = jid:tolower(jid:remove_resource(To)),
- case mod_pubsub:delete_item(
- LTo, Node, From, ItemID, true) of
- {result, _} = Res ->
- Res;
- {error, _} = Err ->
- case is_item_not_found(Err) of
- true -> {result, []};
- false -> Err
- end
- end.
-
--spec is_item_not_found({error, stanza_error()}) -> boolean().
-is_item_not_found({error, #stanza_error{reason = 'item-not-found'}}) -> true;
-is_item_not_found({error, _}) -> false.
-
--spec is_not_subscribed({error, stanza_error()}) -> boolean().
-is_not_subscribed({error, StanzaError}) ->
- xmpp:has_subtag(StanzaError, #ps_error{type = 'not-subscribed'}).
-
-depends(_Host, _Opts) ->
- [{mod_pubsub, hard}].
-
-mod_opt_type(host) -> fun ejabberd_config:v_host/1;
-mod_opt_type(hosts) -> fun ejabberd_config:v_hosts/1.
-
-mod_options(_Host) ->
- [{host, <<"mix.@HOST@">>},
- {hosts, []}].
diff --git a/src/mod_muc.erl b/src/mod_muc.erl
index 3024400ac..8a80e3012 100644
--- a/src/mod_muc.erl
+++ b/src/mod_muc.erl
@@ -435,7 +435,7 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
{_AccessRoute, AccessCreate, _AccessAdmin, _AccessPersistent} = Access,
{Room, _, Nick} = jid:tolower(To),
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
- case find_online_available_room(RMod, ServerHost, Room, Host) of
+ case RMod:find_online_room(ServerHost, Room, Host) of
error ->
case is_create_request(Packet) of
true ->
@@ -463,38 +463,12 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
Err = xmpp:err_item_not_found(ErrText, Lang),
ejabberd_router:route_error(Packet, Err)
end;
- locked_room ->
- Lang = xmpp:get_lang(Packet),
- ErrText = <<"Conference room was destroyed">>,
- Err = xmpp:err_item_not_found(ErrText, Lang),
- ejabberd_router:route_error(Packet, Err);
{ok, Pid} ->
?DEBUG("MUC: send to process ~p~n", [Pid]),
mod_muc_room:route(Pid, Packet),
ok
end.
-find_online_available_room(RMod, ServerHost, Room, Host) ->
- case RMod:find_online_room(ServerHost, Room, Host) of
- error ->
- error;
- {ok, Pid} ->
- check_tombstone(Pid)
- end.
-
--define(TOMBSTONE_REASON, <<"Expiring tombstone">>).
-
-check_tombstone(Pid) ->
- case p1_fsm:sync_send_all_state_event(Pid, check_tombstone) of
- not_tombstone ->
- {ok, Pid};
- locked ->
- locked_room;
- expired ->
- p1_fsm:send_all_state_event(Pid, {destroy, ?TOMBSTONE_REASON}),
- error
- end.
-
-spec process_vcard(iq()) -> iq().
process_vcard(#iq{type = get, lang = Lang, sub_els = [#vcard_temp{}]} = IQ) ->
xmpp:make_iq_result(
@@ -949,8 +923,6 @@ mod_opt_type(min_presence_interval) ->
fun (I) when is_number(I), I >= 0 -> I end;
mod_opt_type(room_shaper) ->
fun (A) when is_atom(A) -> A end;
-mod_opt_type(tombstone_expiry) ->
- fun (I) when is_integer(I) -> I end;
mod_opt_type(user_message_shaper) ->
fun (A) when is_atom(A) -> A end;
mod_opt_type(user_presence_shaper) ->
@@ -1040,7 +1012,6 @@ mod_options(Host) ->
{queue_type, ejabberd_config:default_queue_type(Host)},
{regexp_room_id, <<"">>},
{room_shaper, none},
- {tombstone_expiry, 0},
{user_message_shaper, none},
{user_presence_shaper, none},
{default_room_options,
diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl
index 7b702cdc5..23d821642 100644
--- a/src/mod_muc_admin.erl
+++ b/src/mod_muc_admin.erl
@@ -263,9 +263,9 @@ get_commands_spec() ->
#ejabberd_commands{name = subscribe_room, tags = [muc_room],
desc = "Subscribe to a MUC conference",
module = ?MODULE, function = subscribe_room,
- args_desc = ["Full JID, including some resource", "a user's nick",
+ args_desc = ["User JID", "a user's nick",
"the room to subscribe", "nodes separated by commas: ,"],
- args_example = ["tom@localhost/dummy", "Tom", "room1@conference.localhost",
+ args_example = ["tom@localhost", "Tom", "room1@conference.localhost",
"urn:xmpp:mucsub:nodes:messages,urn:xmpp:mucsub:nodes:affiliations"],
result_desc = "The list of nodes that has subscribed",
result_example = ["urn:xmpp:mucsub:nodes:messages",
@@ -357,11 +357,11 @@ build_summary_room(Name, Host, Pid) ->
C = get_room_config(Pid),
Public = C#config.public,
S = get_room_state(Pid),
- Participants = dict:size(S#state.users),
+ Participants = maps:size(S#state.users),
{<<Name/binary, "@", Host/binary>>,
misc:atom_to_binary(Public),
Participants
- }.
+ }.
muc_register_nick(Nick, FromBinary, ServerHost) ->
Host = find_host(ServerHost),
@@ -523,7 +523,7 @@ build_info_room({Name, Host, Pid}) ->
S = get_room_state(Pid),
Just_created = S#state.just_created,
- Num_participants = length(dict:fetch_keys(S#state.users)),
+ Num_participants = maps:size(S#state.users),
History = (S#state.history)#lqueue.queue,
Ts_last_message =
@@ -778,7 +778,7 @@ decide_room({_Room_name, _Host, Room_pid}, Last_allowed) ->
Just_created = S#state.just_created,
Room_users = S#state.users,
- Num_users = length(?DICT:to_list(Room_users)),
+ Num_users = maps:size(Room_users),
History = (S#state.history)#lqueue.queue,
Ts_now = calendar:universal_time(),
@@ -854,7 +854,7 @@ get_room_occupants(Pid) ->
Info#user.nick,
atom_to_list(Info#user.role)}
end,
- dict:to_list(S#state.users)).
+ maps:to_list(S#state.users)).
get_room_occupants_number(Room, Host) ->
case get_room_pid(Room, Host) of
@@ -862,7 +862,7 @@ get_room_occupants_number(Room, Host) ->
throw({error, room_not_found});
Pid ->
S = get_room_state(Pid),
- dict:size(S#state.users)
+ maps:size(S#state.users)
end.
%%----------------------------
@@ -950,6 +950,13 @@ format_room_option(OptionString, ValueString) ->
subject_author ->ValueString;
presence_broadcast ->misc:expr_to_term(ValueString);
max_users -> binary_to_integer(ValueString);
+ voice_request_min_interval -> binary_to_integer(ValueString);
+ vcard -> ValueString;
+ vcard_xupdate when ValueString /= <<"undefined">>,
+ ValueString /= <<"external">> ->
+ ValueString;
+ lang -> ValueString;
+ pubsub -> ValueString;
_ -> misc:binary_to_atom(ValueString)
end,
{Option, Value}.
@@ -1032,7 +1039,7 @@ get_room_affiliations(Name, Service) ->
{ok, Pid} ->
%% Get the PID of the online room, then request its state
{ok, StateData} = p1_fsm:sync_send_all_state_event(Pid, get_state),
- Affiliations = ?DICT:to_list(StateData#state.affiliations),
+ Affiliations = maps:to_list(StateData#state.affiliations),
lists:map(
fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)->
{Uname, Domain, Aff, Reason};
@@ -1097,9 +1104,8 @@ subscribe_room(User, Nick, Room, Nodes) ->
try jid:decode(Room) of
#jid{luser = Name, lserver = Host} when Name /= <<"">> ->
try jid:decode(User) of
- #jid{lresource = <<"">>} ->
- throw({error, "User's JID should have a resource"});
- UserJID ->
+ UserJID1 ->
+ UserJID = jid:replace_resource(UserJID1, <<"modmucadmin">>),
case get_room_pid(Name, Host) of
Pid when is_pid(Pid) ->
case p1_fsm:sync_send_all_state_event(
@@ -1166,7 +1172,7 @@ get_config_opt_name(Pos) ->
{get_config_opt_name(Opt), element(Opt, Config)}).
make_opts(StateData) ->
Config = StateData#state.config,
- Subscribers = (?DICT):fold(
+ Subscribers = maps:fold(
fun(_LJID, Sub, Acc) ->
[{Sub#subscriber.jid,
Sub#subscriber.nick,
@@ -1198,7 +1204,7 @@ make_opts(StateData) ->
{captcha_whitelist,
(?SETS):to_list((StateData#state.config)#config.captcha_whitelist)},
{affiliations,
- (?DICT):to_list(StateData#state.affiliations)},
+ maps:to_list(StateData#state.affiliations)},
{subject, StateData#state.subject},
{subject_author, StateData#state.subject_author},
{subscribers, Subscribers}].
diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl
index 7f5ca1b71..a847a3874 100644
--- a/src/mod_muc_log.erl
+++ b/src/mod_muc_log.erl
@@ -887,7 +887,7 @@ get_room_occupants(RoomJIDString) ->
MucService = RoomJID#jid.lserver,
StateData = get_room_state(RoomName, MucService),
[{U#user.jid, U#user.nick, U#user.role}
- || {_, U} <- (?DICT):to_list(StateData#state.users)].
+ || U <- maps:values(StateData#state.users)].
-spec get_room_state(binary(), binary()) -> mod_muc_room:state().
diff --git a/src/mod_muc_mnesia.erl b/src/mod_muc_mnesia.erl
index 918ce1dcb..43cf1aa01 100644
--- a/src/mod_muc_mnesia.erl
+++ b/src/mod_muc_mnesia.erl
@@ -340,6 +340,8 @@ handle_cast(_Msg, State) ->
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
clean_table_from_bad_node(Node),
{noreply, State};
+handle_info({mnesia_system_event, {mnesia_up, _Node}}, State) ->
+ {noreply, State};
handle_info(Info, State) ->
?ERROR_MSG("unexpected info: ~p", [Info]),
{noreply, State}.
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
index a8811f58a..a52b3d55a 100644
--- a/src/mod_muc_room.erl
+++ b/src/mod_muc_room.erl
@@ -51,10 +51,10 @@
code_change/4]).
-include("logger.hrl").
-
-include("xmpp.hrl").
-
+-include("translate.hrl").
-include("mod_muc_room.hrl").
+-include("ejabberd_stacktrace.hrl").
-define(MAX_USERS_DEFAULT_LIST,
[5, 10, 20, 30, 50, 100, 200, 500, 1000, 2000, 5000]).
@@ -83,10 +83,10 @@
-callback set_affiliation(binary(), binary(), binary(), jid(), affiliation(),
binary()) -> ok | {error, any()}.
-callback set_affiliations(binary(), binary(), binary(),
- dict:dict()) -> ok | {error, any()}.
+ map()) -> ok | {error, any()}.
-callback get_affiliation(binary(), binary(), binary(),
binary(), binary()) -> {ok, affiliation()} | {error, any()}.
--callback get_affiliations(binary(), binary(), binary()) -> {ok, dict:dict()} | {error, any()}.
+-callback get_affiliations(binary(), binary(), binary()) -> {ok, map()} | {error, any()}.
-callback search_affiliation(binary(), binary(), binary(), affiliation()) ->
{ok, [{ljid(), {affiliation(), binary()}}]} | {error, any()}.
@@ -435,11 +435,10 @@ normal_state({route, ToNick,
{next_state, normal_state, StateData}
end;
normal_state({route, ToNick,
- #iq{from = From, type = Type, lang = Lang} = Packet},
- StateData) ->
- case {(StateData#state.config)#config.allow_query_users,
- (?DICT):find(jid:tolower(From), StateData#state.users)} of
- {true, {ok, #user{nick = FromNick}}} ->
+ #iq{from = From, lang = Lang} = Packet},
+ #state{config = #config{allow_query_users = AllowQuery}} = StateData) ->
+ try maps:get(jid:tolower(From), StateData#state.users) of
+ #user{nick = FromNick} when AllowQuery orelse ToNick == FromNick ->
case find_jid_by_nick(ToNick, StateData) of
false ->
ErrText = <<"Recipient is not in the conference room">>,
@@ -447,28 +446,33 @@ normal_state({route, ToNick,
ejabberd_router:route_error(Packet, Err);
To ->
FromJID = jid:replace_resource(StateData#state.jid, FromNick),
- if Type == get; Type == set ->
- ToJID = case is_vcard_request(Packet) of
- true -> jid:remove_resource(To);
- false -> To
- end,
+ case direct_iq_type(Packet) of
+ vcard ->
ejabberd_router:route_iq(
- xmpp:set_from_to(Packet, FromJID, ToJID), Packet, self());
- true ->
- ejabberd_router:route(
- xmpp:set_from_to(Packet, FromJID, To))
+ xmpp:set_from_to(Packet, FromJID, jid:remove_resource(To)),
+ Packet, self());
+ ping when ToNick == FromNick ->
+ %% Self-ping optimization from XEP-0410
+ ejabberd_router:route(xmpp:make_iq_result(Packet));
+ response ->
+ ejabberd_router:route(xmpp:set_from_to(Packet, FromJID, To));
+ #stanza_error{} = Err ->
+ ejabberd_router:route_error(Packet, Err);
+ _OtherRequest ->
+ ejabberd_router:route_iq(
+ xmpp:set_from_to(Packet, FromJID, To), Packet, self())
end
end;
- {true, error} ->
- ErrText = <<"Only occupants are allowed to send queries "
- "to the conference">>,
- Err = xmpp:err_not_acceptable(ErrText, Lang),
- ejabberd_router:route_error(Packet, Err);
_ ->
ErrText = <<"Queries to the conference members are "
"not allowed in this room">>,
Err = xmpp:err_not_allowed(ErrText, Lang),
ejabberd_router:route_error(Packet, Err)
+ catch _:{badkey, _} ->
+ ErrText = <<"Only occupants are allowed to send queries "
+ "to the conference">>,
+ Err = xmpp:err_not_acceptable(ErrText, Lang),
+ ejabberd_router:route_error(Packet, Err)
end,
{next_state, normal_state, StateData};
normal_state(_Event, StateData) ->
@@ -507,7 +511,7 @@ handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
handle_sync_event({get_disco_item, Filter, JID, Lang}, _From, StateName, StateData) ->
- Len = ?DICT:size(StateData#state.nicks),
+ Len = maps:size(StateData#state.nicks),
Reply = case (Filter == all) or (Filter == Len) or ((Filter /= 0) and (Len /= 0)) of
true ->
get_roomdesc_reply(JID, StateData,
@@ -533,9 +537,6 @@ handle_sync_event({change_config, Config}, _From,
handle_sync_event({change_state, NewStateData}, _From,
StateName, _StateData) ->
{reply, {ok, NewStateData}, StateName, NewStateData};
-handle_sync_event(check_tombstone, _From,
- StateName, StateData) ->
- {reply, check_tombstone(StateData), StateName, StateData};
handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData) ->
case process_item_change(Item, StateData, UJID) of
{error, _} = Err ->
@@ -545,7 +546,7 @@ handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData
end;
handle_sync_event(get_subscribers, _From, StateName, StateData) ->
JIDs = lists:map(fun jid:make/1,
- ?DICT:fetch_keys(StateData#state.subscribers)),
+ maps:keys(StateData#state.subscribers)),
{reply, {ok, JIDs}, StateName, StateData};
handle_sync_event({muc_subscribe, From, Nick, Nodes}, _From,
StateName, StateData) ->
@@ -586,12 +587,10 @@ handle_sync_event({muc_unsubscribe, From}, _From, StateName, StateData) ->
{reply, {error, get_error_text(Err)}, StateName, StateData}
end;
handle_sync_event({is_subscribed, From}, _From, StateName, StateData) ->
- IsSubs = case (?DICT):find(jid:split(From), StateData#state.subscribers) of
- {ok, #subscriber{nodes = Nodes}} ->
- {true, Nodes};
- error ->
- false
- end,
+ IsSubs = try maps:get(jid:split(From), StateData#state.subscribers) of
+ #subscriber{nodes = Nodes} -> {true, Nodes}
+ catch _:{badkey, _} -> false
+ end,
{reply, IsSubs, StateName, StateData};
handle_sync_event(_Event, _From, StateName,
StateData) ->
@@ -646,30 +645,27 @@ handle_info(process_room_queue,
end;
handle_info({captcha_succeed, From}, normal_state,
StateData) ->
- NewState = case (?DICT):find(From,
- StateData#state.robots)
- of
- {ok, {Nick, Packet}} ->
- Robots = (?DICT):store(From, passed,
- StateData#state.robots),
- add_new_user(From, Nick, Packet,
- StateData#state{robots = Robots});
- _ -> StateData
+ NewState = case maps:get(From, StateData#state.robots, passed) of
+ {Nick, Packet} ->
+ Robots = maps:put(From, passed, StateData#state.robots),
+ add_new_user(From, Nick, Packet,
+ StateData#state{robots = Robots});
+ passed ->
+ StateData
end,
{next_state, normal_state, NewState};
handle_info({captcha_failed, From}, normal_state,
StateData) ->
- NewState = case (?DICT):find(From,
- StateData#state.robots)
- of
- {ok, {_Nick, Packet}} ->
- Robots = (?DICT):erase(From, StateData#state.robots),
- Txt = <<"The CAPTCHA verification has failed">>,
- Lang = xmpp:get_lang(Packet),
- Err = xmpp:err_not_authorized(Txt, Lang),
- ejabberd_router:route_error(Packet, Err),
- StateData#state{robots = Robots};
- _ -> StateData
+ NewState = case maps:get(From, StateData#state.robots, passed) of
+ {_Nick, Packet} ->
+ Robots = maps:remove(From, StateData#state.robots),
+ Txt = <<"The CAPTCHA verification has failed">>,
+ Lang = xmpp:get_lang(Packet),
+ Err = xmpp:err_not_authorized(Txt, Lang),
+ ejabberd_router:route_error(Packet, Err),
+ StateData#state{robots = Robots};
+ passed ->
+ StateData
end,
{next_state, normal_state, NewState};
handle_info(shutdown, _StateName, StateData) ->
@@ -717,20 +713,19 @@ terminate(Reason, _StateName, StateData) ->
reason = ReasonT,
role = none}],
status_codes = [332,110]}]},
- (?DICT):fold(fun (LJID, Info, _) ->
- Nick = Info#user.nick,
- case Reason of
- shutdown ->
- send_wrapped(jid:replace_resource(StateData#state.jid,
- Nick),
- Info#user.jid, Packet,
- ?NS_MUCSUB_NODES_PARTICIPANTS,
- StateData);
- _ -> ok
- end,
- tab_remove_online_user(LJID, StateData)
- end,
- [], get_users_and_subscribers(StateData)),
+ maps:fold(
+ fun(LJID, Info, _) ->
+ Nick = Info#user.nick,
+ case Reason of
+ shutdown ->
+ send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
+ Info#user.jid, Packet,
+ ?NS_MUCSUB_NODES_PARTICIPANTS,
+ StateData);
+ _ -> ok
+ end,
+ tab_remove_online_user(LJID, StateData)
+ end, [], get_users_and_subscribers(StateData)),
add_to_log(room_existence, stopped, StateData),
mod_muc:room_destroyed(StateData#state.host, StateData#state.room, self(),
StateData#state.server_host),
@@ -863,7 +858,7 @@ process_normal_message(From, #message{lang = Lang} = Pkt, StateData) ->
{ok, [#muc_invite{}|_] = Invitations} ->
lists:foldl(
fun(Invitation, AccState) ->
- process_invitation(From, Invitation, Lang, AccState)
+ process_invitation(From, Pkt, Invitation, Lang, AccState)
end, StateData, Invitations);
{ok, [{role, participant}]} ->
process_voice_request(From, Pkt, StateData);
@@ -876,9 +871,9 @@ process_normal_message(From, #message{lang = Lang} = Pkt, StateData) ->
StateData
end.
--spec process_invitation(jid(), muc_invite(), binary(), state()) -> state().
-process_invitation(From, Invitation, Lang, StateData) ->
- IJID = route_invitation(From, Invitation, Lang, StateData),
+-spec process_invitation(jid(), message(), muc_invite(), binary(), state()) -> state().
+process_invitation(From, Pkt, Invitation, Lang, StateData) ->
+ IJID = route_invitation(From, Pkt, Invitation, Lang, StateData),
Config = StateData#state.config,
case Config#config.members_only of
true ->
@@ -960,11 +955,22 @@ process_voice_approval(From, Pkt, VoiceApproval, StateData) ->
StateData
end.
--spec is_vcard_request(iq()) -> boolean().
-is_vcard_request(#iq{type = T, sub_els = [El]}) ->
- (T == get orelse T == set) andalso xmpp:get_ns(El) == ?NS_VCARD;
-is_vcard_request(_) ->
- false.
+-spec direct_iq_type(iq()) -> vcard | ping | request | response | stanza_error().
+direct_iq_type(#iq{type = T, sub_els = SubEls, lang = Lang}) when T == get; T == set ->
+ case SubEls of
+ [El] ->
+ case xmpp:get_ns(El) of
+ ?NS_VCARD when T == get -> vcard;
+ ?NS_PING when T == get -> ping;
+ _ -> request
+ end;
+ [] ->
+ xmpp:err_bad_request(?T("No child elements found"), Lang);
+ [_|_] ->
+ xmpp:err_bad_request(?T("Too many child elements"), Lang)
+ end;
+direct_iq_type(#iq{}) ->
+ response.
%% @doc Check if this non participant can send message to room.
%%
@@ -985,17 +991,15 @@ is_user_allowed_message_nonparticipant(JID,
%% If the JID is not a participant, return values for a service message.
-spec get_participant_data(jid(), state()) -> {binary(), role()}.
get_participant_data(From, StateData) ->
- case (?DICT):find(jid:tolower(From),
- StateData#state.users)
- of
- {ok, #user{nick = FromNick, role = Role}} ->
- {FromNick, Role};
- error ->
- case ?DICT:find(jid:tolower(jid:remove_resource(From)),
- StateData#state.subscribers) of
- {ok, #subscriber{nick = FromNick}} ->
- {FromNick, none};
- error ->
+ try maps:get(jid:tolower(From), StateData#state.users) of
+ #user{nick = FromNick, role = Role} ->
+ {FromNick, Role}
+ catch _:{badkey, _} ->
+ try maps:get(jid:tolower(jid:remove_resource(From)),
+ StateData#state.subscribers) of
+ #subscriber{nick = FromNick} ->
+ {FromNick, none}
+ catch _:{badkey, _} ->
{<<"">>, moderator}
end
end.
@@ -1082,8 +1086,8 @@ do_process_presence(Nick, #presence{from = From, type = unavailable} = Packet,
_ -> Packet
end,
NewState = add_user_presence_un(From, NewPacket, StateData),
- case (?DICT):find(Nick, StateData#state.nicks) of
- {ok, [_, _ | _]} ->
+ case maps:get(Nick, StateData#state.nicks, []) of
+ [_, _ | _] ->
Aff = get_affiliation(From, StateData),
Item = #muc_item{affiliation = Aff, role = none, jid = From},
Pres = xmpp:set_subtag(
@@ -1117,8 +1121,8 @@ maybe_strip_status_from_presence(From, Packet, StateData) ->
-spec close_room_if_temporary_and_empty(state()) -> fsm_transition().
close_room_if_temporary_and_empty(StateData1) ->
case not (StateData1#state.config)#config.persistent
- andalso (?DICT):size(StateData1#state.users) == 0
- andalso (?DICT):size(StateData1#state.subscribers) == 0 of
+ andalso maps:size(StateData1#state.users) == 0
+ andalso maps:size(StateData1#state.subscribers) == 0 of
true ->
?INFO_MSG("Destroyed MUC room ~s because it's temporary "
"and empty",
@@ -1128,9 +1132,9 @@ close_room_if_temporary_and_empty(StateData1) ->
_ -> {next_state, normal_state, StateData1}
end.
--spec get_users_and_subscribers(state()) -> dict:dict().
+-spec get_users_and_subscribers(state()) -> map().
get_users_and_subscribers(StateData) ->
- OnlineSubscribers = ?DICT:fold(
+ OnlineSubscribers = maps:fold(
fun(LJID, _, Acc) ->
LBareJID = jid:remove_resource(LJID),
case is_subscriber(LBareJID, StateData) of
@@ -1140,16 +1144,16 @@ get_users_and_subscribers(StateData) ->
Acc
end
end, ?SETS:new(), StateData#state.users),
- ?DICT:fold(
+ maps:fold(
fun(LBareJID, #subscriber{nick = Nick}, Acc) ->
case ?SETS:is_element(LBareJID, OnlineSubscribers) of
false ->
- ?DICT:store(LBareJID,
- #user{jid = jid:make(LBareJID),
- nick = Nick,
- role = none,
- last_presence = undefined},
- Acc);
+ maps:put(LBareJID,
+ #user{jid = jid:make(LBareJID),
+ nick = Nick,
+ role = none,
+ last_presence = undefined},
+ Acc);
true ->
Acc
end
@@ -1158,12 +1162,12 @@ get_users_and_subscribers(StateData) ->
-spec is_user_online(jid(), state()) -> boolean().
is_user_online(JID, StateData) ->
LJID = jid:tolower(JID),
- (?DICT):is_key(LJID, StateData#state.users).
+ maps:is_key(LJID, StateData#state.users).
-spec is_subscriber(jid(), state()) -> boolean().
is_subscriber(JID, StateData) ->
LJID = jid:tolower(jid:remove_resource(JID)),
- (?DICT):is_key(LJID, StateData#state.subscribers).
+ maps:is_key(LJID, StateData#state.subscribers).
%% Check if the user is occupant of the room, or at least is an admin or owner.
-spec is_occupant_or_admin(jid(), state()) -> boolean().
@@ -1241,7 +1245,7 @@ get_error_text(#stanza_error{text = Txt}) ->
-spec make_reason(stanza(), jid(), state(), binary()) -> binary().
make_reason(Packet, From, StateData, Reason1) ->
- {ok, #user{nick = FromNick}} = (?DICT):find(jid:tolower(From), StateData#state.users),
+ #user{nick = FromNick} = maps:get(jid:tolower(From), StateData#state.users),
Condition = get_error_condition(xmpp:get_error(Packet)),
str:format(Reason1, [FromNick, Condition]).
@@ -1254,9 +1258,9 @@ expulse_participant(Packet, From, StateData, Reason1) ->
status = xmpp:mk_text(Reason2)},
StateData),
LJID = jid:tolower(From),
- {ok, #user{nick = Nick}} = (?DICT):find(LJID, StateData#state.users),
- case (?DICT):find(Nick, StateData#state.nicks) of
- {ok, [_, _ | _]} ->
+ #user{nick = Nick} = maps:get(LJID, StateData#state.users),
+ case maps:get(Nick, StateData#state.nicks, []) of
+ [_, _ | _] ->
Aff = get_affiliation(From, StateData),
Item = #muc_item{affiliation = Aff, role = none, jid = From},
Pres = xmpp:set_subtag(
@@ -1271,7 +1275,7 @@ expulse_participant(Packet, From, StateData, Reason1) ->
-spec get_owners(state()) -> [jid:jid()].
get_owners(StateData) ->
- ?DICT:fold(
+ maps:fold(
fun(LJID, owner, Acc) ->
[jid:make(LJID)|Acc];
(LJID, {owner, _}, Acc) ->
@@ -1306,14 +1310,14 @@ set_affiliation_fallback(JID, Affiliation, StateData, Reason) ->
LJID = jid:remove_resource(jid:tolower(JID)),
Affiliations = case Affiliation of
none ->
- (?DICT):erase(LJID, StateData#state.affiliations);
+ maps:remove(LJID, StateData#state.affiliations);
_ ->
- (?DICT):store(LJID, {Affiliation, Reason},
- StateData#state.affiliations)
+ maps:put(LJID, {Affiliation, Reason},
+ StateData#state.affiliations)
end,
StateData#state{affiliations = Affiliations}.
--spec set_affiliations(dict:dict(), state()) -> state().
+-spec set_affiliations(map(), state()) -> state().
set_affiliations(Affiliations,
#state{config = #config{persistent = false}} = StateData) ->
set_affiliations_fallback(Affiliations, StateData);
@@ -1329,7 +1333,7 @@ set_affiliations(Affiliations, StateData) ->
set_affiliations_fallback(Affiliations, StateData)
end.
--spec set_affiliations_fallback(dict:dict(), state()) -> state().
+-spec set_affiliations_fallback(map(), state()) -> state().
set_affiliations_fallback(Affiliations, StateData) ->
StateData#state{affiliations = Affiliations}.
@@ -1367,32 +1371,23 @@ do_get_affiliation(JID, StateData) ->
-spec do_get_affiliation_fallback(jid(), state()) -> affiliation().
do_get_affiliation_fallback(JID, StateData) ->
LJID = jid:tolower(JID),
- case (?DICT):find(LJID, StateData#state.affiliations) of
- {ok, Affiliation} -> Affiliation;
- _ ->
- LJID1 = jid:remove_resource(LJID),
- case (?DICT):find(LJID1, StateData#state.affiliations)
- of
- {ok, Affiliation} -> Affiliation;
- _ ->
- LJID2 = setelement(1, LJID, <<"">>),
- case (?DICT):find(LJID2,
- StateData#state.affiliations)
- of
- {ok, Affiliation} -> Affiliation;
- _ ->
- LJID3 = jid:remove_resource(LJID2),
- case (?DICT):find(LJID3,
- StateData#state.affiliations)
- of
- {ok, Affiliation} -> Affiliation;
- _ -> none
+ try maps:get(LJID, StateData#state.affiliations)
+ catch _:{badkey, _} ->
+ BareLJID = jid:remove_resource(LJID),
+ try maps:get(BareLJID, StateData#state.affiliations)
+ catch _:{badkey, _} ->
+ DomainLJID = setelement(1, LJID, <<"">>),
+ try maps:get(DomainLJID, StateData#state.affiliations)
+ catch _:{badkey, _} ->
+ DomainBareLJID = jid:remove_resource(DomainLJID),
+ try maps:get(DomainBareLJID, StateData#state.affiliations)
+ catch _:{badkey, _} -> none
end
end
end
end.
--spec get_affiliations(state()) -> dict:dict().
+-spec get_affiliations(state()) -> map().
get_affiliations(#state{config = #config{persistent = false}} = StateData) ->
get_affiliations_callback(StateData);
get_affiliations(StateData) ->
@@ -1407,7 +1402,7 @@ get_affiliations(StateData) ->
Affiliations
end.
--spec get_affiliations_callback(state()) -> dict:dict().
+-spec get_affiliations_callback(state()) -> map().
get_affiliations_callback(StateData) ->
StateData#state.affiliations.
@@ -1428,56 +1423,52 @@ set_role(JID, Role, StateData) ->
LJID = jid:tolower(JID),
LJIDs = case LJID of
{U, S, <<"">>} ->
- (?DICT):fold(fun (J, _, Js) ->
- case J of
- {U, S, _} -> [J | Js];
- _ -> Js
- end
- end,
- [], StateData#state.users);
+ maps:fold(fun (J, _, Js) ->
+ case J of
+ {U, S, _} -> [J | Js];
+ _ -> Js
+ end
+ end, [], StateData#state.users);
_ ->
- case (?DICT):is_key(LJID, StateData#state.users) of
+ case maps:is_key(LJID, StateData#state.users) of
true -> [LJID];
_ -> []
end
end,
- {Users, Nicks} = case Role of
- none ->
- lists:foldl(fun (J, {Us, Ns}) ->
- NewNs = case (?DICT):find(J, Us)
- of
- {ok,
- #user{nick = Nick}} ->
- (?DICT):erase(Nick,
- Ns);
- _ -> Ns
- end,
- {(?DICT):erase(J, Us), NewNs}
- end,
- {StateData#state.users,
- StateData#state.nicks},
- LJIDs);
- _ ->
- {lists:foldl(
- fun (J, Us) ->
- {ok, User} = (?DICT):find(J, Us),
- if User#user.last_presence == undefined ->
- Us;
- true ->
- (?DICT):store(J, User#user{role = Role}, Us)
- end
- end,
- StateData#state.users, LJIDs),
- StateData#state.nicks}
- end,
+ {Users, Nicks} =
+ case Role of
+ none ->
+ lists:foldl(
+ fun (J, {Us, Ns}) ->
+ NewNs = try maps:get(J, Us) of
+ #user{nick = Nick} ->
+ maps:remove(Nick, Ns)
+ catch _:{badkey, _} ->
+ Ns
+ end,
+ {maps:remove(J, Us), NewNs}
+ end,
+ {StateData#state.users, StateData#state.nicks}, LJIDs);
+ _ ->
+ {lists:foldl(
+ fun (J, Us) ->
+ User = maps:get(J, Us),
+ if User#user.last_presence == undefined ->
+ Us;
+ true ->
+ maps:put(J, User#user{role = Role}, Us)
+ end
+ end, StateData#state.users, LJIDs),
+ StateData#state.nicks}
+ end,
StateData#state{users = Users, nicks = Nicks}.
-spec get_role(jid(), state()) -> role().
get_role(JID, StateData) ->
LJID = jid:tolower(JID),
- case (?DICT):find(LJID, StateData#state.users) of
- {ok, #user{role = Role}} -> Role;
- _ -> none
+ try maps:get(LJID, StateData#state.users) of
+ #user{role = Role} -> Role
+ catch _:{badkey, _} -> none
end.
-spec get_default_role(affiliation(), state()) -> role().
@@ -1661,29 +1652,30 @@ prepare_room_queue(StateData) ->
-spec update_online_user(jid(), #user{}, state()) -> state().
update_online_user(JID, #user{nick = Nick} = User, StateData) ->
LJID = jid:tolower(JID),
- Nicks1 = case (?DICT):find(LJID, StateData#state.users) of
- {ok, #user{nick = OldNick}} ->
+ add_to_log(join, Nick, StateData),
+ Nicks1 = try maps:get(LJID, StateData#state.users) of
+ #user{nick = OldNick} ->
case lists:delete(
- LJID, ?DICT:fetch(OldNick, StateData#state.nicks)) of
+ LJID, maps:get(OldNick, StateData#state.nicks)) of
[] ->
- ?DICT:erase(OldNick, StateData#state.nicks);
+ maps:remove(OldNick, StateData#state.nicks);
LJIDs ->
- ?DICT:store(OldNick, LJIDs, StateData#state.nicks)
- end;
- error ->
+ maps:put(OldNick, LJIDs, StateData#state.nicks)
+ end
+ catch _:{badkey, _} ->
StateData#state.nicks
end,
- Nicks = (?DICT):update(Nick,
- fun (LJIDs) -> [LJID|LJIDs -- [LJID]] end,
- [LJID], Nicks1),
- Users = (?DICT):update(LJID,
- fun(U) ->
- U#user{nick = Nick}
- end, User, StateData#state.users),
+ Nicks = maps:update_with(Nick,
+ fun (LJIDs) -> [LJID|LJIDs -- [LJID]] end,
+ [LJID], Nicks1),
+ Users = maps:update_with(LJID,
+ fun(U) ->
+ U#user{nick = Nick}
+ end, User, StateData#state.users),
NewStateData = StateData#state{users = Users, nicks = Nicks},
- case {?DICT:find(LJID, StateData#state.users),
- ?DICT:find(LJID, NewStateData#state.users)} of
- {{ok, #user{nick = Old}}, {ok, #user{nick = New}}} when Old /= New ->
+ case {maps:get(LJID, StateData#state.users, error),
+ maps:get(LJID, NewStateData#state.users, error)} of
+ {#user{nick = Old}, #user{nick = New}} when Old /= New ->
send_nick_changing(JID, Old, NewStateData, true, true);
_ ->
ok
@@ -1693,16 +1685,16 @@ update_online_user(JID, #user{nick = Nick} = User, StateData) ->
set_subscriber(JID, Nick, Nodes, StateData) ->
BareJID = jid:remove_resource(JID),
LBareJID = jid:tolower(BareJID),
- Subscribers = ?DICT:store(LBareJID,
- #subscriber{jid = BareJID,
- nick = Nick,
- nodes = Nodes},
- StateData#state.subscribers),
- Nicks = ?DICT:store(Nick, [LBareJID], StateData#state.subscriber_nicks),
+ Subscribers = maps:put(LBareJID,
+ #subscriber{jid = BareJID,
+ nick = Nick,
+ nodes = Nodes},
+ StateData#state.subscribers),
+ Nicks = maps:put(Nick, [LBareJID], StateData#state.subscriber_nicks),
NewStateData = StateData#state{subscribers = Subscribers,
subscriber_nicks = Nicks},
store_room(NewStateData, [{add_subscription, BareJID, Nick, Nodes}]),
- case not ?DICT:is_key(LBareJID, StateData#state.subscribers) of
+ case not maps:is_key(LBareJID, StateData#state.subscribers) of
true ->
send_subscriptions_change_notifications(BareJID, Nick, subscribe, NewStateData);
_ ->
@@ -1723,18 +1715,17 @@ remove_online_user(JID, StateData) ->
-spec remove_online_user(jid(), state(), binary()) -> state().
remove_online_user(JID, StateData, Reason) ->
LJID = jid:tolower(JID),
- {ok, #user{nick = Nick}} = (?DICT):find(LJID,
- StateData#state.users),
+ #user{nick = Nick} = maps:get(LJID, StateData#state.users),
add_to_log(leave, {Nick, Reason}, StateData),
tab_remove_online_user(JID, StateData),
- Users = (?DICT):erase(LJID, StateData#state.users),
- Nicks = case (?DICT):find(Nick, StateData#state.nicks)
- of
- {ok, [LJID]} ->
- (?DICT):erase(Nick, StateData#state.nicks);
- {ok, U} ->
- (?DICT):store(Nick, U -- [LJID], StateData#state.nicks);
- error -> StateData#state.nicks
+ Users = maps:remove(LJID, StateData#state.users),
+ Nicks = try maps:get(Nick, StateData#state.nicks) of
+ [LJID] ->
+ maps:remove(Nick, StateData#state.nicks);
+ U ->
+ maps:put(Nick, U -- [LJID], StateData#state.nicks)
+ catch _:{badkey, _} ->
+ StateData#state.nicks
end,
StateData#state{users = Users, nicks = Nicks}.
@@ -1758,63 +1749,54 @@ strip_status(Presence) ->
add_user_presence(JID, Presence, StateData) ->
LJID = jid:tolower(JID),
FPresence = filter_presence(Presence),
- Users = (?DICT):update(LJID,
- fun (#user{} = User) ->
- User#user{last_presence = FPresence}
- end,
- StateData#state.users),
+ Users = maps:update_with(LJID,
+ fun (#user{} = User) ->
+ User#user{last_presence = FPresence}
+ end, StateData#state.users),
StateData#state{users = Users}.
-spec add_user_presence_un(jid(), presence(), state()) -> state().
add_user_presence_un(JID, Presence, StateData) ->
LJID = jid:tolower(JID),
FPresence = filter_presence(Presence),
- Users = (?DICT):update(LJID,
- fun (#user{} = User) ->
- User#user{last_presence = FPresence,
- role = none}
- end,
- StateData#state.users),
+ Users = maps:update_with(LJID,
+ fun (#user{} = User) ->
+ User#user{last_presence = FPresence,
+ role = none}
+ end, StateData#state.users),
StateData#state{users = Users}.
%% Find and return a list of the full JIDs of the users of Nick.
%% Return jid record.
-spec find_jids_by_nick(binary(), state()) -> [jid()].
find_jids_by_nick(Nick, StateData) ->
- Nicks = ?DICT:merge(fun(_, Val, _) -> Val end,
- StateData#state.nicks,
- StateData#state.subscriber_nicks),
- case (?DICT):find(Nick, Nicks) of
- {ok, [User]} -> [jid:make(User)];
- {ok, Users} -> [jid:make(LJID) || LJID <- Users];
- error -> []
- end.
+ Users = case maps:get(Nick, StateData#state.nicks, []) of
+ [] -> maps:get(Nick, StateData#state.subscriber_nicks, []);
+ Us -> Us
+ end,
+ [jid:make(LJID) || LJID <- Users].
%% Find and return the full JID of the user of Nick with
%% highest-priority presence. Return jid record.
-spec find_jid_by_nick(binary(), state()) -> jid() | false.
find_jid_by_nick(Nick, StateData) ->
- case (?DICT):find(Nick, StateData#state.nicks) of
- {ok, [User]} -> jid:make(User);
- {ok, [FirstUser | Users]} ->
- #user{last_presence = FirstPresence} =
- (?DICT):fetch(FirstUser, StateData#state.users),
- {LJID, _} = lists:foldl(fun (Compare,
- {HighestUser, HighestPresence}) ->
- #user{last_presence = P1} =
- (?DICT):fetch(Compare,
- StateData#state.users),
- case higher_presence(P1,
- HighestPresence)
- of
- true -> {Compare, P1};
- false ->
- {HighestUser, HighestPresence}
- end
- end,
- {FirstUser, FirstPresence}, Users),
- jid:make(LJID);
- error -> false
+ try maps:get(Nick, StateData#state.nicks) of
+ [User] -> jid:make(User);
+ [FirstUser | Users] ->
+ #user{last_presence = FirstPresence} =
+ maps:get(FirstUser, StateData#state.users),
+ {LJID, _} = lists:foldl(
+ fun(Compare, {HighestUser, HighestPresence}) ->
+ #user{last_presence = P1} =
+ maps:get(Compare, StateData#state.users),
+ case higher_presence(P1, HighestPresence) of
+ true -> {Compare, P1};
+ false -> {HighestUser, HighestPresence}
+ end
+ end, {FirstUser, FirstPresence}, Users),
+ jid:make(LJID)
+ catch _:{badkey, _} ->
+ false
end.
-spec higher_presence(undefined | presence(),
@@ -1836,7 +1818,7 @@ get_priority_from_presence(#presence{priority = Prio}) ->
-spec find_nick_by_jid(jid(), state()) -> binary().
find_nick_by_jid(JID, StateData) ->
LJID = jid:tolower(JID),
- {ok, #user{nick = Nick}} = (?DICT):find(LJID, StateData#state.users),
+ #user{nick = Nick} = maps:get(LJID, StateData#state.users),
Nick.
-spec is_nick_change(jid(), binary(), state()) -> boolean().
@@ -1845,8 +1827,7 @@ is_nick_change(JID, Nick, StateData) ->
case Nick of
<<"">> -> false;
_ ->
- {ok, #user{nick = OldNick}} = (?DICT):find(LJID,
- StateData#state.users),
+ #user{nick = OldNick} = maps:get(LJID, StateData#state.users),
Nick /= OldNick
end.
@@ -1854,9 +1835,9 @@ is_nick_change(JID, Nick, StateData) ->
nick_collision(User, Nick, StateData) ->
UserOfNick = case find_jid_by_nick(Nick, StateData) of
false ->
- case ?DICT:find(Nick, StateData#state.subscriber_nicks) of
- {ok, [J]} -> J;
- error -> false
+ try maps:get(Nick, StateData#state.subscriber_nicks) of
+ [J] -> J
+ catch _:{badkey, _} -> false
end;
J -> J
end,
@@ -1873,8 +1854,7 @@ add_new_user(From, Nick, Packet, StateData) ->
MaxUsers = get_max_users(StateData),
MaxAdminUsers = MaxUsers +
get_max_users_admin_threshold(StateData),
- NUsers = dict:fold(fun (_, _, Acc) -> Acc + 1 end, 0,
- StateData#state.users),
+ NUsers = maps:size(StateData#state.users),
Affiliation = get_affiliation(From, StateData),
ServiceAffiliation = get_service_affiliation(From,
StateData),
@@ -1976,7 +1956,7 @@ add_new_user(From, Nick, Packet, StateData) ->
true ->
NewStateData#state{just_created = false};
false ->
- Robots = (?DICT):erase(From, StateData#state.robots),
+ Robots = maps:remove(From, StateData#state.robots),
NewStateData#state{robots = Robots}
end,
if not IsSubscribeRequest -> ResultState;
@@ -2004,8 +1984,8 @@ add_new_user(From, Nick, Packet, StateData) ->
to = From,
id = ID, body = Body,
sub_els = CaptchaEls},
- Robots = (?DICT):store(From, {Nick, Packet},
- StateData#state.robots),
+ Robots = maps:put(From, {Nick, Packet},
+ StateData#state.robots),
ejabberd_router:route(MsgPkt),
NewState = StateData#state{robots = Robots},
if not IsSubscribeRequest ->
@@ -2074,9 +2054,9 @@ check_captcha(Affiliation, From, StateData) ->
andalso ejabberd_captcha:is_feature_available()
of
true when Affiliation == none ->
- case (?DICT):find(From, StateData#state.robots) of
- {ok, passed} -> true;
- _ ->
+ case maps:get(From, StateData#state.robots, error) of
+ passed -> true;
+ _ ->
WList =
(StateData#state.config)#config.captcha_whitelist,
#jid{luser = U, lserver = S, lresource = R} = From,
@@ -2153,7 +2133,7 @@ is_room_overcrowded(StateData) ->
MaxUsersPresence = gen_mod:get_module_opt(
StateData#state.server_host,
mod_muc, max_users_presence),
- (?DICT):size(StateData#state.users) > MaxUsersPresence.
+ maps:size(StateData#state.users) > MaxUsersPresence.
-spec presence_broadcast_allowed(jid(), state()) -> boolean().
presence_broadcast_allowed(JID, StateData) ->
@@ -2174,7 +2154,9 @@ send_initial_presences_and_messages(From, Nick, Presence, NewState, OldState) ->
send_self_presence(JID, State) ->
AvatarHash = (State#state.config)#config.vcard_xupdate,
DiscoInfo = make_disco_info(JID, State),
- DiscoHash = mod_caps:compute_disco_hash(DiscoInfo, sha),
+ Extras = iq_disco_info_extras(<<"en">>, State, true),
+ DiscoInfo1 = DiscoInfo#disco_info{xdata = [Extras]},
+ DiscoHash = mod_caps:compute_disco_hash(DiscoInfo1, sha),
Els1 = [#caps{hash = <<"sha-1">>,
node = ejabberd_config:get_uri(),
version = DiscoHash}],
@@ -2189,7 +2171,7 @@ send_self_presence(JID, State) ->
-spec send_initial_presence(jid(), state(), state()) -> ok.
send_initial_presence(NJID, StateData, OldStateData) ->
- send_new_presence1(NJID, <<"">>, true, StateData, OldStateData).
+ send_new_presence(NJID, <<"">>, true, StateData, OldStateData).
-spec send_update_presence(jid(), state(), state()) -> ok.
send_update_presence(JID, StateData, OldStateData) ->
@@ -2207,22 +2189,21 @@ send_update_presence1(JID, Reason, StateData, OldStateData) ->
LJID = jid:tolower(JID),
LJIDs = case LJID of
{U, S, <<"">>} ->
- (?DICT):fold(fun (J, _, Js) ->
- case J of
- {U, S, _} -> [J | Js];
- _ -> Js
- end
- end,
- [], StateData#state.users);
+ maps:fold(fun (J, _, Js) ->
+ case J of
+ {U, S, _} -> [J | Js];
+ _ -> Js
+ end
+ end, [], StateData#state.users);
_ ->
- case (?DICT):is_key(LJID, StateData#state.users) of
+ case maps:is_key(LJID, StateData#state.users) of
true -> [LJID];
_ -> []
end
end,
lists:foreach(fun (J) ->
- send_new_presence1(J, Reason, false, StateData,
- OldStateData)
+ send_new_presence(J, Reason, false, StateData,
+ OldStateData)
end,
LJIDs).
@@ -2234,14 +2215,6 @@ send_new_presence(NJID, StateData, OldStateData) ->
send_new_presence(NJID, Reason, StateData, OldStateData) ->
send_new_presence(NJID, Reason, false, StateData, OldStateData).
--spec send_new_presence(jid(), binary(), boolean(), state(), state()) -> ok.
-send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
- case is_room_overcrowded(StateData) of
- true -> ok;
- false -> send_new_presence1(NJID, Reason, IsInitialPresence, StateData,
- OldStateData)
- end.
-
-spec is_ra_changed(jid(), boolean(), state(), state()) -> boolean().
is_ra_changed(_, _IsInitialPresence = true, _, _) ->
false;
@@ -2257,32 +2230,31 @@ is_ra_changed(JID, _IsInitialPresence = false, NewStateData, OldStateData) ->
(NewRole /= OldRole) or (NewAff /= OldAff)
end.
--spec send_new_presence1(jid(), binary(), boolean(), state(), state()) -> ok.
-send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
+-spec send_new_presence(jid(), binary(), boolean(), state(), state()) -> ok.
+send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
LNJID = jid:tolower(NJID),
- #user{nick = Nick} = (?DICT):fetch(LNJID, StateData#state.users),
+ #user{nick = Nick} = maps:get(LNJID, StateData#state.users),
LJID = find_jid_by_nick(Nick, StateData),
- {ok,
- #user{jid = RealJID, role = Role0,
- last_presence = Presence0} = UserInfo} =
- (?DICT):find(jid:tolower(LJID),
- StateData#state.users),
+ #user{jid = RealJID, role = Role0,
+ last_presence = Presence0} = UserInfo =
+ maps:get(jid:tolower(LJID), StateData#state.users),
{Role1, Presence1} =
case presence_broadcast_allowed(NJID, StateData) of
true -> {Role0, Presence0};
false -> {none, #presence{type = unavailable}}
end,
Affiliation = get_affiliation(LJID, StateData),
- UserList =
- case not (presence_broadcast_allowed(NJID, StateData) orelse
- presence_broadcast_allowed(NJID, OldStateData)) of
+ UserMap =
+ case is_room_overcrowded(StateData) orelse
+ (not (presence_broadcast_allowed(NJID, StateData) orelse
+ presence_broadcast_allowed(NJID, OldStateData))) of
true ->
- [{LNJID, UserInfo}];
+ #{LNJID => UserInfo};
false ->
- (?DICT):to_list(get_users_and_subscribers(StateData))
+ get_users_and_subscribers(StateData)
end,
- lists:foreach(
- fun({LUJID, Info}) ->
+ maps:fold(
+ fun(LUJID, Info, _) ->
IsSelfPresence = LNJID == LUJID,
{Role, Presence} = if IsSelfPresence -> {Role0, Presence0};
true -> {Role1, Presence1}
@@ -2321,8 +2293,7 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
true ->
ok
end
- end,
- UserList).
+ end, ok, UserMap).
-spec send_existing_presences(jid(), state()) -> ok.
send_existing_presences(ToJID, StateData) ->
@@ -2334,15 +2305,13 @@ send_existing_presences(ToJID, StateData) ->
-spec send_existing_presences1(jid(), state()) -> ok.
send_existing_presences1(ToJID, StateData) ->
LToJID = jid:tolower(ToJID),
- {ok, #user{jid = RealToJID, role = Role}} =
- (?DICT):find(LToJID, StateData#state.users),
- lists:foreach(
- fun({FromNick, _Users}) ->
+ #user{jid = RealToJID, role = Role} = maps:get(LToJID, StateData#state.users),
+ maps:fold(
+ fun(FromNick, _Users, _) ->
LJID = find_jid_by_nick(FromNick, StateData),
#user{jid = FromJID, role = FromRole,
last_presence = Presence} =
- (?DICT):fetch(jid:tolower(LJID),
- StateData#state.users),
+ maps:get(jid:tolower(LJID), StateData#state.users),
PresenceBroadcast =
lists:member(
FromRole, (StateData#state.config)#config.presence_broadcast),
@@ -2364,41 +2333,34 @@ send_existing_presences1(ToJID, StateData) ->
send_wrapped(jid:replace_resource(StateData#state.jid, FromNick),
RealToJID, Packet, ?NS_MUCSUB_NODES_PRESENCE, StateData)
end
- end,
- (?DICT):to_list(StateData#state.nicks)).
+ end, ok, StateData#state.nicks).
-spec set_nick(jid(), binary(), state()) -> state().
set_nick(JID, Nick, State) ->
LJID = jid:tolower(JID),
- {ok, #user{nick = OldNick}} = (?DICT):find(LJID, State#state.users),
- Users = (?DICT):update(LJID,
- fun (#user{} = User) -> User#user{nick = Nick} end,
- State#state.users),
- OldNickUsers = (?DICT):fetch(OldNick, State#state.nicks),
- NewNickUsers = case (?DICT):find(Nick, State#state.nicks) of
- {ok, U} -> U;
- error -> []
- end,
+ #user{nick = OldNick} = maps:get(LJID, State#state.users),
+ Users = maps:update_with(LJID,
+ fun (#user{} = User) -> User#user{nick = Nick} end,
+ State#state.users),
+ OldNickUsers = maps:get(OldNick, State#state.nicks),
+ NewNickUsers = maps:get(Nick, State#state.nicks, []),
Nicks = case OldNickUsers of
[LJID] ->
- (?DICT):store(Nick, [LJID | NewNickUsers -- [LJID]],
- (?DICT):erase(OldNick, State#state.nicks));
+ maps:put(Nick, [LJID | NewNickUsers -- [LJID]],
+ maps:remove(OldNick, State#state.nicks));
[_ | _] ->
- (?DICT):store(Nick, [LJID | NewNickUsers -- [LJID]],
- (?DICT):store(OldNick, OldNickUsers -- [LJID],
- State#state.nicks))
+ maps:put(Nick, [LJID | NewNickUsers -- [LJID]],
+ maps:put(OldNick, OldNickUsers -- [LJID],
+ State#state.nicks))
end,
State#state{users = Users, nicks = Nicks}.
-spec change_nick(jid(), binary(), state()) -> state().
change_nick(JID, Nick, StateData) ->
LJID = jid:tolower(JID),
- {ok, #user{nick = OldNick}} = (?DICT):find(LJID, StateData#state.users),
- OldNickUsers = (?DICT):fetch(OldNick, StateData#state.nicks),
- NewNickUsers = case (?DICT):find(Nick, StateData#state.nicks) of
- {ok, U} -> U;
- error -> []
- end,
+ #user{nick = OldNick} = maps:get(LJID, StateData#state.users),
+ OldNickUsers = maps:get(OldNick, StateData#state.nicks),
+ NewNickUsers = maps:get(Nick, StateData#state.nicks, []),
SendOldUnavailable = length(OldNickUsers) == 1,
SendNewAvailable = SendOldUnavailable orelse NewNickUsers == [],
NewStateData = set_nick(JID, Nick, StateData),
@@ -2414,14 +2376,12 @@ change_nick(JID, Nick, StateData) ->
-spec send_nick_changing(jid(), binary(), state(), boolean(), boolean()) -> ok.
send_nick_changing(JID, OldNick, StateData,
SendOldUnavailable, SendNewAvailable) ->
- {ok,
- #user{jid = RealJID, nick = Nick, role = Role,
- last_presence = Presence}} =
- (?DICT):find(jid:tolower(JID),
- StateData#state.users),
+ #user{jid = RealJID, nick = Nick, role = Role,
+ last_presence = Presence} =
+ maps:get(jid:tolower(JID), StateData#state.users),
Affiliation = get_affiliation(JID, StateData),
- lists:foreach(
- fun({LJID, Info}) when Presence /= undefined ->
+ maps:fold(
+ fun(LJID, Info, _) when Presence /= undefined ->
IsSelfPresence = LJID == jid:tolower(JID),
Item0 = #muc_item{affiliation = Affiliation, role = Role},
Item = case Info#user.role == moderator orelse
@@ -2456,24 +2416,23 @@ send_nick_changing(JID, OldNick, StateData,
StateData);
true -> ok
end;
- (_) ->
+ (_, _, _) ->
ok
- end,
- ?DICT:to_list(get_users_and_subscribers(StateData))).
+ end, ok, get_users_and_subscribers(StateData)).
-spec maybe_send_affiliation(jid(), affiliation(), state()) -> ok.
maybe_send_affiliation(JID, Affiliation, StateData) ->
LJID = jid:tolower(JID),
Users = get_users_and_subscribers(StateData),
IsOccupant = case LJID of
- {LUser, LServer, <<"">>} ->
- not (?DICT):is_empty(
- (?DICT):filter(fun({U, S, _}, _) ->
- U == LUser andalso
- S == LServer
- end, Users));
- {_LUser, _LServer, _LResource} ->
- (?DICT):is_key(LJID, Users)
+ {LUser, LServer, <<"">>} ->
+ #{} /= maps:filter(
+ fun({U, S, _}, _) ->
+ U == LUser andalso
+ S == LServer
+ end, Users);
+ {_LUser, _LServer, _LResource} ->
+ maps:is_key(LJID, Users)
end,
case IsOccupant of
true ->
@@ -2492,11 +2451,11 @@ send_affiliation(JID, Affiliation, StateData) ->
Users = get_users_and_subscribers(StateData),
Recipients = case (StateData#state.config)#config.anonymous of
true ->
- (?DICT):filter(fun(_, #user{role = moderator}) ->
- true;
- (_, _) ->
- false
- end, Users);
+ maps:filter(fun(_, #user{role = moderator}) ->
+ true;
+ (_, _) ->
+ false
+ end, Users);
false ->
Users
end,
@@ -2562,7 +2521,7 @@ add_message_to_history(FromNick, FromJID, Packet, StateData) ->
jid = FromJID}]},
xmpp:set_subtag(Packet, Addresses)
end,
- TSPacket = xmpp_util:add_delay_info(
+ TSPacket = misc:add_delay_info(
AddrPacket, StateData#state.jid, TimeStamp),
SPacket = xmpp:set_from_to(
TSPacket,
@@ -2690,7 +2649,7 @@ user_to_item(#user{role = Role, nick = Nick, jid = JID},
search_role(Role, StateData) ->
lists:filter(fun ({_, #user{role = R}}) -> Role == R
end,
- (?DICT):to_list(StateData#state.users)).
+ maps:to_list(StateData#state.users)).
-spec search_affiliation(affiliation(), state()) ->
[{ljid(),
@@ -2720,8 +2679,7 @@ search_affiliation_fallback(Affiliation, StateData) ->
{A1, _Reason} -> Affiliation == A1;
_ -> Affiliation == A
end
- end,
- (?DICT):to_list(StateData#state.affiliations)).
+ end, maps:to_list(StateData#state.affiliations)).
-spec process_admin_items_set(jid(), [muc_item()], binary(),
#state{}) -> {result, undefined, #state{}} |
@@ -2808,7 +2766,7 @@ process_item_change(Item, SD, UJID) ->
maybe_send_affiliation(JID, A, SD1),
SD1
end
- catch E:R ->
+ catch ?EX_RULE(E, R, St) ->
FromSuffix = case UJID of
#jid{} ->
JidString = jid:encode(UJID),
@@ -2816,9 +2774,8 @@ process_item_change(Item, SD, UJID) ->
undefined ->
<<"">>
end,
- St = erlang:get_stacktrace(),
?ERROR_MSG("failed to set item ~p~s: ~p",
- [Item, FromSuffix, {E, {R, St}}]),
+ [Item, FromSuffix, {E, {R, ?EX_STACK(St)}}]),
{error, xmpp:err_internal_server_error()}
end.
@@ -2863,8 +2820,13 @@ find_changed_items(UJID, UAffiliation, URole,
TAffiliation = get_affiliation(JID, StateData),
TRole = get_role(JID, StateData),
ServiceAf = get_service_affiliation(JID, StateData),
+ UIsSubscriber = is_subscriber(UJID, StateData),
+ URole1 = case {URole, UIsSubscriber} of
+ {none, true} -> subscriber;
+ {UR, _} -> UR
+ end,
CanChangeRA = case can_change_ra(UAffiliation,
- URole,
+ URole1,
TAffiliation,
TRole, RoleOrAff, RoleOrAffValue,
ServiceAf) of
@@ -2984,9 +2946,19 @@ can_change_ra(_FAffiliation, _FRole, _TAffiliation,
can_change_ra(_FAffiliation, moderator, _TAffiliation,
visitor, role, none, _ServiceAf) ->
true;
+can_change_ra(FAffiliation, subscriber, _TAffiliation,
+ visitor, role, none, _ServiceAf)
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
+ true;
can_change_ra(_FAffiliation, moderator, _TAffiliation,
visitor, role, participant, _ServiceAf) ->
true;
+can_change_ra(FAffiliation, subscriber, _TAffiliation,
+ visitor, role, participant, _ServiceAf)
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
+ true;
can_change_ra(FAffiliation, _FRole, _TAffiliation,
visitor, role, moderator, _ServiceAf)
when (FAffiliation == owner) or
@@ -2995,9 +2967,19 @@ can_change_ra(FAffiliation, _FRole, _TAffiliation,
can_change_ra(_FAffiliation, moderator, _TAffiliation,
participant, role, none, _ServiceAf) ->
true;
+can_change_ra(FAffiliation, subscriber, _TAffiliation,
+ participant, role, none, _ServiceAf)
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
+ true;
can_change_ra(_FAffiliation, moderator, _TAffiliation,
participant, role, visitor, _ServiceAf) ->
true;
+can_change_ra(FAffiliation, subscriber, _TAffiliation,
+ participant, role, visitor, _ServiceAf)
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
+ true;
can_change_ra(FAffiliation, _FRole, _TAffiliation,
participant, role, moderator, _ServiceAf)
when (FAffiliation == owner) or
@@ -3027,6 +3009,24 @@ can_change_ra(_FAffiliation, _FRole, admin, moderator,
can_change_ra(admin, _FRole, _TAffiliation, moderator,
role, participant, _ServiceAf) ->
true;
+can_change_ra(owner, moderator, TAffiliation,
+ moderator, role, none, _ServiceAf)
+ when TAffiliation /= owner ->
+ true;
+can_change_ra(owner, subscriber, TAffiliation,
+ moderator, role, none, _ServiceAf)
+ when TAffiliation /= owner ->
+ true;
+can_change_ra(admin, moderator, TAffiliation,
+ moderator, role, none, _ServiceAf)
+ when (TAffiliation /= owner) and
+ (TAffiliation /= admin) ->
+ true;
+can_change_ra(admin, subscriber, TAffiliation,
+ moderator, role, none, _ServiceAf)
+ when (TAffiliation /= owner) and
+ (TAffiliation /= admin) ->
+ true;
can_change_ra(_FAffiliation, _FRole, _TAffiliation,
_TRole, role, _Value, _ServiceAf) ->
false.
@@ -3044,23 +3044,21 @@ send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation,
StateData) ->
LJID = jid:tolower(JID),
LJIDs = case LJID of
- {U, S, <<"">>} ->
- (?DICT):fold(fun (J, _, Js) ->
- case J of
- {U, S, _} -> [J | Js];
- _ -> Js
- end
- end,
- [], StateData#state.users);
- _ ->
- case (?DICT):is_key(LJID, StateData#state.users) of
- true -> [LJID];
- _ -> []
- end
+ {U, S, <<"">>} ->
+ maps:fold(fun (J, _, Js) ->
+ case J of
+ {U, S, _} -> [J | Js];
+ _ -> Js
+ end
+ end, [], StateData#state.users);
+ _ ->
+ case maps:is_key(LJID, StateData#state.users) of
+ true -> [LJID];
+ _ -> []
+ end
end,
lists:foreach(fun (J) ->
- {ok, #user{nick = Nick}} = (?DICT):find(J,
- StateData#state.users),
+ #user{nick = Nick} = maps:get(J, StateData#state.users),
add_to_log(kickban, {Nick, Reason, Code}, StateData),
tab_remove_online_user(J, StateData),
send_kickban_presence1(UJID, J, Reason, Code,
@@ -3072,12 +3070,10 @@ send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation,
affiliation(), state()) -> ok.
send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
StateData) ->
- {ok, #user{jid = RealJID, nick = Nick}} =
- (?DICT):find(jid:tolower(UJID),
- StateData#state.users),
+ #user{jid = RealJID, nick = Nick} = maps:get(jid:tolower(UJID), StateData#state.users),
ActorNick = get_actor_nick(MJID, StateData),
- lists:foreach(
- fun({LJID, Info}) ->
+ maps:fold(
+ fun(LJID, Info, _) ->
IsSelfPresence = jid:tolower(UJID) == LJID,
Item0 = #muc_item{affiliation = Affiliation,
role = none},
@@ -3109,16 +3105,15 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
true ->
ok
end
- end,
- (?DICT):to_list(get_users_and_subscribers(StateData))).
+ end, ok, get_users_and_subscribers(StateData)).
-spec get_actor_nick(undefined | jid(), state()) -> binary().
get_actor_nick(undefined, _StateData) ->
<<"">>;
get_actor_nick(MJID, StateData) ->
- case (?DICT):find(jid:tolower(MJID), StateData#state.users) of
- {ok, #user{nick = ActorNick}} -> ActorNick;
- _ -> <<"">>
+ try maps:get(jid:tolower(MJID), StateData#state.users) of
+ #user{nick = ActorNick} -> ActorNick
+ catch _:{badkey, _} -> <<"">>
end.
convert_legacy_fields(Fs) ->
@@ -3373,7 +3368,7 @@ set_config(Options, StateData, Lang) ->
{_, _} -> roomconfig_change
end,
Users = [{U#user.jid, U#user.nick, U#user.role}
- || {_, U} <- (?DICT):to_list(StateData#state.users)],
+ || U <- maps:values(StateData#state.users)],
add_to_log(Type, Users, NSD),
Res
catch _:{badmatch, {error, #stanza_error{}} = Err} ->
@@ -3496,15 +3491,15 @@ send_config_change_info(New, #state{config = Old} = StateData) ->
_ -> [104]
end,
if Codes /= [] ->
- lists:foreach(
- fun({_LJID, #user{jid = JID}}) ->
+ maps:fold(
+ fun(_LJID, #user{jid = JID}, _) ->
send_self_presence(JID, StateData#state{config = New})
- end, ?DICT:to_list(StateData#state.users)),
+ end, ok, StateData#state.users),
Message = #message{type = groupchat,
id = p1_rand:get_string(),
sub_els = [#muc_user{status_codes = Codes}]},
send_wrapped_multiple(StateData#state.jid,
- get_users_and_subscribers(StateData),
+ get_users_and_subscribers(StateData),
Message,
?NS_MUCSUB_NODES_CONFIG,
StateData);
@@ -3514,17 +3509,16 @@ send_config_change_info(New, #state{config = Old} = StateData) ->
-spec remove_nonmembers(state()) -> state().
remove_nonmembers(StateData) ->
- lists:foldl(fun ({_LJID, #user{jid = JID}}, SD) ->
- Affiliation = get_affiliation(JID, SD),
- case Affiliation of
- none ->
- catch send_kickban_presence(undefined, JID, <<"">>,
- 322, SD),
- set_role(JID, none, SD);
- _ -> SD
- end
- end,
- StateData, (?DICT):to_list(get_users_and_subscribers(StateData))).
+ maps:fold(
+ fun(_LJID, #user{jid = JID}, SD) ->
+ Affiliation = get_affiliation(JID, SD),
+ case Affiliation of
+ none ->
+ catch send_kickban_presence(undefined, JID, <<"">>, 322, SD),
+ set_role(JID, none, SD);
+ _ -> SD
+ end
+ end, StateData, get_users_and_subscribers(StateData)).
-spec set_opts([{atom(), any()}], state()) -> state().
set_opts([], StateData) ->
@@ -3661,18 +3655,18 @@ set_opts([{Opt, Val} | Opts], StateData) ->
lists:foldl(
fun({JID, Nick, Nodes}, {SubAcc, NickAcc}) ->
BareJID = jid:remove_resource(JID),
- {?DICT:store(
- jid:tolower(BareJID),
- #subscriber{jid = BareJID,
- nick = Nick,
- nodes = Nodes},
- SubAcc),
- ?DICT:store(Nick, [jid:tolower(BareJID)], NickAcc)}
- end, {?DICT:new(), ?DICT:new()}, Val),
+ {maps:put(
+ jid:tolower(BareJID),
+ #subscriber{jid = BareJID,
+ nick = Nick,
+ nodes = Nodes},
+ SubAcc),
+ maps:put(Nick, [jid:tolower(BareJID)], NickAcc)}
+ end, {#{}, #{}}, Val),
StateData#state{subscribers = Subscribers,
subscriber_nicks = Nicks};
affiliations ->
- StateData#state{affiliations = (?DICT):from_list(Val)};
+ StateData#state{affiliations = maps:from_list(Val)};
subject ->
Subj = if Val == <<"">> -> [];
is_binary(Val) -> [#text{data = Val}];
@@ -3704,7 +3698,7 @@ set_vcard_xupdate(State) ->
-spec make_opts(state()) -> [{atom(), any()}].
make_opts(StateData) ->
Config = StateData#state.config,
- Subscribers = (?DICT):fold(
+ Subscribers = maps:fold(
fun(_LJID, Sub, Acc) ->
[{Sub#subscriber.jid,
Sub#subscriber.nick,
@@ -3739,7 +3733,7 @@ make_opts(StateData) ->
{captcha_whitelist,
(?SETS):to_list((StateData#state.config)#config.captcha_whitelist)},
{affiliations,
- (?DICT):to_list(StateData#state.affiliations)},
+ maps:to_list(StateData#state.affiliations)},
{subject, StateData#state.subject},
{subject_author, StateData#state.subject_author},
{subscribers, Subscribers}].
@@ -3776,8 +3770,8 @@ config_fields() ->
-spec destroy_room(muc_destroy(), state()) -> {result, undefined, stop}.
destroy_room(DEl, StateData) ->
Destroy = DEl#muc_destroy{xmlns = ?NS_MUC_USER},
- lists:foreach(
- fun({_LJID, Info}) ->
+ maps:fold(
+ fun(_LJID, Info, _) ->
Nick = Info#user.nick,
Item = #muc_item{affiliation = none,
role = none},
@@ -3788,57 +3782,15 @@ destroy_room(DEl, StateData) ->
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
Info#user.jid, Packet,
?NS_MUCSUB_NODES_CONFIG, StateData)
- end,
- (?DICT):to_list(get_users_and_subscribers(StateData))),
+ end, ok, get_users_and_subscribers(StateData)),
case (StateData#state.config)#config.persistent of
true ->
mod_muc:forget_room(StateData#state.server_host,
- StateData#state.host, StateData#state.room),
- maybe_create_tombstone(DEl, StateData);
+ StateData#state.host, StateData#state.room);
false -> ok
end,
{result, undefined, stop}.
--define(TOMBSTONE_NAME, <<"Room tombstone">>).
--define(TOMBSTONE_REASON, <<"Expiring tombstone">>).
-
-maybe_create_tombstone(DEl, StateData) ->
- case DEl#muc_destroy.reason of
- ?TOMBSTONE_REASON ->
- ok;
- _ ->
- TombstoneExpiry = gen_mod:get_module_opt(StateData#state.server_host,
- mod_muc, tombstone_expiry),
- create_tombstone(TombstoneExpiry, StateData)
- end.
-
-create_tombstone(TombstoneExpiry, _) when TombstoneExpiry =< 0 ->
- ok;
-create_tombstone(TombstoneExpiry, #state{server_host = Server, host = Host, room = Room}) ->
- ?INFO_MSG("Creating tombstone for room ~s@~s", [Room, Host]),
- Password = integer_to_binary(misc:now_to_usec(now()) + TombstoneExpiry*1000000),
- Opts1 = gen_mod:get_module_opt(Server, mod_muc, default_room_options),
- {_, Opts2} = proplists:split(Opts1, [title, password, persistent, public]),
- Opts3 = [{title, ?TOMBSTONE_NAME}, {password, Password}, {persistent, true}, {public, false} | Opts2],
- mod_muc:create_room(
- Host,
- Room,
- jid:make(<<"tombstone">>, <<"">>),
- <<"">>,
- Opts3).
-
-check_tombstone(#state{config = Config}) ->
- case Config#config.title of
- ?TOMBSTONE_NAME ->
- case binary_to_integer(Config#config.password) < misc:now_to_usec(now()) of
- true ->
- expired;
- false ->
- locked
- end;
- _ -> not_tombstone
- end.
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Disco
@@ -3851,7 +3803,7 @@ check_tombstone(#state{config = Config}) ->
-spec make_disco_info(jid(), state()) -> disco_info().
make_disco_info(_From, StateData) ->
Config = StateData#state.config,
- Feats = [?NS_VCARD, ?NS_MUC,
+ Feats = [?NS_VCARD, ?NS_MUC, ?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
?CONFIG_OPT_TO_FEATURE((Config#config.public),
<<"muc_public">>, <<"muc_hidden">>),
?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
@@ -3897,10 +3849,11 @@ process_iq_disco_info(From, #iq{type = get, lang = Lang,
try
true = mod_caps:is_valid_node(Node),
DiscoInfo = make_disco_info(From, StateData),
- Hash = mod_caps:compute_disco_hash(DiscoInfo, sha),
- Node = <<(ejabberd_config:get_uri())/binary, $#, Hash/binary>>,
Extras = iq_disco_info_extras(Lang, StateData, true),
- {result, DiscoInfo#disco_info{node = Node, xdata = [Extras]}}
+ DiscoInfo1 = DiscoInfo#disco_info{xdata = [Extras]},
+ Hash = mod_caps:compute_disco_hash(DiscoInfo1, sha),
+ Node = <<(ejabberd_config:get_uri())/binary, $#, Hash/binary>>,
+ {result, DiscoInfo1#disco_info{node = Node}}
catch _:{badmatch, _} ->
Txt = <<"Invalid node name">>,
{error, xmpp:err_item_not_found(Txt, Lang)}
@@ -3932,7 +3885,7 @@ iq_disco_info_extras(Lang, StateData, Static) ->
end,
Fs3 = case Static of
false ->
- [{occupants, ?DICT:size(StateData#state.nicks)}|Fs2];
+ [{occupants, maps:size(StateData#state.nicks)}|Fs2];
true ->
Fs2
end,
@@ -4032,8 +3985,8 @@ process_iq_mucsub(From,
sub_els = [#muc_subscribe{nick = Nick}]} = Packet,
StateData) ->
LBareJID = jid:tolower(jid:remove_resource(From)),
- case (?DICT):find(LBareJID, StateData#state.subscribers) of
- {ok, #subscriber{nick = Nick1}} when Nick1 /= Nick ->
+ try maps:get(LBareJID, StateData#state.subscribers) of
+ #subscriber{nick = Nick1} when Nick1 /= Nick ->
Nodes = get_subscription_nodes(Packet),
case {nick_collision(From, Nick, StateData),
mod_muc:can_use_nick(StateData#state.server_host,
@@ -4049,11 +4002,11 @@ process_iq_mucsub(From,
NewStateData = set_subscriber(From, Nick, Nodes, StateData),
{result, subscribe_result(Packet), NewStateData}
end;
- {ok, #subscriber{}} ->
+ #subscriber{} ->
Nodes = get_subscription_nodes(Packet),
NewStateData = set_subscriber(From, Nick, Nodes, StateData),
- {result, subscribe_result(Packet), NewStateData};
- error ->
+ {result, subscribe_result(Packet), NewStateData}
+ catch _:{badkey, _} ->
SD2 = StateData#state{config = (StateData#state.config)#config{allow_subscription = true}},
add_new_user(From, Nick, Packet, SD2)
end;
@@ -4074,10 +4027,10 @@ process_iq_mucsub(From, #iq{type = set, lang = Lang,
process_iq_mucsub(From, #iq{type = set, sub_els = [#muc_unsubscribe{}]},
StateData) ->
LBareJID = jid:tolower(jid:remove_resource(From)),
- case ?DICT:find(LBareJID, StateData#state.subscribers) of
- {ok, #subscriber{nick = Nick}} ->
- Nicks = ?DICT:erase(Nick, StateData#state.subscriber_nicks),
- Subscribers = ?DICT:erase(LBareJID, StateData#state.subscribers),
+ try maps:get(LBareJID, StateData#state.subscribers) of
+ #subscriber{nick = Nick} ->
+ Nicks = maps:remove(Nick, StateData#state.subscriber_nicks),
+ Subscribers = maps:remove(LBareJID, StateData#state.subscribers),
NewStateData = StateData#state{subscribers = Subscribers,
subscriber_nicks = Nicks},
store_room(NewStateData, [{del_subscription, LBareJID}]),
@@ -4086,8 +4039,8 @@ process_iq_mucsub(From, #iq{type = set, sub_els = [#muc_unsubscribe{}]},
{stop, normal, _} -> stop;
{next_state, normal_state, SD} -> SD
end,
- {result, undefined, NewStateData2};
- _ ->
+ {result, undefined, NewStateData2}
+ catch _:{badkey, _} ->
{result, undefined, StateData}
end;
process_iq_mucsub(From, #iq{type = get, lang = Lang,
@@ -4096,7 +4049,7 @@ process_iq_mucsub(From, #iq{type = get, lang = Lang,
FAffiliation = get_affiliation(From, StateData),
FRole = get_role(From, StateData),
if FRole == moderator; FAffiliation == owner; FAffiliation == admin ->
- Subs = dict:fold(
+ Subs = maps:fold(
fun(_, #subscriber{jid = J, nodes = Nodes}, Acc) ->
[#muc_subscription{jid = J, events = Nodes}|Acc]
end, [], StateData#state.subscribers),
@@ -4111,8 +4064,8 @@ process_iq_mucsub(_From, #iq{type = get, lang = Lang}, _StateData) ->
remove_subscriptions(StateData) ->
if not (StateData#state.config)#config.allow_subscription ->
- StateData#state{subscribers = ?DICT:new(),
- subscriber_nicks = ?DICT:new()};
+ StateData#state{subscribers = #{},
+ subscriber_nicks = #{}};
true ->
StateData
end.
@@ -4163,12 +4116,12 @@ get_roomdesc_tail(StateData, Lang) ->
true -> <<"">>;
_ -> translate:translate(Lang, <<"private, ">>)
end,
- Len = (?DICT):size(StateData#state.nicks),
+ Len = maps:size(StateData#state.nicks),
<<" (", Desc/binary, (integer_to_binary(Len))/binary, ")">>.
-spec get_mucroom_disco_items(state()) -> disco_items().
get_mucroom_disco_items(StateData) ->
- Items = ?DICT:fold(
+ Items = maps:fold(
fun(Nick, _, Acc) ->
[#disco_item{jid = jid:make(StateData#state.room,
StateData#state.host,
@@ -4214,7 +4167,8 @@ send_voice_request(From, Lang, StateData) ->
ok | {error, stanza_error()}.
check_invitation(From, Invitations, Lang, StateData) ->
FAffiliation = get_affiliation(From, StateData),
- CanInvite = (StateData#state.config)#config.allow_user_invites orelse
+ CanInvite = ((StateData#state.config)#config.allow_user_invites
+ and not (StateData#state.config)#config.members_only) orelse
FAffiliation == admin orelse FAffiliation == owner,
case CanInvite of
true ->
@@ -4233,8 +4187,8 @@ check_invitation(From, Invitations, Lang, StateData) ->
{error, xmpp:err_not_allowed(Txt, Lang)}
end.
--spec route_invitation(jid(), muc_invite(), binary(), state()) -> jid().
-route_invitation(From, Invitation, Lang, StateData) ->
+-spec route_invitation(jid(), message(), muc_invite(), binary(), state()) -> jid().
+route_invitation(From, Pkt, Invitation, Lang, StateData) ->
#muc_invite{to = JID, reason = Reason} = Invitation,
Invite = Invitation#muc_invite{to = undefined, from = From},
Password = case (StateData#state.config)#config.password_protected of
@@ -4273,10 +4227,12 @@ route_invitation(From, Invitation, Lang, StateData) ->
type = normal,
body = xmpp:mk_text(Body),
sub_els = [XUser, XConference]},
- ejabberd_hooks:run(muc_invite, StateData#state.server_host,
- [StateData#state.jid, StateData#state.config,
- From, JID, Reason]),
- ejabberd_router:route(Msg),
+ Msg2 = ejabberd_hooks:run_fold(muc_invite,
+ StateData#state.server_host,
+ Msg,
+ [StateData#state.jid, StateData#state.config,
+ From, JID, Reason, Pkt]),
+ ejabberd_router:route(Msg2),
JID.
%% Handle a message sent to the room by a non-participant.
@@ -4363,7 +4319,7 @@ store_room(StateData, ChangesHints) ->
-spec send_subscriptions_change_notifications(jid(), binary(), subscribe|unsubscribe, state()) -> ok.
send_subscriptions_change_notifications(From, Nick, Type, State) ->
- ?DICT:fold(fun(_, #subscriber{nodes = Nodes, jid = JID}, _) ->
+ maps:fold(fun(_, #subscriber{nodes = Nodes, jid = JID}, _) ->
case lists:member(?NS_MUCSUB_NODES_SUBSCRIBERS, Nodes) of
true ->
ShowJid = case (State#state.config)#config.anonymous == false orelse
@@ -4399,23 +4355,23 @@ send_subscriptions_change_notifications(From, Nick, Type, State) ->
send_wrapped(From, To, Packet, Node, State) ->
LTo = jid:tolower(To),
LBareTo = jid:tolower(jid:remove_resource(To)),
- IsOffline = case ?DICT:find(LTo, State#state.users) of
- {ok, #user{last_presence = undefined}} -> true;
+ IsOffline = case maps:get(LTo, State#state.users, error) of
+ #user{last_presence = undefined} -> true;
error -> true;
_ -> false
end,
if IsOffline ->
- case ?DICT:find(LBareTo, State#state.subscribers) of
- {ok, #subscriber{nodes = Nodes, jid = JID}} ->
- case lists:member(Node, Nodes) of
- true ->
+ try maps:get(LBareTo, State#state.subscribers) of
+ #subscriber{nodes = Nodes, jid = JID} ->
+ case lists:member(Node, Nodes) of
+ true ->
NewPacket = wrap(From, JID, Packet, Node),
ejabberd_router:route(
xmpp:set_from_to(NewPacket, State#state.jid, JID));
- false ->
- ok
- end;
- _ ->
+ false ->
+ ok
+ end
+ catch _:{badkey, _} ->
ok
end;
true ->
@@ -4455,17 +4411,12 @@ wrap(From, To, Packet, Node) ->
id = p1_rand:get_string(),
sub_els = [El]}]}}]}.
-%% -spec send_multiple(jid(), binary(), [#user{}], stanza()) -> ok.
-%% send_multiple(From, Server, Users, Packet) ->
-%% JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)],
-%% ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet).
-
--spec send_wrapped_multiple(jid(), dict:dict(), stanza(), binary(), state()) -> ok.
+-spec send_wrapped_multiple(jid(), map(), stanza(), binary(), state()) -> ok.
send_wrapped_multiple(From, Users, Packet, Node, State) ->
- lists:foreach(
- fun({_, #user{jid = To}}) ->
+ maps:fold(
+ fun(_, #user{jid = To}, _) ->
send_wrapped(From, To, Packet, Node, State)
- end, ?DICT:to_list(Users)).
+ end, ok, Users).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Detect messange stanzas that don't have meaninful content
diff --git a/src/mod_multicast.erl b/src/mod_multicast.erl
index 2e4241424..72c177b80 100644
--- a/src/mod_multicast.erl
+++ b/src/mod_multicast.erl
@@ -34,7 +34,8 @@
-behaviour(gen_mod).
%% API
--export([start/2, stop/1, reload/3]).
+-export([start/2, stop/1, reload/3,
+ user_send_packet/1]).
%% gen_server callbacks
-export([init/1, handle_info/2, handle_call/3,
@@ -111,6 +112,40 @@ reload(LServerS, NewOpts, OldOpts) ->
Proc = gen_mod:get_module_proc(LServerS, ?MODULE),
gen_server:cast(Proc, {reload, NewOpts, OldOpts}).
+-define(SETS, gb_sets).
+
+user_send_packet({#presence{} = Packet, C2SState} = Acc) ->
+ case xmpp:get_subtag(Packet, #addresses{}) of
+ #addresses{list = Addresses} ->
+ {ToDeliver, _Delivereds} = split_addresses_todeliver(Addresses),
+ NewState =
+ lists:foldl(
+ fun(Address, St) ->
+ case Address#address.jid of
+ #jid{} = JID ->
+ LJID = jid:tolower(JID),
+ #{pres_a := PresA} = St,
+ A =
+ case Packet#presence.type of
+ available ->
+ ?SETS:add_element(LJID, PresA);
+ unavailable ->
+ ?SETS:del_element(LJID, PresA);
+ _ ->
+ PresA
+ end,
+ St#{pres_a => A};
+ undefined ->
+ St
+ end
+ end, C2SState, ToDeliver),
+ {Packet, NewState};
+ false ->
+ Acc
+ end;
+user_send_packet(Acc) ->
+ Acc.
+
%%====================================================================
%% gen_server callbacks
%%====================================================================
@@ -125,6 +160,8 @@ init([LServerS, Opts]) ->
try_start_loop(),
ejabberd_router_multicast:register_route(LServerS),
ejabberd_router:register_route(LServiceS, LServerS),
+ ejabberd_hooks:add(user_send_packet, LServerS, ?MODULE,
+ user_send_packet, 50),
{ok,
#state{lservice = LServiceS, lserver = LServerS,
access = Access, service_limits = SLimits}}.
@@ -189,6 +226,8 @@ handle_info({get_host, Pid}, State) ->
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, State) ->
+ ejabberd_hooks:delete(user_send_packet, State#state.lserver, ?MODULE,
+ user_send_packet, 50),
ejabberd_router_multicast:unregister_route(State#state.lserver),
ejabberd_router:unregister_route(State#state.lservice),
ok.
@@ -826,6 +865,9 @@ add_response(RServer, Response, State) ->
search_server_on_cache(RServer, LServerS, _LServiceS, _Maxmins)
when RServer == LServerS ->
route_single;
+search_server_on_cache(RServer, _LServerS, LServiceS, _Maxmins)
+ when RServer == LServiceS ->
+ route_single;
search_server_on_cache(RServer, _LServerS, LServiceS, Maxmins) ->
case look_server(RServer) of
not_cached ->
diff --git a/src/mod_offline.erl b/src/mod_offline.erl
index 9e8af2c16..84187eb70 100644
--- a/src/mod_offline.erl
+++ b/src/mod_offline.erl
@@ -365,7 +365,6 @@ remove_msg_by_node(To, Seq) ->
-spec need_to_store(binary(), message()) -> boolean().
need_to_store(_LServer, #message{type = error}) -> false;
-need_to_store(_LServer, #message{type = groupchat}) -> false;
need_to_store(LServer, #message{type = Type} = Packet) ->
case xmpp:has_subtag(Packet, #offline{}) of
false ->
@@ -374,17 +373,26 @@ need_to_store(LServer, #message{type = Type} = Packet) ->
true;
no_store ->
false;
- none when Type == headline ->
- false;
none ->
- case gen_mod:get_module_opt(
- LServer, ?MODULE, store_empty_body) of
- true ->
+ Store = case Type of
+ groupchat ->
+ gen_mod:get_module_opt(
+ LServer, ?MODULE, store_groupchat);
+ headline ->
+ false;
+ _ ->
+ true
+ end,
+ case {Store, gen_mod:get_module_opt(
+ LServer, ?MODULE, store_empty_body)} of
+ {false, _} ->
+ false;
+ {_, true} ->
true;
- false ->
+ {_, false} ->
Packet#message.body /= [];
- unless_chat_state ->
- not xmpp_util:is_standalone_chat_state(Packet)
+ {_, unless_chat_state} ->
+ not misc:is_standalone_chat_state(Packet)
end
end;
true ->
@@ -795,8 +803,8 @@ add_delay_info(Packet, LServer, TS) ->
_ -> TS
end,
Packet1 = xmpp:put_meta(Packet, from_offline, true),
- xmpp_util:add_delay_info(Packet1, jid:make(LServer), NewTS,
- <<"Offline storage">>).
+ misc:add_delay_info(Packet1, jid:make(LServer), NewTS,
+ <<"Offline storage">>).
-spec get_priority_from_presence(presence()) -> integer().
get_priority_from_presence(#presence{priority = Prio}) ->
@@ -837,6 +845,8 @@ import(LServer, {sql, _}, DBType, <<"spool">>,
mod_opt_type(access_max_user_messages) ->
fun acl:shaper_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
+mod_opt_type(store_groupchat) ->
+ fun(V) when is_boolean(V) -> V end;
mod_opt_type(store_empty_body) ->
fun (V) when is_boolean(V) -> V;
(unless_chat_state) -> unless_chat_state
@@ -845,4 +855,5 @@ mod_opt_type(store_empty_body) ->
mod_options(Host) ->
[{db_type, ejabberd_config:default_db(Host, ?MODULE)},
{access_max_user_messages, max_user_offline_messages},
- {store_empty_body, unless_chat_state}].
+ {store_empty_body, unless_chat_state},
+ {store_groupchat, false}].
diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl
index 0e764c269..f2cc682d6 100644
--- a/src/mod_offline_sql.erl
+++ b/src/mod_offline_sql.erl
@@ -48,7 +48,7 @@ store_message(#offline_msg{us = {LUser, LServer}} = M) ->
From = M#offline_msg.from,
To = M#offline_msg.to,
Packet = xmpp:set_from_to(M#offline_msg.packet, From, To),
- NewPacket = xmpp_util:add_delay_info(
+ NewPacket = misc:add_delay_info(
Packet, jid:make(LServer),
M#offline_msg.timestamp,
<<"Offline Storage">>),
@@ -90,8 +90,17 @@ remove_expired_messages(_LServer) ->
remove_old_messages(Days, LServer) ->
case ejabberd_sql:sql_query(
LServer,
- ?SQL("DELETE FROM spool"
- " WHERE created_at < NOW() - INTERVAL %(Days)d DAY")) of
+ fun(pgsql, _) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("DELETE FROM spool"
+ " WHERE created_at <"
+ " NOW() - INTERVAL '%(Days)d DAY'"));
+ (_, _) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("DELETE FROM spool"
+ " WHERE created_at < NOW() - INTERVAL %(Days)d DAY"))
+ end)
+ of
{updated, N} ->
?INFO_MSG("~p message(s) deleted from offline spool", [N]);
_Error ->
@@ -198,7 +207,7 @@ export(_Server) ->
try xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of
Packet ->
Packet1 = xmpp:set_from_to(Packet, From, To),
- Packet2 = xmpp_util:add_delay_info(
+ Packet2 = misc:add_delay_info(
Packet1, jid:make(LServer),
TimeStamp, <<"Offline Storage">>),
XML = fxml:element_to_binary(xmpp:encode(Packet2)),
diff --git a/src/mod_private.erl b/src/mod_private.erl
index f6a57c63c..b32fff98e 100644
--- a/src/mod_private.erl
+++ b/src/mod_private.erl
@@ -28,17 +28,21 @@
-author('alexey@process-one.net').
-protocol({xep, 49, '1.2'}).
+-protocol({xep, 411, '0.2.0'}).
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, process_sm_iq/1, import_info/0,
remove_user/2, get_data/2, get_data/3, export/1,
- import/5, import_start/2, mod_opt_type/1, set_data/3,
- mod_options/1, depends/2]).
+ import/5, import_start/2, mod_opt_type/1, set_data/2,
+ mod_options/1, depends/2, get_sm_features/5, pubsub_publish_item/6]).
+
+-export([get_commands_spec/0, bookmarks_to_pep/2]).
-include("logger.hrl").
-include("xmpp.hrl").
-include("mod_private.hrl").
+-include("ejabberd_commands.hrl").
-define(PRIVATE_CACHE, private_cache).
@@ -57,16 +61,23 @@ start(Host, Opts) ->
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
init_cache(Mod, Host, Opts),
- ejabberd_hooks:add(remove_user, Host, ?MODULE,
- remove_user, 50),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- ?NS_PRIVATE, ?MODULE, process_sm_iq).
+ ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50),
+ ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
+ ejabberd_hooks:add(pubsub_publish_item, Host, ?MODULE, pubsub_publish_item, 50),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE, ?MODULE, process_sm_iq),
+ ejabberd_commands:register_commands(get_commands_spec()).
stop(Host) ->
- ejabberd_hooks:delete(remove_user, Host, ?MODULE,
- remove_user, 50),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
- ?NS_PRIVATE).
+ ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
+ ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
+ ejabberd_hooks:delete(pubsub_publish_item, Host, ?MODULE, pubsub_publish_item, 50),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE),
+ case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
+ false ->
+ ejabberd_commands:unregister_commands(get_commands_spec());
+ true ->
+ ok
+ end.
reload(Host, NewOpts, OldOpts) ->
NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE),
@@ -78,9 +89,46 @@ reload(Host, NewOpts, OldOpts) ->
end,
init_cache(NewMod, Host, NewOpts).
+depends(_Host, _Opts) ->
+ [{mod_pubsub, soft}].
+
+mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
+mod_opt_type(O) when O == cache_life_time; O == cache_size ->
+ fun (I) when is_integer(I), I > 0 -> I;
+ (infinity) -> infinity
+ end;
+mod_opt_type(O) when O == use_cache; O == cache_missed ->
+ fun (B) when is_boolean(B) -> B end.
+
+mod_options(Host) ->
+ [{db_type, ejabberd_config:default_db(Host, ?MODULE)},
+ {use_cache, ejabberd_config:use_cache(Host)},
+ {cache_size, ejabberd_config:cache_size(Host)},
+ {cache_missed, ejabberd_config:cache_missed(Host)},
+ {cache_life_time, ejabberd_config:cache_life_time(Host)}].
+
+-spec get_sm_features({error, stanza_error()} | empty | {result, [binary()]},
+ jid(), jid(), binary(), binary()) ->
+ {error, stanza_error()} | empty | {result, [binary()]}.
+get_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
+ Acc;
+get_sm_features(Acc, _From, To, <<"">>, _Lang) ->
+ case gen_mod:is_loaded(To#jid.lserver, mod_pubsub) of
+ true ->
+ {result, [?NS_BOOKMARKS_CONVERSION_0 |
+ case Acc of
+ {result, Features} -> Features;
+ empty -> []
+ end]};
+ false ->
+ Acc
+ end;
+get_sm_features(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
-spec process_sm_iq(iq()) -> iq().
process_sm_iq(#iq{type = Type, lang = Lang,
- from = #jid{luser = LUser, lserver = LServer},
+ from = #jid{luser = LUser, lserver = LServer} = From,
to = #jid{luser = LUser, lserver = LServer},
sub_els = [#private{sub_els = Els0}]} = IQ) ->
case filter_xmlels(Els0) of
@@ -88,9 +136,11 @@ process_sm_iq(#iq{type = Type, lang = Lang,
Txt = <<"No private data found in this query">>,
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
Data when Type == set ->
- case set_data(LUser, LServer, Data) of
+ case set_data(From, Data) of
ok ->
xmpp:make_iq_result(IQ);
+ {error, #stanza_error{} = Err} ->
+ xmpp:make_error(IQ, Err);
{error, _} ->
Txt = <<"Database failure">>,
Err = xmpp:err_internal_server_error(Txt, Lang),
@@ -120,12 +170,21 @@ filter_xmlels(Els) ->
end
end, Els).
--spec set_data(binary(), binary(), [{binary(), xmlel()}]) -> ok | {error, _}.
-set_data(LUser, LServer, Data) ->
+-spec set_data(jid(), [{binary(), xmlel()}]) -> ok | {error, _}.
+set_data(JID, Data) ->
+ set_data(JID, Data, true).
+
+-spec set_data(jid(), [{binary(), xmlel()}], boolean()) -> ok | {error, _}.
+set_data(JID, Data, Publish) ->
+ {LUser, LServer, _} = jid:tolower(JID),
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:set_data(LUser, LServer, Data) of
ok ->
- delete_cache(Mod, LUser, LServer, Data);
+ delete_cache(Mod, LUser, LServer, Data),
+ case Publish of
+ true -> publish_data(JID, Data);
+ false -> ok
+ end;
{error, _} = Err ->
Err
end.
@@ -181,6 +240,87 @@ remove_user(User, Server) ->
Mod:del_data(LUser, LServer),
delete_cache(Mod, LUser, LServer, Data).
+%%%===================================================================
+%%% Pubsub
+%%%===================================================================
+-spec publish_data(jid(), [{binary(), xmlel()}]) -> ok | {error, stanza_error()}.
+publish_data(JID, Data) ->
+ {_, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
+ case gen_mod:is_loaded(LServer, mod_pubsub) of
+ true ->
+ case lists:keyfind(?NS_STORAGE_BOOKMARKS, 1, Data) of
+ false -> ok;
+ {_, El} ->
+ PubOpts = [{persist_items, true},
+ {access_model, whitelist}],
+ case mod_pubsub:publish_item(
+ LBJID, LServer, ?NS_STORAGE_BOOKMARKS, JID,
+ <<>>, [El], PubOpts, all) of
+ {result, _} -> ok;
+ {error, _} = Err -> Err
+ end
+ end;
+ false ->
+ ok
+ end.
+
+-spec pubsub_publish_item(binary(), binary(), jid(), jid(),
+ binary(), [xmlel()]) -> any().
+pubsub_publish_item(LServer, ?NS_STORAGE_BOOKMARKS,
+ #jid{luser = LUser, lserver = LServer} = From,
+ #jid{luser = LUser, lserver = LServer},
+ _ItemId, [Payload|_]) ->
+ set_data(From, [{?NS_STORAGE_BOOKMARKS, Payload}], false);
+pubsub_publish_item(_, _, _, _, _, _) ->
+ ok.
+
+%%%===================================================================
+%%% Commands
+%%%===================================================================
+-spec get_commands_spec() -> [ejabberd_commands()].
+get_commands_spec() ->
+ [#ejabberd_commands{name = bookmarks_to_pep, tags = [private],
+ desc = "Export private XML storage bookmarks to PEP",
+ module = ?MODULE, function = bookmarks_to_pep,
+ args = [{user, binary}, {server, binary}],
+ args_desc = ["Username", "Server"],
+ args_example = [<<"bob">>, <<"example.com">>],
+ result = {res, restuple},
+ result_desc = "Result tuple",
+ result_example = {ok, <<"Bookmarks exported">>}}].
+
+-spec bookmarks_to_pep(binary(), binary())
+ -> {ok, binary()} | {error, binary()}.
+bookmarks_to_pep(User, Server) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Res = case use_cache(Mod, LServer) of
+ true ->
+ ets_cache:lookup(
+ ?PRIVATE_CACHE, {LUser, LServer, ?NS_STORAGE_BOOKMARKS},
+ fun() ->
+ Mod:get_data(LUser, LServer, ?NS_STORAGE_BOOKMARKS)
+ end);
+ false ->
+ Mod:get_data(LUser, LServer, ?NS_STORAGE_BOOKMARKS)
+ end,
+ case Res of
+ {ok, El} ->
+ Data = [{?NS_STORAGE_BOOKMARKS, El}],
+ case publish_data(jid:make(User, Server), Data) of
+ ok ->
+ {ok, <<"Bookmarks exported to PEP node">>};
+ {error, Err} ->
+ {error, xmpp:format_stanza_error(Err)}
+ end;
+ _ ->
+ {error, <<"Cannot retrieve bookmarks from private XML storage">>}
+ end.
+
+%%%===================================================================
+%%% Cache
+%%%===================================================================
-spec delete_cache(module(), binary(), binary(), [{binary(), xmlel()}]) -> ok.
delete_cache(Mod, LUser, LServer, Data) ->
case use_cache(Mod, LServer) of
@@ -230,6 +370,9 @@ cache_nodes(Mod, Host) ->
false -> ejabberd_cluster:get_nodes()
end.
+%%%===================================================================
+%%% Import/Export
+%%%===================================================================
import_info() ->
[{<<"private_storage">>, 4}].
@@ -244,21 +387,3 @@ export(LServer) ->
import(LServer, {sql, _}, DBType, Tab, L) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, Tab, L).
-
-depends(_Host, _Opts) ->
- [].
-
-mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
-mod_opt_type(O) when O == cache_life_time; O == cache_size ->
- fun (I) when is_integer(I), I > 0 -> I;
- (infinity) -> infinity
- end;
-mod_opt_type(O) when O == use_cache; O == cache_missed ->
- fun (B) when is_boolean(B) -> B end.
-
-mod_options(Host) ->
- [{db_type, ejabberd_config:default_db(Host, ?MODULE)},
- {use_cache, ejabberd_config:use_cache(Host)},
- {cache_size, ejabberd_config:cache_size(Host)},
- {cache_missed, ejabberd_config:cache_missed(Host)},
- {cache_life_time, ejabberd_config:cache_life_time(Host)}].
diff --git a/src/mod_proxy65.erl b/src/mod_proxy65.erl
index cc3546cf2..c911dd7aa 100644
--- a/src/mod_proxy65.erl
+++ b/src/mod_proxy65.erl
@@ -99,15 +99,7 @@ init([Host, Opts]) ->
Service = {mod_proxy65_service,
{mod_proxy65_service, start_link, [Host, Opts]},
transient, 5000, worker, [mod_proxy65_service]},
- StreamSupervisor = {ejabberd_mod_proxy65_sup,
- {ejabberd_tmp_sup, start_link,
- [gen_mod:get_module_proc(Host,
- ejabberd_mod_proxy65_sup),
- mod_proxy65_stream]},
- transient, infinity, supervisor, [ejabberd_tmp_sup]},
- {ok,
- {{one_for_one, 10, 1},
- [StreamSupervisor, Service]}}.
+ {ok, {{one_for_one, 10, 1}, [Service]}}.
depends(_Host, _Opts) ->
[].
diff --git a/src/mod_proxy65_service.erl b/src/mod_proxy65_service.erl
index 6bef07532..9510ff4b2 100644
--- a/src/mod_proxy65_service.erl
+++ b/src/mod_proxy65_service.erl
@@ -115,13 +115,13 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%%------------------------
add_listener(Host, Opts) ->
- NewOpts = [{server_host, Host} | Opts],
- ejabberd_listener:add_listener(get_port_ip(Host),
- mod_proxy65_stream, NewOpts).
+ {_, IP, _} = EndPoint = get_endpoint(Host),
+ Opts1 = [{server_host, Host} | Opts],
+ Opts2 = lists:keystore(ip, 1, Opts1, {ip, IP}),
+ ejabberd_listener:add_listener(EndPoint, mod_proxy65_stream, Opts2).
delete_listener(Host) ->
- catch ejabberd_listener:delete_listener(get_port_ip(Host),
- mod_proxy65_stream).
+ ejabberd_listener:delete_listener(get_endpoint(Host), mod_proxy65_stream).
%%%------------------------
%%% IQ Processing
@@ -238,7 +238,7 @@ transform_module_options(Opts) ->
-spec get_streamhost(binary(), binary()) -> streamhost().
get_streamhost(Host, ServerHost) ->
- {Port, IP} = get_port_ip(ServerHost),
+ {Port, IP, _} = get_endpoint(ServerHost),
HostName0 = case gen_mod:get_module_opt(ServerHost, mod_proxy65, hostname) of
undefined -> misc:ip_to_list(IP);
Val -> Val
@@ -249,14 +249,14 @@ get_streamhost(Host, ServerHost) ->
host = HostName,
port = Port}.
--spec get_port_ip(binary()) -> {pos_integer(), inet:ip_address()}.
-get_port_ip(Host) ->
+-spec get_endpoint(binary()) -> {inet:port_number(), inet:ip_address(), tcp}.
+get_endpoint(Host) ->
Port = gen_mod:get_module_opt(Host, mod_proxy65, port),
IP = case gen_mod:get_module_opt(Host, mod_proxy65, ip) of
undefined -> get_my_ip();
Addr -> Addr
end,
- {Port, IP}.
+ {Port, IP, tcp}.
-spec get_my_ip() -> inet:ip_address().
get_my_ip() ->
diff --git a/src/mod_proxy65_stream.erl b/src/mod_proxy65_stream.erl
index 4e7aa0334..668817868 100644
--- a/src/mod_proxy65_stream.erl
+++ b/src/mod_proxy65_stream.erl
@@ -27,18 +27,19 @@
-author('xram@jabber.ru').
-behaviour(p1_fsm).
+-behaviour(ejabberd_listener).
%% gen_fsm callbacks.
-export([init/1, handle_event/3, handle_sync_event/4,
code_change/4, handle_info/3, terminate/3]).
%% gen_fsm states.
--export([wait_for_init/2, wait_for_auth/2,
+-export([accepting/2, wait_for_init/2, wait_for_auth/2,
wait_for_request/2, wait_for_activation/2,
stream_established/2]).
--export([start/2, stop/1, start_link/3, activate/2,
- relay/3, socket_type/0, listen_opt_type/1,
+-export([start/2, stop/1, start_link/2, start_link/3, activate/2,
+ relay/3, accept/1, listen_opt_type/1,
listen_options/0]).
-include("mod_proxy65.hrl").
@@ -69,10 +70,14 @@ start({gen_tcp, Socket}, Opts1) ->
fun({server_host, _}) -> true;
(_) -> false
end, Opts1),
- Supervisor = gen_mod:get_module_proc(Host,
- ejabberd_mod_proxy65_sup),
- supervisor:start_child(Supervisor,
- [Socket, Host, Opts]).
+ p1_fsm:start(?MODULE, [Socket, Host, Opts], []).
+
+start_link({gen_tcp, Socket}, Opts1) ->
+ {[{server_host, Host}], Opts} = lists:partition(
+ fun({server_host, _}) -> true;
+ (_) -> false
+ end, Opts1),
+ start_link(Socket, Host, Opts).
start_link(Socket, Host, Opts) ->
p1_fsm:start_link(?MODULE, [Socket, Host, Opts], []).
@@ -84,9 +89,8 @@ init([Socket, Host, Opts]) ->
RecvBuf = gen_mod:get_opt(recbuf, Opts),
SendBuf = gen_mod:get_opt(sndbuf, Opts),
TRef = erlang:send_after(?WAIT_TIMEOUT, self(), stop),
- inet:setopts(Socket,
- [{active, true}, {recbuf, RecvBuf}, {sndbuf, SendBuf}]),
- {ok, wait_for_init,
+ inet:setopts(Socket, [{recbuf, RecvBuf}, {sndbuf, SendBuf}]),
+ {ok, accepting,
#state{host = Host, auth_type = AuthType,
socket = Socket, shaper = Shaper, timer = TRef}}.
@@ -101,7 +105,8 @@ terminate(_Reason, StateName, #state{sha1 = SHA1}) ->
%%%------------------------------
%%% API.
%%%------------------------------
-socket_type() -> raw.
+accept(StreamPid) ->
+ p1_fsm:send_event(StreamPid, accept).
stop(StreamPid) -> StreamPid ! stop.
@@ -125,6 +130,10 @@ activate({P1, J1}, {P2, J2}) ->
%%%-----------------------
%%% States
%%%-----------------------
+accepting(accept, State) ->
+ inet:setopts(State#state.socket, [{active, true}]),
+ {next_state, wait_for_init, State}.
+
wait_for_init(Packet,
#state{socket = Socket, auth_type = AuthType} =
StateData) ->
diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl
index 2609beb86..72edea9ce 100644
--- a/src/mod_pubsub.erl
+++ b/src/mod_pubsub.erl
@@ -65,7 +65,7 @@
%% exports for console debug manual use
-export([create_node/5, create_node/7, delete_node/3,
- subscribe_node/5, unsubscribe_node/5, publish_item/6,
+ subscribe_node/5, unsubscribe_node/5, publish_item/6, publish_item/8,
delete_item/4, delete_item/5, send_items/7, get_items/2, get_item/3,
get_cached_item/2, get_configure/5, set_configure/5,
tree_action/3, node_action/4, node_call/4]).
@@ -2990,6 +2990,7 @@ send_last_pep(From, To) ->
Host = host(ServerHost),
Publisher = jid:tolower(From),
Owner = jid:remove_resource(Publisher),
+ RecipientIsOwner = jid:remove_resource(jid:tolower(To)) == Owner,
lists:foreach(
fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) ->
case match_option(Options, send_last_published_item, on_sub_and_presence) of
@@ -2998,8 +2999,11 @@ send_last_pep(From, To) ->
Subscribed = case get_option(Options, access_model) of
open -> true;
presence -> true;
- whitelist -> false; % subscribers are added manually
- authorize -> false; % likewise
+ %% TODO: Fix the 'whitelist'/'authorize'
+ %% cases. Currently, only node owners
+ %% receive last PEP notifications.
+ whitelist -> RecipientIsOwner;
+ authorize -> RecipientIsOwner;
roster ->
Grps = get_option(Options, roster_groups_allowed, []),
{OU, OS, _} = Owner,
diff --git a/src/mod_push.erl b/src/mod_push.erl
index 760f6e540..cadce92b0 100644
--- a/src/mod_push.erl
+++ b/src/mod_push.erl
@@ -635,7 +635,7 @@ make_summary(_Host, _Pkt, _Dir) ->
-spec unwrap_carbon(stanza()) -> stanza().
unwrap_carbon(#message{meta = #{carbon_copy := true}} = Msg) ->
- xmpp_util:unwrap_carbon(Msg);
+ misc:unwrap_carbon(Msg);
unwrap_carbon(Stanza) ->
Stanza.
diff --git a/src/mod_register.erl b/src/mod_register.erl
index cbc3893fd..3785c9c00 100644
--- a/src/mod_register.erl
+++ b/src/mod_register.erl
@@ -73,8 +73,14 @@ depends(_Host, _Opts) ->
[].
-spec stream_feature_register([xmpp_element()], binary()) -> [xmpp_element()].
-stream_feature_register(Acc, _Host) ->
- [#feature_register{}|Acc].
+stream_feature_register(Acc, Host) ->
+ case {gen_mod:get_module_opt(Host, ?MODULE, access),
+ gen_mod:get_module_opt(Host, ?MODULE, ip_access),
+ gen_mod:get_module_opt(Host, ?MODULE, redirect_url)} of
+ {none, _, <<>>} -> Acc;
+ {_, none, <<>>} -> Acc;
+ {_, _, _} -> [#feature_register{}|Acc]
+ end.
c2s_unauthenticated_packet(#{ip := IP, server := Server} = State,
#iq{type = T, sub_els = [_]} = IQ)
@@ -344,9 +350,6 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
{error, xmpp:err_not_allowed(Txt, Lang)};
{error, not_allowed} ->
{error, xmpp:err_not_allowed()};
- {error, too_many_users} ->
- Txt = <<"Too many users registered">>,
- {error, xmpp:err_resource_constraint(Txt, Lang)};
{error, _} ->
?ERROR_MSG("failed to register user "
"~s@~s: ~p",
diff --git a/src/mod_roster.erl b/src/mod_roster.erl
index 1f42b69e0..f97b8ea5f 100644
--- a/src/mod_roster.erl
+++ b/src/mod_roster.erl
@@ -53,14 +53,11 @@
depends/2]).
-include("logger.hrl").
-
-include("xmpp.hrl").
-
-include("mod_roster.hrl").
-
-include("ejabberd_http.hrl").
-
-include("ejabberd_web_admin.hrl").
+-include("ejabberd_stacktrace.hrl").
-define(ROSTER_CACHE, roster_cache).
-define(ROSTER_ITEM_CACHE, roster_item_cache).
@@ -231,7 +228,7 @@ roster_version(LServer, LUser) ->
case roster_version_on_db(LServer) of
true ->
case read_roster_version(LUser, LServer) of
- error -> not_found;
+ error -> undefined;
{ok, V} -> V
end;
false ->
@@ -320,10 +317,9 @@ process_iq_get(#iq{to = To, lang = Lang,
#roster_query{items = Items,
ver = Version}
end)
- catch E:R ->
- St = erlang:get_stacktrace(),
+ catch ?EX_RULE(E, R, St) ->
?ERROR_MSG("failed to process roster get for ~s: ~p",
- [jid:encode(To), {E, {R, St}}]),
+ [jid:encode(To), {E, {R, ?EX_STACK(St)}}]),
Txt = <<"Roster module has failed">>,
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end.
diff --git a/src/mod_sip.erl b/src/mod_sip.erl
index daed4fa19..814b9df6f 100644
--- a/src/mod_sip.erl
+++ b/src/mod_sip.erl
@@ -151,7 +151,7 @@ request(Req, SIPSock, TrID, Action) ->
mod_sip_proxy:route(Req, SIPSock, TrID, Pid),
{mod_sip_proxy, route, [Pid]};
Err ->
- ?INFO_MSG("failed to proxy request ~p: ~p", [Req, Err]),
+ ?WARNING_MSG("Failed to proxy request ~p: ~p", [Req, Err]),
Err
end;
{proxy_auth, LServer} ->
diff --git a/src/mod_stream_mgmt.erl b/src/mod_stream_mgmt.erl
index f897a2794..5adeaf8c2 100644
--- a/src/mod_stream_mgmt.erl
+++ b/src/mod_stream_mgmt.erl
@@ -40,6 +40,8 @@
-include("logger.hrl").
-include("p1_queue.hrl").
+-define(STREAM_MGMT_CACHE, stream_mgmt_cache).
+
-define(is_sm_packet(Pkt),
is_record(Pkt, sm_enable) or
is_record(Pkt, sm_resume) or
@@ -51,7 +53,8 @@
%%%===================================================================
%%% API
%%%===================================================================
-start(Host, _Opts) ->
+start(Host, Opts) ->
+ init_cache(Opts),
ejabberd_hooks:add(c2s_init, ?MODULE, c2s_stream_init, 50),
ejabberd_hooks:add(c2s_stream_started, Host, ?MODULE,
c2s_stream_started, 50),
@@ -94,7 +97,8 @@ stop(Host) ->
ejabberd_hooks:delete(c2s_closed, Host, ?MODULE, c2s_closed, 50),
ejabberd_hooks:delete(c2s_terminated, Host, ?MODULE, c2s_terminated, 50).
-reload(_Host, _NewOpts, _OldOpts) ->
+reload(_Host, NewOpts, _OldOpts) ->
+ init_cache(NewOpts),
?WARNING_MSG("module ~s is reloaded, but new configuration will take "
"effect for newly created client connections only", [?MODULE]).
@@ -284,23 +288,16 @@ c2s_terminated(#{mgmt_state := resumed, jid := JID} = State, _Reason) ->
[jid:encode(JID)]),
bounce_message_queue(),
{stop, State};
-c2s_terminated(#{mgmt_state := MgmtState, mgmt_stanzas_in := In, sid := SID,
- user := U, server := S, resource := R} = State, Reason) ->
- Result = case MgmtState of
- timeout ->
- Info = [{num_stanzas_in, In}],
- %% TODO: Usually, ejabberd_c2s:process_terminated/2 is
- %% called later in the hook chain. We swap the order so
- %% that the offline info won't be purged after we stored
- %% it. This should be fixed in a proper way.
- State1 = ejabberd_c2s:process_terminated(State, Reason),
- ejabberd_sm:set_offline_info(SID, U, S, R, Info),
- {stop, State1};
- _ ->
- State
- end,
+c2s_terminated(#{mgmt_state := MgmtState, mgmt_stanzas_in := In,
+ sid := {Time, _}, jid := JID} = State, _Reason) ->
+ case MgmtState of
+ timeout ->
+ store_stanzas_in(jid:tolower(JID), Time, In);
+ _ ->
+ ok
+ end,
route_unacked_stanzas(State),
- Result;
+ State;
c2s_terminated(State, _Reason) ->
State.
@@ -446,8 +443,8 @@ handle_resume(#{user := User, lserver := LServer,
[xmpp_socket:pp(Socket), jid:encode(JID)]),
{ok, State5};
{error, El, Msg} ->
- ?INFO_MSG("Cannot resume session for ~s@~s: ~s",
- [User, LServer, Msg]),
+ ?WARNING_MSG("Cannot resume session for ~s@~s: ~s",
+ [User, LServer, Msg]),
{error, send(State, El)}
end.
@@ -641,16 +638,11 @@ inherit_session_state(#{user := U, server := S,
{term, {R, Time}} ->
case ejabberd_sm:get_session_pid(U, S, R) of
none ->
- case ejabberd_sm:get_offline_info(Time, U, S, R) of
- none ->
+ case pop_stanzas_in({U, S, R}, Time) of
+ error ->
{error, <<"Previous session PID not found">>};
- Info ->
- case proplists:get_value(num_stanzas_in, Info) of
- undefined ->
- {error, <<"Previous session timed out">>};
- H ->
- {error, <<"Previous session timed out">>, H}
- end
+ {ok, H} ->
+ {error, <<"Previous session timed out">>, H}
end;
OldPID ->
OldSID = {Time, OldPID},
@@ -706,7 +698,7 @@ make_resume_id(#{sid := {Time, _}, resource := Resource}) ->
(state(), xmlel(), erlang:timestamp()) -> xmlel().
add_resent_delay_info(#{lserver := LServer}, El, Time)
when is_record(El, message); is_record(El, presence) ->
- xmpp_util:add_delay_info(El, jid:make(LServer), Time, <<"Resent">>);
+ misc:add_delay_info(El, jid:make(LServer), Time, <<"Resent">>);
add_resent_delay_info(_State, El, _Time) ->
%% TODO
El.
@@ -751,6 +743,32 @@ need_to_enqueue(State, _) ->
{false, State}.
%%%===================================================================
+%%% Cache-like storage for last handled stanzas
+%%%===================================================================
+init_cache(Opts) ->
+ ets_cache:new(?STREAM_MGMT_CACHE, cache_opts(Opts)).
+
+cache_opts(Opts) ->
+ [{max_size, gen_mod:get_opt(cache_size, Opts)},
+ {life_time, infinity}].
+
+-spec store_stanzas_in(ljid(), erlang:timestamp(), non_neg_integer()) -> boolean().
+store_stanzas_in(LJID, Time, Num) ->
+ ets_cache:insert(?STREAM_MGMT_CACHE, {LJID, Time}, Num,
+ ejabberd_cluster:get_nodes()).
+
+-spec pop_stanzas_in(ljid(), erlang:timestamp()) -> {ok, non_neg_integer()} | error.
+pop_stanzas_in(LJID, Time) ->
+ case ets_cache:lookup(?STREAM_MGMT_CACHE, {LJID, Time}) of
+ {ok, Val} ->
+ ets_cache:delete(?STREAM_MGMT_CACHE, {LJID, Time},
+ ejabberd_cluster:get_nodes()),
+ {ok, Val};
+ error ->
+ error
+ end.
+
+%%%===================================================================
%%% Configuration processing
%%%===================================================================
get_max_ack_queue(Host) ->
@@ -796,6 +814,11 @@ mod_opt_type(resend_on_timeout) ->
fun(B) when is_boolean(B) -> B;
(if_offline) -> if_offline
end;
+mod_opt_type(cache_size) ->
+ fun(I) when is_integer(I), I>0 -> I;
+ (unlimited) -> infinity;
+ (infinity) -> infinity
+ end;
mod_opt_type(queue_type) ->
fun(ram) -> ram; (file) -> file end.
@@ -804,5 +827,6 @@ mod_options(Host) ->
{resume_timeout, 300},
{max_resume_timeout, undefined},
{ack_timeout, 60},
+ {cache_size, ejabberd_config:cache_size(Host)},
{resend_on_timeout, false},
{queue_type, ejabberd_config:default_queue_type(Host)}].
diff --git a/src/node_pep.erl b/src/node_pep.erl
index 9d5f21285..e98cde81b 100644
--- a/src/node_pep.erl
+++ b/src/node_pep.erl
@@ -118,8 +118,7 @@ create_node(Nidx, Owner) ->
node_flat:create_node(Nidx, Owner).
delete_node(Nodes) ->
- {result, {_, _, Result}} = node_flat:delete_node(Nodes),
- {result, {default, Result}}.
+ node_flat:delete_node(Nodes).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
diff --git a/src/node_pep_sql.erl b/src/node_pep_sql.erl
index bf989f173..e5ceb1e8c 100644
--- a/src/node_pep_sql.erl
+++ b/src/node_pep_sql.erl
@@ -74,8 +74,7 @@ create_node(Nidx, Owner) ->
{result, {default, broadcast}}.
delete_node(Nodes) ->
- {result, {_, _, Result}} = node_flat_sql:delete_node(Nodes),
- {result, {default, Result}}.
+ node_flat_sql:delete_node(Nodes).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl
index 7da288c32..d15142358 100644
--- a/src/prosody2ejabberd.erl
+++ b/src/prosody2ejabberd.erl
@@ -60,8 +60,8 @@ from_dir(ProsodyDir) ->
Err
end;
{error, _} = Err ->
- ?INFO_MSG("The file 'luerl.beam' is not found: maybe "
- "ejabberd is not compiled with Lua support", []),
+ ?ERROR_MSG("The file 'luerl.beam' is not found: maybe "
+ "ejabberd is not compiled with Lua support", []),
Err
end.
@@ -169,8 +169,6 @@ convert_data(Host, "roster", User, [Data]) ->
end, Data),
lists:foreach(fun mod_roster:set_roster/1, Rosters);
convert_data(Host, "private", User, [Data]) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Host),
PrivData = lists:flatmap(
fun({_TagXMLNS, Raw}) ->
case deserialize(Raw) of
@@ -181,7 +179,7 @@ convert_data(Host, "private", User, [Data]) ->
[]
end
end, Data),
- mod_private:set_data(LUser, LServer, PrivData);
+ mod_private:set_data(jid:make(User, Host), PrivData);
convert_data(Host, "vcard", User, [Data]) ->
LServer = jid:nameprep(Host),
case deserialize(Data) of
@@ -191,7 +189,11 @@ convert_data(Host, "vcard", User, [Data]) ->
ok
end;
convert_data(_Host, "config", _User, [Data]) ->
- RoomJID = jid:decode(proplists:get_value(<<"jid">>, Data, <<"">>)),
+ RoomJID1 = case proplists:get_value(<<"jid">>, Data, not_found) of
+ not_found -> proplists:get_value(<<"_jid">>, Data, room_jid_not_found);
+ A when is_binary(A) -> A
+ end,
+ RoomJID = jid:decode(RoomJID1),
Config = proplists:get_value(<<"_data">>, Data, []),
RoomCfg = convert_room_config(Data),
case proplists:get_bool(<<"persistent">>, Config) of
@@ -305,22 +307,24 @@ convert_roster_item(LUser, LServer, JIDstring, LuaList) ->
InitR = #roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer},
jid = LJID},
- Roster =
- lists:foldl(
- fun({<<"groups">>, Val}, R) ->
+ lists:foldl(
+ fun({<<"groups">>, Val}, [R]) ->
Gs = lists:flatmap(
fun({G, true}) -> [G];
(_) -> []
end, Val),
- R#roster{groups = Gs};
- ({<<"subscription">>, Sub}, R) ->
- R#roster{subscription = misc:binary_to_atom(Sub)};
- ({<<"ask">>, <<"subscribe">>}, R) ->
- R#roster{ask = out};
- ({<<"name">>, Name}, R) ->
- R#roster{name = Name}
- end, InitR, LuaList),
- [Roster]
+ [R#roster{groups = Gs}];
+ ({<<"subscription">>, Sub}, [R]) ->
+ [R#roster{subscription = misc:binary_to_atom(Sub)}];
+ ({<<"ask">>, <<"subscribe">>}, [R]) ->
+ [R#roster{ask = out}];
+ ({<<"name">>, Name}, [R]) ->
+ [R#roster{name = Name}];
+ ({<<"persist">>, false}, _) ->
+ [];
+ (_, []) ->
+ []
+ end, [InitR], LuaList)
catch _:{bad_jid, _} ->
[]
end.
@@ -360,9 +364,11 @@ convert_room_config(Data) ->
end,
[{affiliations, convert_room_affiliations(Data)},
{allow_change_subj, proplists:get_bool(<<"changesubject">>, Config)},
+ {mam, proplists:get_bool(<<"archiving">>, Config)},
{description, proplists:get_value(<<"description">>, Config, <<"">>)},
{members_only, proplists:get_bool(<<"members_only">>, Config)},
{moderated, proplists:get_bool(<<"moderated">>, Config)},
+ {persistent, proplists:get_bool(<<"persistent">>, Config)},
{anonymous, Anonymous}] ++ Pass ++ Subj.
convert_privacy_item({_, Item}) ->
@@ -519,6 +525,11 @@ el_to_offline_msg(LUser, LServer, #xmlel{attrs = Attrs} = El) ->
deserialize(L) ->
deserialize(L, #xmlel{}, []).
+deserialize([{Other, _}|T], El, Acc)
+ when (Other == <<"key">>)
+ or (Other == <<"when">>)
+ or (Other == <<"with">>) ->
+ deserialize(T, El, Acc);
deserialize([{<<"attr">>, Attrs}|T], El, Acc) ->
deserialize(T, El#xmlel{attrs = Attrs ++ El#xmlel.attrs}, Acc);
deserialize([{<<"name">>, Name}|T], El, Acc) ->
diff --git a/src/proxy_protocol.erl b/src/proxy_protocol.erl
new file mode 100644
index 000000000..2103a4004
--- /dev/null
+++ b/src/proxy_protocol.erl
@@ -0,0 +1,184 @@
+%%%----------------------------------------------------------------------
+%%% File : ejabberd_http.erl
+%%% Author : Paweł Chmielowski <pawel@process-one.net>
+%%% Purpose :
+%%% Created : 27 Nov 2018 by Paweł Chmielowski <pawel@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2018 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(proxy_protocol).
+-author("pawel@process-one.net").
+
+%% API
+-export([decode/3]).
+
+decode(SockMod, Socket, Timeout) ->
+ V = SockMod:recv(Socket, 6, Timeout),
+ case V of
+ {ok, <<"PROXY ">>} ->
+ decode_v1(SockMod, Socket, Timeout);
+ {ok, <<16#0d, 16#0a, 16#0d, 16#0a, 16#00, 16#0d>>} ->
+ decode_v2(SockMod, Socket, Timeout);
+ _ ->
+ {error, eproto}
+ end.
+
+decode_v1(SockMod, Socket, Timeout) ->
+ case read_until_rn(SockMod, Socket, <<>>, false, Timeout) of
+ {error, _} = Err ->
+ Err;
+ Val ->
+ case binary:split(Val, <<" ">>, [global]) of
+ [<<"TCP4">>, SAddr, DAddr, SPort, DPort] ->
+ try {inet_parse:ipv4strict_address(binary_to_list(SAddr)),
+ inet_parse:ipv4strict_address(binary_to_list(DAddr)),
+ binary_to_integer(SPort),
+ binary_to_integer(DPort)}
+ of
+ {{ok, DA}, {ok, SA}, DP, SP} ->
+ {{SA, SP}, {DA, DP}};
+ _ ->
+ {error, eproto}
+ catch
+ error:badarg ->
+ {error, eproto}
+ end;
+ [<<"TCP6">>, SAddr, DAddr, SPort, DPort] ->
+ try {inet_parse:ipv6strict_address(binary_to_list(SAddr)),
+ inet_parse:ipv6strict_address(binary_to_list(DAddr)),
+ binary_to_integer(SPort),
+ binary_to_integer(DPort)}
+ of
+ {{ok, DA}, {ok, SA}, DP, SP} ->
+ {{SA, SP}, {DA, DP}};
+ _ ->
+ {error, eproto}
+ catch
+ error:badarg ->
+ {error, eproto}
+ end;
+ [<<"UNKNOWN">> | _] ->
+ {undefined, undefined}
+ end
+ end.
+
+decode_v2(SockMod, Socket, Timeout) ->
+ case SockMod:recv(Socket, 10, Timeout) of
+ {error, _} = Err ->
+ Err;
+ {ok, <<16#0a, 16#51, 16#55, 16#49, 16#54, 16#0a,
+ 2:4, Command:4, Transport:8, AddrLen:16/big-unsigned-integer>>} ->
+ case SockMod:recv(Socket, AddrLen, Timeout) of
+ {error, _} = Err ->
+ Err;
+ {ok, Data} ->
+ case Command of
+ 0 ->
+ case {inet:sockname(Socket), inet:peername(Socket)} of
+ {{ok, SA}, {ok, DA}} ->
+ {SA, DA};
+ {{error, _} = E, _} ->
+ E;
+ {_, {error, _} = E} ->
+ E
+ end;
+ 1 ->
+ case Transport of
+ % UNSPEC or UNIX
+ V when V == 0; V == 16#31; V == 16#32 ->
+ {{unknown, unknown}, {unknown, unknown}};
+ % IPV4 over TCP or UDP
+ V when V == 16#11; V == 16#12 ->
+ case Data of
+ <<D1:8, D2:8, D3:8, D4:8,
+ S1:8, S2:8, S3:8, S4:8,
+ DP:16/big-unsigned-integer,
+ SP:16/big-unsigned-integer,
+ _/binary>> ->
+ {{{S1, S2, S3, S4}, SP},
+ {{D1, D2, D3, D4}, DP}};
+ _ ->
+ {error, eproto}
+ end;
+ % IPV6 over TCP or UDP
+ V when V == 16#21; V == 16#22 ->
+ case Data of
+ <<D1:16/big-unsigned-integer,
+ D2:16/big-unsigned-integer,
+ D3:16/big-unsigned-integer,
+ D4:16/big-unsigned-integer,
+ D5:16/big-unsigned-integer,
+ D6:16/big-unsigned-integer,
+ D7:16/big-unsigned-integer,
+ D8:16/big-unsigned-integer,
+ S1:16/big-unsigned-integer,
+ S2:16/big-unsigned-integer,
+ S3:16/big-unsigned-integer,
+ S4:16/big-unsigned-integer,
+ S5:16/big-unsigned-integer,
+ S6:16/big-unsigned-integer,
+ S7:16/big-unsigned-integer,
+ S8:16/big-unsigned-integer,
+ DP:16/big-unsigned-integer,
+ SP:16/big-unsigned-integer,
+ _/binary>> ->
+ {{{S1, S2, S3, S4, S5, S6, S7, S8}, SP},
+ {{D1, D2, D3, D4, D5, D6, D7, D8}, DP}};
+ _ ->
+ {error, eproto}
+ end
+ end;
+ _ ->
+ {error, eproto}
+ end
+ end;
+ <<16#0a, 16#51, 16#55, 16#49, 16#54, 16#0a, _/binary>> ->
+ {error, eproto};
+ _ ->
+ {error, eproto}
+ end.
+
+read_until_rn(_SockMod, _Socket, Data, _, _) when size(Data) > 107 ->
+ {error, eproto};
+read_until_rn(SockMod, Socket, Data, true, Timeout) ->
+ case SockMod:recv(Socket, 1, Timeout) of
+ {ok, <<"\n">>} ->
+ Data;
+ {ok, <<"\r">>} ->
+ read_until_rn(SockMod, Socket, <<Data/binary, "\r">>,
+ true, Timeout);
+ {ok, Other} ->
+ read_until_rn(SockMod, Socket, <<Data/binary, "\r", Other/binary>>,
+ false, Timeout);
+ {error, _} = Err ->
+ Err
+ end;
+read_until_rn(SockMod, Socket, Data, false, Timeout) ->
+ case SockMod:recv(Socket, 2, Timeout) of
+ {ok, <<"\r\n">>} ->
+ Data;
+ {ok, <<Byte:8, "\r">>} ->
+ read_until_rn(SockMod, Socket, <<Data/binary, Byte:8>>,
+ true, Timeout);
+ {ok, Other} ->
+ read_until_rn(SockMod, Socket, <<Data/binary, Other/binary>>,
+ false, Timeout);
+ {error, _} = Err ->
+ Err
+ end.
diff --git a/src/xml_compress.erl b/src/xml_compress.erl
new file mode 100644
index 000000000..673b25c14
--- /dev/null
+++ b/src/xml_compress.erl
@@ -0,0 +1,958 @@
+-module(xml_compress).
+-export([encode/3, decode/3]).
+
+% This file was generated by xml_compress_gen
+%
+% Rules used:
+%
+% [{<<"eu.siacs.conversations.axolotl">>,<<"key">>,
+% [{<<"prekey">>,[<<"true">>]},{<<"rid">>,[]}],
+% []},
+% {<<"jabber:client">>,<<"message">>,
+% [{<<"from">>,[j2,{j1}]},
+% {<<"id">>,[]},
+% {<<"to">>,[j1,j2,{j1}]},
+% {<<"type">>,[<<"chat">>,<<"groupchat">>,<<"normal">>]},
+% {<<"xml:lang">>,[<<"en">>]}],
+% []},
+% {<<"urn:xmpp:hints">>,<<"store">>,[],[]},
+% {<<"jabber:client">>,<<"body">>,[],
+% [<<73,32,115,101,110,116,32,121,111,117,32,97,110,32,79,77,69,77,79,32,101,
+% 110,99,114,121,112,116,101,100,32,109,101,115,115,97,103,101,32,98,117,
+% 116,32,121,111,117,114,32,99,108,105,101,110,116,32,100,111,101,115,110,
+% 226,128,153,116,32,115,101,101,109,32,116,111,32,115,117,112,112,111,
+% 114,116,32,116,104,97,116,46,32,70,105,110,100,32,109,111,114,101,32,
+% 105,110,102,111,114,109,97,116,105,111,110,32,111,110,32,104,116,116,
+% 112,115,58,47,47,99,111,110,118,101,114,115,97,116,105,111,110,115,46,
+% 105,109,47,111,109,101,109,111>>]},
+% {<<"urn:xmpp:sid:0">>,<<"origin-id">>,[{<<"id">>,[]}],[]},
+% {<<"urn:xmpp:chat-markers:0">>,<<"markable">>,[],[]},
+% {<<"eu.siacs.conversations.axolotl">>,<<"encrypted">>,[],[]},
+% {<<"eu.siacs.conversations.axolotl">>,<<"header">>,[{<<"sid">>,[]}],[]},
+% {<<"eu.siacs.conversations.axolotl">>,<<"iv">>,[],[]},
+% {<<"eu.siacs.conversations.axolotl">>,<<"payload">>,[],[]},
+% {<<"urn:xmpp:eme:0">>,<<"encryption">>,
+% [{<<"name">>,[<<"OMEMO">>]},
+% {<<"namespace">>,[<<"eu.siacs.conversations.axolotl">>]}],
+% []},
+% {<<"urn:xmpp:delay">>,<<"delay">>,[{<<"from">>,[j1]},{<<"stamp">>,[]}],[]},
+% {<<"http://jabber.org/protocol/address">>,<<"address">>,
+% [{<<"jid">>,[{j1}]},{<<"type">>,[<<"ofrom">>]}],
+% []},
+% {<<"http://jabber.org/protocol/address">>,<<"addresses">>,[],[]},
+% {<<"urn:xmpp:chat-markers:0">>,<<"displayed">>,
+% [{<<"id">>,[]},{<<"sender">>,[{j1},{j2}]}],
+% []},
+% {<<"urn:xmpp:mam:tmp">>,<<"archived">>,[{<<"by">>,[]},{<<"id">>,[]}],[]},
+% {<<"urn:xmpp:sid:0">>,<<"stanza-id">>,[{<<"by">>,[]},{<<"id">>,[]}],[]},
+% {<<"urn:xmpp:receipts">>,<<"request">>,[],[]},
+% {<<"urn:xmpp:chat-markers:0">>,<<"received">>,[{<<"id">>,[]}],[]},
+% {<<"urn:xmpp:receipts">>,<<"received">>,[{<<"id">>,[]}],[]},
+% {<<"http://jabber.org/protocol/chatstates">>,<<"active">>,[],[]},
+% {<<"http://jabber.org/protocol/muc#user">>,<<"invite">>,
+% [{<<"from">>,[{j1}]}],
+% []},
+% {<<"http://jabber.org/protocol/muc#user">>,<<"reason">>,[],[]},
+% {<<"http://jabber.org/protocol/muc#user">>,<<"x">>,[],[]},
+% {<<"jabber:x:conference">>,<<"x">>,[{<<"jid">>,[j2]}],[]},
+% {<<"jabber:client">>,<<"subject">>,[],[]},
+% {<<"jabber:client">>,<<"thread">>,[],[]},
+% {<<"http://jabber.org/protocol/pubsub#event">>,<<"event">>,[],[]},
+% {<<"http://jabber.org/protocol/pubsub#event">>,<<"item">>,[{<<"id">>,[]}],[]},
+% {<<"http://jabber.org/protocol/pubsub#event">>,<<"items">>,
+% [{<<"node">>,[<<"urn:xmpp:mucsub:nodes:messages">>]}],
+% []},
+% {<<"p1:push:custom">>,<<"x">>,[{<<"key">>,[]},{<<"value">>,[]}],[]},
+% {<<"p1:pushed">>,<<"x">>,[],[]},
+% {<<"urn:xmpp:message-correct:0">>,<<"replace">>,[{<<"id">>,[]}],[]},
+% {<<"http://jabber.org/protocol/chatstates">>,<<"composing">>,[],[]}]
+
+encode(El, J1, J2) ->
+ encode_child(El, <<"jabber:client">>,
+ J1, J2, byte_size(J1), byte_size(J2), <<1:8>>).
+
+encode_attr({<<"xmlns">>, _}, Acc) ->
+ Acc;
+encode_attr({N, V}, Acc) ->
+ <<Acc/binary, 1:8, (encode_string(N))/binary,
+ (encode_string(V))/binary>>.
+
+encode_attrs(Attrs, Acc) ->
+ lists:foldl(fun encode_attr/2, Acc, Attrs).
+
+encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ E1 = if
+ PNs == Ns -> encode_attrs(Attrs, <<Pfx/binary, 2:8, (encode_string(Name))/binary>>);
+ true -> encode_attrs(Attrs, <<Pfx/binary, 3:8, (encode_string(Ns))/binary, (encode_string(Name))/binary>>)
+ end,
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E1/binary, 2:8>>),
+ <<E2/binary, 4:8>>.
+
+encode_child({xmlel, Name, Attrs, Children}, PNs, J1, J2, J1L, J2L, Pfx) ->
+ case lists:keyfind(<<"xmlns">>, 1, Attrs) of
+ false ->
+ encode(PNs, PNs, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx);
+ {_, Ns} ->
+ encode(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+ end;
+encode_child({xmlcdata, Data}, _PNs, _J1, _J2, _J1L, _J2L, Pfx) ->
+ <<Pfx/binary, 1:8, (encode_string(Data))/binary>>.
+
+encode_children(Children, PNs, J1, J2, J1L, J2L, Pfx) ->
+ lists:foldl(
+ fun(Child, Acc) ->
+ encode_child(Child, PNs, J1, J2, J1L, J2L, Acc)
+ end, Pfx, Children).
+
+encode_string(Data) ->
+ <<V1:4, V2:6, V3:6>> = <<(byte_size(Data)):16/unsigned-big-integer>>,
+ case {V1, V2, V3} of
+ {0, 0, V3} ->
+ <<V3:8, Data/binary>>;
+ {0, V2, V3} ->
+ <<(V3 bor 64):8, V2:8, Data/binary>>;
+ _ ->
+ <<(V3 bor 64):8, (V2 bor 64):8, V1:8, Data/binary>>
+ end.
+
+encode(PNs, <<"eu.siacs.conversations.axolotl">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"key">> ->
+ E = lists:foldl(fun
+ ({<<"prekey">>, AVal}, Acc) ->
+ case AVal of
+ <<"true">> -> <<Acc/binary, 3:8>>;
+ _ -> <<Acc/binary, 4:8, (encode_string(AVal))/binary>>
+ end;
+ ({<<"rid">>, AVal}, Acc) ->
+ <<Acc/binary, 5:8, (encode_string(AVal))/binary>>;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 5:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"encrypted">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 12:8>>),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"header">> ->
+ E = lists:foldl(fun
+ ({<<"sid">>, AVal}, Acc) ->
+ <<Acc/binary, 3:8, (encode_string(AVal))/binary>>;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 13:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"iv">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 14:8>>),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"payload">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 15:8>>),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"jabber:client">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"message">> ->
+ E = lists:foldl(fun
+ ({<<"from">>, AVal}, Acc) ->
+ case AVal of
+ J2 -> <<Acc/binary, 3:8>>;
+ <<J1:J1L/binary, Rest/binary>> -> <<Acc/binary, 4:8, (encode_string(Rest))/binary>>;
+ _ -> <<Acc/binary, 5:8, (encode_string(AVal))/binary>>
+ end;
+ ({<<"id">>, AVal}, Acc) ->
+ <<Acc/binary, 6:8, (encode_string(AVal))/binary>>;
+ ({<<"to">>, AVal}, Acc) ->
+ case AVal of
+ J1 -> <<Acc/binary, 7:8>>;
+ J2 -> <<Acc/binary, 8:8>>;
+ <<J1:J1L/binary, Rest/binary>> -> <<Acc/binary, 9:8, (encode_string(Rest))/binary>>;
+ _ -> <<Acc/binary, 10:8, (encode_string(AVal))/binary>>
+ end;
+ ({<<"type">>, AVal}, Acc) ->
+ case AVal of
+ <<"chat">> -> <<Acc/binary, 11:8>>;
+ <<"groupchat">> -> <<Acc/binary, 12:8>>;
+ <<"normal">> -> <<Acc/binary, 13:8>>;
+ _ -> <<Acc/binary, 14:8, (encode_string(AVal))/binary>>
+ end;
+ ({<<"xml:lang">>, AVal}, Acc) ->
+ case AVal of
+ <<"en">> -> <<Acc/binary, 15:8>>;
+ _ -> <<Acc/binary, 16:8, (encode_string(AVal))/binary>>
+ end;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 6:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"body">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 8:8>>),
+ E2 = lists:foldl(fun
+ ({xmlcdata, <<73,32,115,101,110,116,32,121,111,117,32,97,110,32,79,77,69,
+ 77,79,32,101,110,99,114,121,112,116,101,100,32,109,101,115,
+ 115,97,103,101,32,98,117,116,32,121,111,117,114,32,99,108,
+ 105,101,110,116,32,100,111,101,115,110,226,128,153,116,32,
+ 115,101,101,109,32,116,111,32,115,117,112,112,111,114,116,32,
+ 116,104,97,116,46,32,70,105,110,100,32,109,111,114,101,32,
+ 105,110,102,111,114,109,97,116,105,111,110,32,111,110,32,104,
+ 116,116,112,115,58,47,47,99,111,110,118,101,114,115,97,116,
+ 105,111,110,115,46,105,109,47,111,109,101,109,111>>}, Acc) -> <<Acc/binary, 9:8>>;
+ (El, Acc) -> encode_child(El, Ns, J1, J2, J1L, J2L, Acc)
+ end, <<E/binary, 2:8>>, Children),
+ <<E2/binary, 4:8>>;
+ <<"subject">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 31:8>>),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"thread">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 32:8>>),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"urn:xmpp:hints">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"store">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 7:8>>),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"urn:xmpp:sid:0">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"origin-id">> ->
+ E = lists:foldl(fun
+ ({<<"id">>, AVal}, Acc) ->
+ <<Acc/binary, 3:8, (encode_string(AVal))/binary>>;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 10:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"stanza-id">> ->
+ E = lists:foldl(fun
+ ({<<"by">>, AVal}, Acc) ->
+ <<Acc/binary, 3:8, (encode_string(AVal))/binary>>;
+ ({<<"id">>, AVal}, Acc) ->
+ <<Acc/binary, 4:8, (encode_string(AVal))/binary>>;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 22:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"urn:xmpp:chat-markers:0">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"markable">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 11:8>>),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"displayed">> ->
+ E = lists:foldl(fun
+ ({<<"id">>, AVal}, Acc) ->
+ <<Acc/binary, 3:8, (encode_string(AVal))/binary>>;
+ ({<<"sender">>, AVal}, Acc) ->
+ case AVal of
+ <<J1:J1L/binary, Rest/binary>> -> <<Acc/binary, 4:8, (encode_string(Rest))/binary>>;
+ <<J2:J2L/binary, Rest/binary>> -> <<Acc/binary, 5:8, (encode_string(Rest))/binary>>;
+ _ -> <<Acc/binary, 6:8, (encode_string(AVal))/binary>>
+ end;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 20:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"received">> ->
+ E = lists:foldl(fun
+ ({<<"id">>, AVal}, Acc) ->
+ <<Acc/binary, 3:8, (encode_string(AVal))/binary>>;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 24:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"urn:xmpp:eme:0">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"encryption">> ->
+ E = lists:foldl(fun
+ ({<<"name">>, AVal}, Acc) ->
+ case AVal of
+ <<"OMEMO">> -> <<Acc/binary, 3:8>>;
+ _ -> <<Acc/binary, 4:8, (encode_string(AVal))/binary>>
+ end;
+ ({<<"namespace">>, AVal}, Acc) ->
+ case AVal of
+ <<"eu.siacs.conversations.axolotl">> -> <<Acc/binary, 5:8>>;
+ _ -> <<Acc/binary, 6:8, (encode_string(AVal))/binary>>
+ end;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 16:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"urn:xmpp:delay">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"delay">> ->
+ E = lists:foldl(fun
+ ({<<"from">>, AVal}, Acc) ->
+ case AVal of
+ J1 -> <<Acc/binary, 3:8>>;
+ _ -> <<Acc/binary, 4:8, (encode_string(AVal))/binary>>
+ end;
+ ({<<"stamp">>, AVal}, Acc) ->
+ <<Acc/binary, 5:8, (encode_string(AVal))/binary>>;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 17:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"http://jabber.org/protocol/address">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"address">> ->
+ E = lists:foldl(fun
+ ({<<"jid">>, AVal}, Acc) ->
+ case AVal of
+ <<J1:J1L/binary, Rest/binary>> -> <<Acc/binary, 3:8, (encode_string(Rest))/binary>>;
+ _ -> <<Acc/binary, 4:8, (encode_string(AVal))/binary>>
+ end;
+ ({<<"type">>, AVal}, Acc) ->
+ case AVal of
+ <<"ofrom">> -> <<Acc/binary, 5:8>>;
+ _ -> <<Acc/binary, 6:8, (encode_string(AVal))/binary>>
+ end;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 18:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"addresses">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 19:8>>),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"urn:xmpp:mam:tmp">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"archived">> ->
+ E = lists:foldl(fun
+ ({<<"by">>, AVal}, Acc) ->
+ <<Acc/binary, 3:8, (encode_string(AVal))/binary>>;
+ ({<<"id">>, AVal}, Acc) ->
+ <<Acc/binary, 4:8, (encode_string(AVal))/binary>>;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 21:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"urn:xmpp:receipts">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"request">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 23:8>>),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"received">> ->
+ E = lists:foldl(fun
+ ({<<"id">>, AVal}, Acc) ->
+ <<Acc/binary, 3:8, (encode_string(AVal))/binary>>;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 25:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"http://jabber.org/protocol/chatstates">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"active">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 26:8>>),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"composing">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 39:8>>),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"http://jabber.org/protocol/muc#user">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"invite">> ->
+ E = lists:foldl(fun
+ ({<<"from">>, AVal}, Acc) ->
+ case AVal of
+ <<J1:J1L/binary, Rest/binary>> -> <<Acc/binary, 3:8, (encode_string(Rest))/binary>>;
+ _ -> <<Acc/binary, 4:8, (encode_string(AVal))/binary>>
+ end;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 27:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"reason">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 28:8>>),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"x">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 29:8>>),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"jabber:x:conference">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"x">> ->
+ E = lists:foldl(fun
+ ({<<"jid">>, AVal}, Acc) ->
+ case AVal of
+ J2 -> <<Acc/binary, 3:8>>;
+ _ -> <<Acc/binary, 4:8, (encode_string(AVal))/binary>>
+ end;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 30:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"http://jabber.org/protocol/pubsub#event">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"event">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 33:8>>),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"item">> ->
+ E = lists:foldl(fun
+ ({<<"id">>, AVal}, Acc) ->
+ <<Acc/binary, 3:8, (encode_string(AVal))/binary>>;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 34:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ <<"items">> ->
+ E = lists:foldl(fun
+ ({<<"node">>, AVal}, Acc) ->
+ case AVal of
+ <<"urn:xmpp:mucsub:nodes:messages">> -> <<Acc/binary, 3:8>>;
+ _ -> <<Acc/binary, 4:8, (encode_string(AVal))/binary>>
+ end;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 35:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"p1:push:custom">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"x">> ->
+ E = lists:foldl(fun
+ ({<<"key">>, AVal}, Acc) ->
+ <<Acc/binary, 3:8, (encode_string(AVal))/binary>>;
+ ({<<"value">>, AVal}, Acc) ->
+ <<Acc/binary, 4:8, (encode_string(AVal))/binary>>;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 36:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"p1:pushed">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"x">> ->
+ E = encode_attrs(Attrs, <<Pfx/binary, 37:8>>),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, <<"urn:xmpp:message-correct:0">> = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ case Name of
+ <<"replace">> ->
+ E = lists:foldl(fun
+ ({<<"id">>, AVal}, Acc) ->
+ <<Acc/binary, 3:8, (encode_string(AVal))/binary>>;
+ (Attr, Acc) -> encode_attr(Attr, Acc)
+ end, <<Pfx/binary, 38:8>>, Attrs),
+ E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E/binary, 2:8>>),
+ <<E2/binary, 4:8>>;
+ _ -> encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)
+end;
+encode(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->
+ encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx).
+
+decode(<<$<, _/binary>> = Data, _J1, _J2) ->
+ fxml_stream:parse_element(Data);
+decode(<<1:8, Rest/binary>>, J1, J2) ->
+ {El, _} = decode(Rest, <<"jabber:client">>, J1, J2),
+ El.
+
+decode_string(Data) ->
+ case Data of
+ <<0:2, L:6, Str:L/binary, Rest/binary>> ->
+ {Str, Rest};
+ <<1:2, L1:6, 0:2, L2:6, Rest/binary>> ->
+ L = L2*64 + L1,
+ <<Str:L/binary, Rest2/binary>> = Rest,
+ {Str, Rest2};
+ <<1:2, L1:6, 1:2, L2:6, L3:8, Rest/binary>> ->
+ L = (L3*64 + L2)*64 + L1,
+ <<Str:L/binary, Rest2/binary>> = Rest,
+ {Str, Rest2}
+ end.
+
+decode_child(<<1:8, Rest/binary>>, _PNs, _J1, _J2) ->
+ {Text, Rest2} = decode_string(Rest),
+ {{xmlcdata, Text}, Rest2};
+decode_child(<<2:8, Rest/binary>>, PNs, J1, J2) ->
+ {Name, Rest2} = decode_string(Rest),
+ {Attrs, Rest3} = decode_attrs(Rest2),
+ {Children, Rest4} = decode_children(Rest3, PNs, J1, J2),
+ {{xmlel, Name, Attrs, Children}, Rest4};
+decode_child(<<3:8, Rest/binary>>, PNs, J1, J2) ->
+ {Name, Rest2} = decode_string(Rest),
+ {Ns, Rest3} = decode_string(Rest2),
+ {Attrs, Rest4} = decode_attrs(Rest3),
+ {Children, Rest5} = decode_children(Rest4, Ns, J1, J2),
+ {{xmlel, Name, add_ns(PNs, Ns, Attrs), Children}, Rest5};
+decode_child(<<4:8, Rest/binary>>, _PNs, _J1, _J2) ->
+ {stop, Rest};
+decode_child(Other, PNs, J1, J2) ->
+ decode(Other, PNs, J1, J2).
+
+decode_children(Data, PNs, J1, J2) ->
+ prefix_map(fun(Data2) -> decode(Data2, PNs, J1, J2) end, Data).
+
+decode_attr(<<1:8, Rest/binary>>) ->
+ {Name, Rest2} = decode_string(Rest),
+ {Val, Rest3} = decode_string(Rest2),
+ {{Name, Val}, Rest3};
+decode_attr(<<2:8, Rest/binary>>) ->
+ {stop, Rest}.
+
+decode_attrs(Data) ->
+ prefix_map(fun decode_attr/1, Data).
+
+prefix_map(F, Data) ->
+ prefix_map(F, Data, []).
+
+prefix_map(F, Data, Acc) ->
+ case F(Data) of
+ {stop, Rest} ->
+ {lists:reverse(Acc), Rest};
+ {Val, Rest} ->
+ prefix_map(F, Rest, [Val | Acc])
+ end.
+
+add_ns(Ns, Ns, Attrs) ->
+ Attrs;
+add_ns(_, Ns, Attrs) ->
+ [{<<"xmlns">>, Ns} | Attrs].
+
+decode(<<5:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"eu.siacs.conversations.axolotl">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {{<<"prekey">>, <<"true">>}, Rest3};
+ (<<4:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"prekey">>, AVal}, Rest4};
+ (<<5:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"rid">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"key">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<12:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"eu.siacs.conversations.axolotl">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"encrypted">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<13:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"eu.siacs.conversations.axolotl">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"sid">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"header">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<14:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"eu.siacs.conversations.axolotl">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"iv">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<15:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"eu.siacs.conversations.axolotl">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"payload">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<6:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"jabber:client">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {{<<"from">>, J2}, Rest3};
+ (<<4:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"from">>, <<J1/binary, AVal/binary>>}, Rest4};
+ (<<5:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"from">>, AVal}, Rest4};
+ (<<6:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"id">>, AVal}, Rest4};
+ (<<7:8, Rest3/binary>>) ->
+ {{<<"to">>, J1}, Rest3};
+ (<<8:8, Rest3/binary>>) ->
+ {{<<"to">>, J2}, Rest3};
+ (<<9:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"to">>, <<J1/binary, AVal/binary>>}, Rest4};
+ (<<10:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"to">>, AVal}, Rest4};
+ (<<11:8, Rest3/binary>>) ->
+ {{<<"type">>, <<"chat">>}, Rest3};
+ (<<12:8, Rest3/binary>>) ->
+ {{<<"type">>, <<"groupchat">>}, Rest3};
+ (<<13:8, Rest3/binary>>) ->
+ {{<<"type">>, <<"normal">>}, Rest3};
+ (<<14:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"type">>, AVal}, Rest4};
+ (<<15:8, Rest3/binary>>) ->
+ {{<<"xml:lang">>, <<"en">>}, Rest3};
+ (<<16:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"xml:lang">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"message">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<8:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"jabber:client">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = prefix_map(fun (<<9:8, Rest5/binary>>) ->
+ {{xmlcdata, <<73,32,115,101,110,116,32,121,111,117,32,97,110,32,79,77,69,
+ 77,79,32,101,110,99,114,121,112,116,101,100,32,109,101,115,
+ 115,97,103,101,32,98,117,116,32,121,111,117,114,32,99,108,
+ 105,101,110,116,32,100,111,101,115,110,226,128,153,116,32,
+ 115,101,101,109,32,116,111,32,115,117,112,112,111,114,116,
+ 32,116,104,97,116,46,32,70,105,110,100,32,109,111,114,101,
+ 32,105,110,102,111,114,109,97,116,105,111,110,32,111,110,
+ 32,104,116,116,112,115,58,47,47,99,111,110,118,101,114,115,
+ 97,116,105,111,110,115,46,105,109,47,111,109,101,109,111>>}, Rest5};
+ (Other) ->
+ decode_child(Other, Ns, J1, J2)
+ end, Rest2),
+ {{xmlel, <<"body">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<31:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"jabber:client">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"subject">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<32:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"jabber:client">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"thread">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<7:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"urn:xmpp:hints">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"store">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<10:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"urn:xmpp:sid:0">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"id">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"origin-id">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<22:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"urn:xmpp:sid:0">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"by">>, AVal}, Rest4};
+ (<<4:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"id">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"stanza-id">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<11:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"urn:xmpp:chat-markers:0">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"markable">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<20:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"urn:xmpp:chat-markers:0">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"id">>, AVal}, Rest4};
+ (<<4:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"sender">>, <<J1/binary, AVal/binary>>}, Rest4};
+ (<<5:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"sender">>, <<J2/binary, AVal/binary>>}, Rest4};
+ (<<6:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"sender">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"displayed">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<24:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"urn:xmpp:chat-markers:0">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"id">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"received">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<16:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"urn:xmpp:eme:0">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {{<<"name">>, <<"OMEMO">>}, Rest3};
+ (<<4:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"name">>, AVal}, Rest4};
+ (<<5:8, Rest3/binary>>) ->
+ {{<<"namespace">>, <<"eu.siacs.conversations.axolotl">>}, Rest3};
+ (<<6:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"namespace">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"encryption">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<17:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"urn:xmpp:delay">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {{<<"from">>, J1}, Rest3};
+ (<<4:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"from">>, AVal}, Rest4};
+ (<<5:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"stamp">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"delay">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<18:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"http://jabber.org/protocol/address">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"jid">>, <<J1/binary, AVal/binary>>}, Rest4};
+ (<<4:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"jid">>, AVal}, Rest4};
+ (<<5:8, Rest3/binary>>) ->
+ {{<<"type">>, <<"ofrom">>}, Rest3};
+ (<<6:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"type">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"address">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<19:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"http://jabber.org/protocol/address">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"addresses">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<21:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"urn:xmpp:mam:tmp">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"by">>, AVal}, Rest4};
+ (<<4:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"id">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"archived">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<23:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"urn:xmpp:receipts">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"request">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<25:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"urn:xmpp:receipts">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"id">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"received">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<26:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"http://jabber.org/protocol/chatstates">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"active">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<39:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"http://jabber.org/protocol/chatstates">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"composing">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<27:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"http://jabber.org/protocol/muc#user">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"from">>, <<J1/binary, AVal/binary>>}, Rest4};
+ (<<4:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"from">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"invite">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<28:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"http://jabber.org/protocol/muc#user">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"reason">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<29:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"http://jabber.org/protocol/muc#user">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"x">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<30:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"jabber:x:conference">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {{<<"jid">>, J2}, Rest3};
+ (<<4:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"jid">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"x">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<33:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"http://jabber.org/protocol/pubsub#event">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"event">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<34:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"http://jabber.org/protocol/pubsub#event">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"id">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"item">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<35:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"http://jabber.org/protocol/pubsub#event">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {{<<"node">>, <<"urn:xmpp:mucsub:nodes:messages">>}, Rest3};
+ (<<4:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"node">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"items">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<36:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"p1:push:custom">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"key">>, AVal}, Rest4};
+ (<<4:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"value">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"x">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<37:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"p1:pushed">>,
+ {Attrs, Rest2} = decode_attrs(Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"x">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(<<38:8, Rest/binary>>, PNs, J1, J2) ->
+ Ns = <<"urn:xmpp:message-correct:0">>,
+ {Attrs, Rest2} = prefix_map(fun
+ (<<3:8, Rest3/binary>>) ->
+ {AVal, Rest4} = decode_string(Rest3),
+ {{<<"id">>, AVal}, Rest4};
+ (<<2:8, Rest3/binary>>) ->
+ {stop, Rest3};
+ (Data) ->
+ decode_attr(Data)
+ end, Rest),
+ {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),
+ {{xmlel, <<"replace">>, add_ns(PNs, Ns, Attrs), Children}, Rest6};
+decode(Other, PNs, J1, J2) ->
+ decode_child(Other, PNs, J1, J2).
+
diff --git a/test/acl_test.exs b/test/acl_test.exs
deleted file mode 100644
index ee2b37ab9..000000000
--- a/test/acl_test.exs
+++ /dev/null
@@ -1,389 +0,0 @@
-# ----------------------------------------------------------------------
-#
-# ejabberd, Copyright (C) 2002-2017 ProcessOne
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 2 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# ----------------------------------------------------------------------
-
-defmodule ACLTest do
- @author "mremond@process-one.net"
-
- use ExUnit.Case, async: false
-
- setup_all do
- :ok = :mnesia.start
- :ejabberd_mnesia.start
- :jid.start
- :ejabberd_hooks.start_link
- :stringprep.start
- :ok = :ejabberd_config.start(["domain1", "domain2"], [])
- {:ok, _} = :acl.start_link
- :ok
- end
-
- setup do
- :acl.clear
- end
-
- test "access rule match with user part ACL" do
- :acl.add(:global, :basic_acl_1, {:user, "test1"})
- :acl.add(:global, :basic_acl_1, {:user, "test2"})
- :acl.add_access(:global, :basic_rule_1, [{:allow, [{:acl, :basic_acl_1}]}])
- # JID can only be passes as jid record.
- # => TODO: Support passing JID as binary.
- assert :acl.match_rule(:global, :basic_rule_1, :jid.from_string("test1@domain1")) == :allow
- assert :acl.match_rule(:global, :basic_rule_1, :jid.from_string("test1@domain2")) == :allow
- assert :acl.match_rule(:global, :basic_rule_1, :jid.from_string("test2@domain1")) == :allow
- assert :acl.match_rule(:global, :basic_rule_1, :jid.from_string("test2@domain2")) == :allow
- # We match on user part only for local domain. As an implicit rule remote domain are not matched
- assert :acl.match_rule(:global, :basic_rule_1, :jid.from_string("test1@otherdomain")) == :deny
- assert :acl.match_rule(:global, :basic_rule_1, :jid.from_string("test2@otherdomain")) == :deny
- assert :acl.match_rule(:global, :basic_rule_1, :jid.from_string("test11@domain1")) == :deny
-
- :acl.add(:global, :basic_acl_2, {:user, {"test2", "domain1"}})
- :acl.add_access(:global, :basic_rule_2, [{:allow, [{:acl, :basic_acl_2}]}])
- assert :acl.match_rule(:global, :basic_rule_2, :jid.from_string("test2@domain1")) == :allow
- assert :acl.match_rule(:global, :basic_rule_2, :jid.from_string("test2@domain2")) == :deny
- assert :acl.match_rule(:global, :basic_rule_2, :jid.from_string("test2@otherdomain")) == :deny
- assert :acl.match_rule(:global, :basic_rule_2, {127,0,0,1}) == :deny
- end
-
- test "IP based ACL" do
- :acl.add(:global, :ip_acl_1, {:ip, "127.0.0.0/24"})
- :acl.add_access(:global, :ip_rule_1, [{:allow, [{:acl, :ip_acl_1}]}])
- # IP must be expressed as a tuple when calling match rule
- assert :acl.match_rule(:global, :ip_rule_1, {127,0,0,1}) == :allow
- assert :acl.match_rule(:global, :ip_rule_1, {127,0,1,1}) == :deny
- assert :acl.match_rule(:global, :ip_rule_1, :jid.from_string("test1@domain1")) == :deny
- end
-
- test "Access rule are evaluated sequentially" do
- :acl.add(:global, :user_acl_1, {:user, {"test1", "domain2"}})
- :acl.add(:global, :user_acl_2, {:user, "test1"})
- :acl.add_access(:global, :user_rule_1, [{:deny, [{:acl, :user_acl_1}]}, {:allow, [{:acl, :user_acl_2}]}])
- assert :acl.match_rule(:global, :user_rule_1, :jid.from_string("test1@domain1")) == :allow
- assert :acl.match_rule(:global, :user_rule_1, :jid.from_string("test1@domain2")) == :deny
- end
-
- # Access rules are sometimes used to provide values (i.e.: max_s2s_connections, max_user_sessions)
- test "Access rules providing values" do
- :acl.add(:global, :user_acl, {:user_regexp, ""})
- :acl.add(:global, :admin_acl, {:user, "admin"})
- :acl.add_access(:global, :value_rule_1, [{10, [{:acl, :admin_acl}]}, {5, [{:acl, :user_acl}]}])
- assert :acl.match_rule(:global, :value_rule_1, :jid.from_string("test1@domain1")) == 5
- assert :acl.match_rule(:global, :value_rule_1, :jid.from_string("admin@domain1")) == 10
-
- # If we have no match, :deny is still the default value
- # => TODO maybe we should have a match rule which allow passing custom default value ?
- assert :acl.match_rule(:global, :value_rule_1, :jid.from_string("user@otherdomain")) == :deny
- end
-
-
- # At the moment IP and user rules to no go well together: There is
- # no way to combine IP and user restrictions.
- # => TODO we need to implement access rules that implement both and will deny the access
- # if either IP or user returns deny
- test "mixing IP and user access rules" do
- :acl.add(:global, :user_acl_1, {:user, "test1"})
- :acl.add(:global, :ip_acl_1, {:ip, "127.0.0.0/24"})
- :acl.add_access(:global, :mixed_rule_1, [{:allow, [{:acl, :user_acl_1}]}, {:allow, [{:acl, :ip_acl_1}]}])
- assert :acl.match_rule(:global, :mixed_rule_1, :jid.from_string("test1@domain1")) == :allow
- assert :acl.match_rule(:global, :mixed_rule_1, {127,0,0,1}) == :allow
-
- :acl.add_access(:global, :mixed_rule_2, [{:deny, [{:acl, :user_acl_1}]}, {:allow, [{:acl, :ip_acl_1}]}])
- assert :acl.match_rule(:global, :mixed_rule_2, :jid.from_string("test1@domain1")) == :deny
- assert :acl.match_rule(:global, :mixed_rule_2, {127,0,0,1}) == :allow
- end
-
- test "access_matches works with predefined access rules" do
- :acl.add(:global, :user_acl_2, {:user, "user"})
- :acl.add_access(:global, :user_rule_2, [{:allow, [{:acl, :user_acl_2}]}, {:deny, [:all]}])
-
- assert :acl.access_matches(:user_rule_2, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
- assert :acl.access_matches(:user_rule_2, %{usr: {"user2", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
- end
-
- test "access_matches rule all always matches" do
- assert :acl.access_matches(:all, %{}, :global) == :allow
- assert :acl.access_matches(:all, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
- end
-
- test "access_matches rule none never matches" do
- assert :acl.access_matches(:none, %{}, :global) == :deny
- assert :acl.access_matches(:none, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
- end
-
- test "access_matches with not existing rule never matches" do
- assert :acl.access_matches(:bleble, %{}, :global) == :deny
- assert :acl.access_matches(:bleble, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
- end
-
- test "access_matches works with inlined access rules" do
- :acl.add(:global, :user_acl_3, {:user, "user"})
-
- assert :acl.access_matches([{:allow, [{:acl, :user_acl_3}]}, {:deny, [:all]}],
- %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
- assert :acl.access_matches([{:allow, [{:acl, :user_acl_3}]}, {:deny, [:all]}],
- %{usr: {"user2", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
- end
-
- test "access_matches allow to have acl rules inlined" do
- assert :acl.access_matches([{:allow, [{:user, "user"}]}, {:deny, [:all]}],
- %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
- assert :acl.access_matches([{:allow, [{:user, "user"}]}, {:deny, [:all]}],
- %{usr: {"user2", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
- end
-
- test "access_matches test have implicit deny at end" do
- assert :acl.access_matches([{:allow, [{:user, "user"}]}],
- %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
- assert :acl.access_matches([{:allow, [{:user, "user"}]}],
- %{usr: {"user2", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
- end
-
- test "access_matches requires that all subrules match" do
- rules = [{:allow, [{:user, "user"}, {:ip, {{127,0,0,1}, 32}}]}]
- assert :acl.access_matches(rules, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user", "domain1", ""}, ip: {127,0,0,2}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user2", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
- end
-
- test "access_matches rules are matched in order" do
- rules = [{:allow, [{:user, "user"}]}, {:deny, [{:user, "user2"}]}, {:allow, [{:user_regexp, "user"}]}]
- assert :acl.access_matches(rules, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user2", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user22", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
- end
-
- test "access_matches rules that require ip but no one is provided don't crash" do
- rules = [{:allow, [{:ip, {{127,0,0,1}, 32}}]},
- {:allow, [{:user, "user"}]},
- {:allow, [{:user, "user2"}, {:ip, {{127,0,0,1}, 32}}]}]
- assert :acl.access_matches(rules, %{usr: {"user", "domain1", ""}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user2", "domain1", ""}}, :global) == :deny
- end
-
- test "access_matches rules that require usr but no one is provided don't crash" do
- rules = [{:allow, [{:ip, {{127,0,0,1}, 32}}]},
- {:allow, [{:user, "user"}]},
- {:allow, [{:user, "user2"}, {:ip, {{127,0,0,2}, 32}}]}]
- assert :acl.access_matches(rules, %{ip: {127,0,0,1}}, :global) == :allow
- assert :acl.access_matches(rules, %{ip: {127,0,0,2}}, :global) == :deny
- end
-
- test "access_matches rules with all always matches" do
- rules = [{:allow, [:all]}, {:deny, {:user, "user"}}]
- assert :acl.access_matches(rules, %{}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
- end
-
- test "access_matches rules with {acl, all} always matches" do
- rules = [{:allow, [{:acl, :all}]}, {:deny, {:user, "user"}}]
- assert :acl.access_matches(rules, %{}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :allow
- end
-
- test "access_matches rules with none never matches" do
- rules = [{:allow, [:none]}, {:deny, [:all]}]
- assert :acl.access_matches(rules, %{}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
- end
-
- test "access_matches with no rules never matches" do
- assert :acl.access_matches([], %{}, :global) == :deny
- assert :acl.access_matches([], %{usr: {"user", "domain1", ""}, ip: {127,0,0,1}}, :global) == :deny
- end
-
- test "access_matches ip rule accepts {ip, port}" do
- rules = [{:allow, [{:ip, {{127,0,0,1}, 32}}]}]
- assert :acl.access_matches(rules, %{ip: {{127,0,0,1}, 5000}}, :global) == :allow
- assert :acl.access_matches(rules, %{ip: {{127,0,0,2}, 5000}}, :global) == :deny
- end
-
- test "access_matches user rule works" do
- rules = [{:allow, [{:user, "user1"}]}]
- assert :acl.access_matches(rules, %{usr: {"user1", "domain1", ""}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user2", "domain1", ""}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user1", "domain3", ""}}, :global) == :deny
- end
-
- test "access_matches 2 arg user rule works" do
- rules = [{:allow, [{:user, {"user1", "server1"}}]}]
- assert :acl.access_matches(rules, %{usr: {"user1", "server1", ""}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user1", "server2", ""}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user2", "server1", ""}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user2", "server2", ""}}, :global) == :deny
- end
-
- test "access_matches server rule works" do
- rules = [{:allow, [{:server, "server1"}]}]
- assert :acl.access_matches(rules, %{usr: {"user", "server1", ""}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user", "server2", ""}}, :global) == :deny
- end
-
- test "access_matches resource rule works" do
- rules = [{:allow, [{:resource, "res1"}]}]
- assert :acl.access_matches(rules, %{usr: {"user", "domain1", "res1"}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user", "domain1", "res2"}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user", "domain3", "res1"}}, :global) == :allow
- end
-
- test "access_matches user_regexp rule works" do
- rules = [{:allow, [{:user_regexp, "user[0-9]"}]}]
- assert :acl.access_matches(rules, %{usr: {"user1", "domain1", "res1"}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"userA", "domain1", "res1"}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user1", "domain3", "res1"}}, :global) == :deny
- end
-
- test "access_matches 2 arg user_regexp rule works" do
- rules = [{:allow, [{:user_regexp, {"user[0-9]", "server1"}}]}]
- assert :acl.access_matches(rules, %{usr: {"user1", "server1", "res1"}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"userA", "server1", "res1"}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user1", "server2", "res1"}}, :global) == :deny
- end
-
- test "access_matches server_regexp rule works" do
- rules = [{:allow, [{:server_regexp, "server[0-9]"}]}]
- assert :acl.access_matches(rules, %{usr: {"user", "server1", ""}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user", "serverA", ""}}, :global) == :deny
- end
-
- test "access_matches resource_regexp rule works" do
- rules = [{:allow, [{:resource_regexp, "res[0-9]"}]}]
- assert :acl.access_matches(rules, %{usr: {"user", "domain1", "res1"}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user", "domain1", "resA"}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user", "domain3", "res1"}}, :global) == :allow
- end
-
- test "access_matches node_regexp rule works" do
- rules = [{:allow, [{:node_regexp, {"user[0-9]", "server[0-9]"}}]}]
- assert :acl.access_matches(rules, %{usr: {"user1", "server1", "res1"}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"userA", "server1", "res1"}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user1", "serverA", "res1"}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"userA", "serverA", "res1"}}, :global) == :deny
- end
-
- test "access_matches user_glob rule works" do
- rules = [{:allow, [{:user_glob, "user?"}]}]
- assert :acl.access_matches(rules, %{usr: {"user1", "domain1", "res1"}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user11", "domain1", "res1"}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user1", "domain3", "res1"}}, :global) == :deny
- end
-
- test "access_matches 2 arg user_glob rule works" do
- rules = [{:allow, [{:user_glob, {"user?", "server1"}}]}]
- assert :acl.access_matches(rules, %{usr: {"user1", "server1", "res1"}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user11", "server1", "res1"}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user1", "server2", "res1"}}, :global) == :deny
- end
-
- test "access_matches server_glob rule works" do
- rules = [{:allow, [{:server_glob, "server?"}]}]
- assert :acl.access_matches(rules, %{usr: {"user", "server1", ""}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user", "server11", ""}}, :global) == :deny
- end
-
- test "access_matches resource_glob rule works" do
- rules = [{:allow, [{:resource_glob, "res?"}]}]
- assert :acl.access_matches(rules, %{usr: {"user", "domain1", "res1"}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user", "domain1", "res11"}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user", "domain3", "res1"}}, :global) == :allow
- end
-
- test "access_matches node_glob rule works" do
- rules = [{:allow, [{:node_glob, {"user?", "server?"}}]}]
- assert :acl.access_matches(rules, %{usr: {"user1", "server1", "res1"}}, :global) == :allow
- assert :acl.access_matches(rules, %{usr: {"user11", "server1", "res1"}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user1", "server11", "res1"}}, :global) == :deny
- assert :acl.access_matches(rules, %{usr: {"user11", "server11", "res1"}}, :global) == :deny
- end
-
- test "transform_access_rules_config expands allow rule" do
- assert :acl.transform_access_rules_config([:allow]) == [{:allow, [:all]}]
- end
-
- test "transform_access_rules_config expands deny rule" do
- assert :acl.transform_access_rules_config([:deny]) == [{:deny, [:all]}]
- end
-
- test "transform_access_rules_config expands <integer> rule" do
- assert :acl.transform_access_rules_config([100]) == [{100, [:all]}]
- end
-
- test "transform_access_rules_config expands <shaper_name> rule" do
- assert :acl.transform_access_rules_config([:fast]) == [{:fast, [:all]}]
- end
-
- test "transform_access_rules_config expands allow: <acl_name> rule" do
- assert :acl.transform_access_rules_config([{:allow, :test1}]) == [{:allow, [{:acl, :test1}]}]
- end
-
- test "transform_access_rules_config expands deny: <acl_name> rule" do
- assert :acl.transform_access_rules_config([{:deny, :test1}]) == [{:deny, [{:acl, :test1}]}]
- end
-
- test "transform_access_rules_config expands integer: <acl_name> rule" do
- assert :acl.transform_access_rules_config([{100, :test1}]) == [{100, [{:acl, :test1}]}]
- end
-
- test "transform_access_rules_config expands <shaper_name>: <acl_name> rule" do
- assert :acl.transform_access_rules_config([{:fast, :test1}]) == [{:fast, [{:acl, :test1}]}]
- end
-
- test "transform_access_rules_config expands allow rule (no list)" do
- assert :acl.transform_access_rules_config(:allow) == [{:allow, [:all]}]
- end
-
- test "transform_access_rules_config expands deny rule (no list)" do
- assert :acl.transform_access_rules_config(:deny) == [{:deny, [:all]}]
- end
-
- test "transform_access_rules_config expands <integer> rule (no list)" do
- assert :acl.transform_access_rules_config(100) == [{100, [:all]}]
- end
-
- test "transform_access_rules_config expands <shaper_name> rule (no list)" do
- assert :acl.transform_access_rules_config(:fast) == [{:fast, [:all]}]
- end
-
- test "access_rules_validator works with <AccessName>" do
- assert :acl.access_rules_validator(:my_access) == :my_access
- end
-
- test "shapes_rules_validator works with <AccessName>" do
- assert :acl.shaper_rules_validator(:my_access) == :my_access
- end
-
- ## Checking ACL on both user pattern and IP
- ## ========================================
-
- # Typical example is mod_register
-
- # Deprecated approach
- test "module can test both IP and user through two independent :acl.match_rule check (deprecated)" do
- :acl.add(:global, :user_acl, {:user, {"test1", "domain1"}})
- :acl.add(:global, :ip_acl, {:ip, "127.0.0.0/24"})
- :acl.add_access(:global, :user_rule, [{:allow, [{:acl, :user_acl}]}])
- :acl.add_access(:global, :ip_rule, [{:allow, [{:acl, :ip_acl}]}])
-
- # acl module in 16.03 is not able to provide a function for compound result:
- assert :acl.match_rule(:global, :user_rule, :jid.from_string("test1@domain1")) == :allow
- assert :acl.match_rule(:global, :ip_rule, {127,0,0,1}) == :allow
- assert :acl.match_rule(:global, :user_rule, :jid.from_string("test2@domain1")) == :deny
- assert :acl.match_rule(:global, :ip_rule, {127,0,1,1}) == :deny
- end
-
-end
diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl
index 553eae1e4..1d8de7b8e 100644
--- a/test/ejabberd_SUITE.erl
+++ b/test/ejabberd_SUITE.erl
@@ -401,7 +401,7 @@ db_tests(riak) ->
presence_broadcast,
last,
roster_tests:single_cases(),
- private,
+ %%private_tests:single_cases(),
privacy_tests:single_cases(),
vcard_tests:single_cases(),
muc_tests:single_cases(),
@@ -424,7 +424,7 @@ db_tests(DB) when DB == mnesia; DB == redis ->
presence_broadcast,
last,
roster_tests:single_cases(),
- private,
+ private_tests:single_cases(),
privacy_tests:single_cases(),
vcard_tests:single_cases(),
pubsub_tests:single_cases(),
@@ -455,7 +455,7 @@ db_tests(_) ->
presence_broadcast,
last,
roster_tests:single_cases(),
- private,
+ private_tests:single_cases(),
privacy_tests:single_cases(),
vcard_tests:single_cases(),
pubsub_tests:single_cases(),
@@ -602,7 +602,7 @@ test_connect_bad_ns_stream(Config) ->
test_connect_bad_lang(Config) ->
Lang = iolist_to_binary(lists:duplicate(36, $x)),
Config0 = init_stream(set_opt(lang, Lang, Config)),
- ?recv1(#stream_error{reason = 'policy-violation'}),
+ ?recv1(#stream_error{reason = 'invalid-xml'}),
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config0).
@@ -978,33 +978,6 @@ disco(Config) ->
end, Items),
disconnect(Config).
-private(Config) ->
- Conference = #bookmark_conference{name = <<"Some name">>,
- autojoin = true,
- jid = jid:make(
- <<"some">>,
- <<"some.conference.org">>,
- <<>>)},
- Storage = #bookmark_storage{conference = [Conference]},
- StorageXMLOut = xmpp:encode(Storage),
- WrongEl = #xmlel{name = <<"wrong">>},
- #iq{type = error} =
- send_recv(Config, #iq{type = get,
- sub_els = [#private{sub_els = [WrongEl]}]}),
- #iq{type = result, sub_els = []} =
- send_recv(
- Config, #iq{type = set,
- sub_els = [#private{sub_els = [WrongEl, StorageXMLOut]}]}),
- #iq{type = result,
- sub_els = [#private{sub_els = [StorageXMLIn]}]} =
- send_recv(
- Config,
- #iq{type = get,
- sub_els = [#private{sub_els = [xmpp:encode(
- #bookmark_storage{})]}]}),
- Storage = xmpp:decode(StorageXMLIn),
- disconnect(Config).
-
last(Config) ->
true = is_feature_advertised(Config, ?NS_LAST),
#iq{type = result, sub_els = [#last{}]} =
diff --git a/test/ejabberd_SUITE_data/ca.key b/test/ejabberd_SUITE_data/ca.key
index d68386a90..cc59087c6 100644
--- a/test/ejabberd_SUITE_data/ca.key
+++ b/test/ejabberd_SUITE_data/ca.key
@@ -1,27 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
-MIIEpQIBAAKCAQEAtc6rVzp7V9xPMZCFLSR0QBC0yKl4K6irRmkiOX+dU+HlScN9
-CGXhr44TtR4oP5LIoOmCHqF8dGo/S+cDahBJArG48/amZYUgXbQKwp27wcC52Qva
-QVpfxZtBuEi8fdN/O1rgwgvFvTS6jRCuIRSK2+0oCZbVQHCv03LUEEk7lADWwZo6
-ad3hVDQqpdRagzPB3jk3wUXLv5cfHbwx+9iT1tvTtH1LgzXyLeSe2Agokl/5n8P+
-FQqD2KKd8lal9dDREpjhYHme9b5Qdzm8F/iQxrA5s4plte1fIeBdJdcTSIfdn+1A
-Gxx4Itzk8+Ss4n6B+N8ldYO/mEVPy41vV1fSCQIDAQABAoIBAQCdmEVr+nS6p206
-cKUQiX3/nrunCUfyyGWXRkD1iIpvEOx5cWxroCzuyYCJyldnLCyfV2qEdMT2ALdX
-ayvJf85Z+IZza4W3kJuKvitpoeMEQ7SV0TFi3x0PpNJmFR7XD0NFxzP24M+KgC+J
-YkEeybjIhR3kmgvGrSesaegrKd/sHQs/hVCTQLSA3ymTBVmf6E1RGMG6VPzqISXc
-Ul16ywbZq3DS0LC55KdMpOaWQUsKqEftZ2+j/piZBqthM6mPHqVecT/vOJSNMH7b
-M3JaBWXeWcClj5ikI8lyGNT/qqLrTZGVFm9fcFZ3JERoAedIYcFrNdP+b3P35+ck
-n+RwMrGRAoGBAOYdeKglwuh0LCllR6VvhAKSVx8lH2mIAFw9b0ElbdCmsNOpzk8a
-vtlt3FIl+LrzoixVGxBw/LovtE3HvHkh9gCgC6m3RumpdLpMmxSufXjumm6fUH69
-HkGjYEjXG1XLagHK4WcBGn6tJshLkXLiT9KYR7oHL1vnkynFAVxXGYtNAoGBAMpC
-GRJtjsym5H0O5Q401wOuwr3smSLd3PX69qZlldcNrNtXToxQpcbguG2q6xQtv4+u
-uvLqcjaNcOw4nLs0uxncEtLoBLss36WpHZJ7h6FXoSNSDUnmiLEy1nqNy+t2awQ/
-G23J2I/NYhkWV1k6cKP9buE6zok/tG2zazRsieutAoGBAOHaPSkz3sgN9aYvfJF4
-ESYBAbdU1S7byBbASWi0VsL6SAWG3TGwu25mMF8EW8sKArrmUOjjLHmFBr9scaSm
-W2+jpa7ObrA5nJKZS4CR/h5qeXJntTafYYnk00TKs06SlpnpyS8y2xFH2w+tc7VB
-QD0fLw77rFTUxhCyBjLeXfjlAoGBAL2S9JMMM0NozlLVSiZlqn7eJ9QCVoqSr0+z
-ecn+/1IBlFpGW+4yjR96dAL+p7ZTJvDSkpGq44B0VDpk2aqyQwh6+P3L0NtrtPg+
-KeSpejfkvbkwaBozRT3pa5SVIWRCqw3g5KGkBiLLG75t7VNJ7lgW6/2ZtAp6tm/4
-MmyBq0hFAoGAbeD8BcnKa9nct7zjJnKjTv63zqoIkj1Wo1rSoqq0LkHCXXMsWQh+
-D2uf75eZ2A6xzw6LEGE5p07FjI+zung6m3DR5dHVt8hxDYqyoBM/pIdu1rlcVUY1
-Y55ErDdbkPwGbcjJmR4Yg2DGeBDMW10Pa9WnsbiNmPsfXnzG6TDnxCk=
+MIIEpQIBAAKCAQEA5WxWkSLK3iadpy2v57FVc7pK307aWHQqirg+q5PreRB1nLsr
+oW+TaXfgB5B1/GTFStnSbmczqpkuWyi4hIB9ZzM62kWuOpZPx0+w5hHx73VWxpsr
+YgaBkoQsn8BF84PfmRDNG76TOacuoLzeqnN1deWDgOGQ9a7ZesOQLuZBPF6oysfK
+OpAR035fQM6XaaR8Ti6Ko53DkCzw8MiySrAHJOkgxhmX11+hUMjldWCEiRs1VL/g
+rolajqe3B+wu0UdonZ/QUeVk4KRnDIAIJSKw8XmgcB4oI5cUrnDnOmv2784RgJZs
+ZxuGF0e5mz5v8BqXqKiFwH/CD1inUpMA89MATQIDAQABAoIBAQCc2O1x+ixhplrg
+AZ8iMp2uKe2oL5udH4Y6Im5OFSnGMdeGmHviuYo5b8gMw9m1/RrY6oQwEIRFHMaR
+cgx8IfAaDu8sbLkJutu98qCJGjmiMUFrNIh7UuFgztZHPUdVjZHfbpobXrX+k2qQ
+X6+HLrpeKNQ3136oSKrMgEjhl2+AGhe/uqFGw+nwCNzY3BnAJOWS8pipgV0IQ1Eo
+AdJU8SoW/LToo5RTZNodPhyqLl10D1tRJ8WSAndAkvaoMRHJasYQDrmz449+QiTZ
+SLRf9n/TtcKJQTaqwskV/dOdygeBUKnZQhq663TKgTWcTxF1dA5T3QxXv/7p+8Ow
+9GxuxBjBAoGBAPRjb8OCLD8EAtxFXWRWBH5GWF3vGnDIq5FkPaue0uyDaw+TLgJE
+AKV7Ik0IRRZkUdc/xix22Bg83L0ErOD2qLHgZuUvuXtiv+Dq/D2BIb5M3zQy8giA
+vxdlE5O9i8aG647P+ACGOpYZ7a/K645HGxqOZpf8ZRmST5VzNY7qVxb9AoGBAPBS
+4Bo66VMWf6BLd8RIK3DzOf0TWRRMCAwX9kCNTG22TX79imJHWB5lWQQam4yp4Cya
+wo08DT3YcffURW9bJTF2q+JZHMqlEr8q9kcjIJu8uQ7X9N4JsUfCcWaBSHHBNgx/
+coved2h02NFcJmV3HuF2l/miah6p9rPJmGnvG1eRAoGBAKIEqju7OQot5peRhPDX
+9fKhQERGGAldgCDLi/cTPFKAbaHNuVrXKnaKw5q+OM83gupo5UDlKS4oa08Eongi
+DoSeeJjIovch6IN8Re2ghnZbED7S55KriARChlAUAW6EU/ZB+fCfDIgmeGVq6e9R
+RK6+aVWphn0Feq1hy8gLo+EhAoGBAI/hvmRV4v2o2a5ZoJH2d3O/W3eGTu3U+3hq
+HDfXoOuKmukt2N0wQ7SnDt1jJL/ZsOpjmZk/W9osLUeoYg3ibuknWI9CtPcqT4f+
+q8Y5ZLt5CP63EtagzO/enVA2lO3uNHLVFvpgrfLvCiSGXEKhR+7KtwBxWcGUFqzb
+RJIf4qnRAoGAR+c24S4MtVuw6+UVKyLxhjB6iDTvJijdIr/+ofbeM5TQHGsYzZzP
+HHNdZ5ECz5eDnaNzvAs4CCuy+75cqlUhAgzrLlCj+dJN/fYEJsD6AjWdto3Zorig
+XBFM8FtXP7VRjFNwCCbdhrFOcmgbAtz3ReS6Ts6drSw7OgyeDajam1U=
-----END RSA PRIVATE KEY-----
diff --git a/test/ejabberd_SUITE_data/ca.pem b/test/ejabberd_SUITE_data/ca.pem
index 4c8e13072..089238d62 100644
--- a/test/ejabberd_SUITE_data/ca.pem
+++ b/test/ejabberd_SUITE_data/ca.pem
@@ -1,21 +1,21 @@
-----BEGIN CERTIFICATE-----
-MIIDazCCAlOgAwIBAgIUa9rEblUCcNwodsydB2e1nKlFVNswDQYJKoZIhvcNAQEL
+MIIDazCCAlOgAwIBAgIUUynLQejEU8NykU/YNfL1dyC7vxcwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
-GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xODA4MjQwOTMwNTZaFw0xODA5
-MjMwOTMwNTZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xODA5MjQxMzE4MjRaFw00NjAy
+MDkxMzE4MjRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
-AQUAA4IBDwAwggEKAoIBAQC1zqtXOntX3E8xkIUtJHRAELTIqXgrqKtGaSI5f51T
-4eVJw30IZeGvjhO1Hig/ksig6YIeoXx0aj9L5wNqEEkCsbjz9qZlhSBdtArCnbvB
-wLnZC9pBWl/Fm0G4SLx90387WuDCC8W9NLqNEK4hFIrb7SgJltVAcK/TctQQSTuU
-ANbBmjpp3eFUNCql1FqDM8HeOTfBRcu/lx8dvDH72JPW29O0fUuDNfIt5J7YCCiS
-X/mfw/4VCoPYop3yVqX10NESmOFgeZ71vlB3ObwX+JDGsDmzimW17V8h4F0l1xNI
-h92f7UAbHHgi3OTz5KzifoH43yV1g7+YRU/LjW9XV9IJAgMBAAGjUzBRMB0GA1Ud
-DgQWBBSFGsmx8f2Jkv9ns8Jew5Hs0x6AQTAfBgNVHSMEGDAWgBSFGsmx8f2Jkv9n
-s8Jew5Hs0x6AQTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCZ
-/E+LQUDn1dUgXkQvwtr4LKoJgwrLDycROKcXCM88z73QjAXCXgUtSAbhV8ZcsakL
-v5XO3ytu//iHYLUVcT72eN24H8P7jUYV8egmceep1PXUPNl6ukYSs4SAlx9YIxSI
-b8CqySYypCztk5aStR51schM6PjDY09UT+x74BtSme3tcUPYBJ1j382zQOp7jBc6
-bW/vL9ouonrfO7GlADwBi7K8y3eaTampIkrogh0YTreDVvz7266CmeCCuaJGNs3C
-NNyWBliI0zqpyfU+7uVD5wgBS6ncz9eZcsFpOrh+H1ZpcRLaaYkjf0znYGbLCFuP
-w7NWOLnq2KH+n2g5M5nw
+AQUAA4IBDwAwggEKAoIBAQDlbFaRIsreJp2nLa/nsVVzukrfTtpYdCqKuD6rk+t5
+EHWcuyuhb5Npd+AHkHX8ZMVK2dJuZzOqmS5bKLiEgH1nMzraRa46lk/HT7DmEfHv
+dVbGmytiBoGShCyfwEXzg9+ZEM0bvpM5py6gvN6qc3V15YOA4ZD1rtl6w5Au5kE8
+XqjKx8o6kBHTfl9AzpdppHxOLoqjncOQLPDwyLJKsAck6SDGGZfXX6FQyOV1YISJ
+GzVUv+CuiVqOp7cH7C7RR2idn9BR5WTgpGcMgAglIrDxeaBwHigjlxSucOc6a/bv
+zhGAlmxnG4YXR7mbPm/wGpeoqIXAf8IPWKdSkwDz0wBNAgMBAAGjUzBRMB0GA1Ud
+DgQWBBQGU3AZGF8ahVEnpfHB5ETAW5uIBzAfBgNVHSMEGDAWgBQGU3AZGF8ahVEn
+pfHB5ETAW5uIBzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAK
+jIEjOh7k1xaEMBygQob9XGLmyLgmw1GEvWx7wiDpcdHXuAH9mLC4NPNSjOXPNK2V
+u4dh1KHy1z+dHJbt2apXejxtiwlcMWmPDF2EtKjstUN+KXecG7vjReArs71T9ir/
+7Xfwfg6TKD3H7efYFJaBb7d/lyneNP1Ive/rkRsGqCglkoX4ajcAm7MLkkFD8TCP
+NqFc7SdA4OsaeYiUmjnyTUDbKgG0bDAXymhsUzd6Pa9kKQx+dH4GPiCoNoypCXD7
+RZSlETNGZ0vdxCjpdvT4eYxSIalG4rAU85turqPF/ovdzUzb72Sta0L5Hrf0rLa/
+um3+Xel8qI+p3kErAG2v
-----END CERTIFICATE-----
diff --git a/test/ejabberd_SUITE_data/cert.pem b/test/ejabberd_SUITE_data/cert.pem
index 7e110f0a2..7b82b3ca7 100644
--- a/test/ejabberd_SUITE_data/cert.pem
+++ b/test/ejabberd_SUITE_data/cert.pem
@@ -1,54 +1,54 @@
-----BEGIN CERTIFICATE-----
MIIEjTCCA3WgAwIBAgIBATANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJBVTET
MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
-dHkgTHRkMB4XDTE4MDgyNDA5MzA1NloXDTQ2MDEwOTA5MzA1NlowWTELMAkGA1UE
+dHkgTHRkMB4XDTE4MDkyNDEzMTgyNFoXDTQ2MDIwOTEzMTgyNFowWTELMAkGA1UE
BhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdp
ZGdpdHMgUHR5IEx0ZDESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B
-AQEFAAOCAQ8AMIIBCgKCAQEAwLATNFRSFKTfWd0HHoX4uw9lWw5uuq/bAIx9yYKY
-TaOr/tQKgzS2EXSnvMngrywJlP1HUzLrp3WNR0CiPIQ0YfeAP3xiSrotEWObRb/0
-YwOZhCbOi0WpWPgZh3ct/XaEHhbnKYtblya1wVrCN/yur3ck+ru4Mka2lfxouKF9
-y8kZn2qF0CEbR/bbJiOy0Cr+q+LCIOiD+iwKHhju6ks+OqmFwXhglE62b7yKZtcu
-SddnsimF6n0VXkS2Vm5Kg901Sed7QPtVR6EtIBh7WiwU6iLoOofFjOg3B7v3K8hX
-66Scj5V/mz++m2W9BTqkoYRmszPHXnFeF8ZhAzc2ncUWrQIDAQABo4IBcjCCAW4w
+AQEFAAOCAQ8AMIIBCgKCAQEA1oQMN4MZ/wEf4SM7chwHZ+ymQ5Knt45VZ0jmgpnK
+Fx0p+eJoNegvvwY/80NWTmcgbGnqruJiOh5AEUNDtCD5G/70oz2WHgZBZkuLsopE
+a/2sDmwxvUbv1f/mD8iHcDaWUvKAy4TUHFeHDQL28HJom9E7bgYadeuhebwZcsbu
+lPFePw+fWM7jLWxkMYClfsdzsBrgerbZVPnAuj77cGXZSQ6p96jOPiJ/mjOVCwWJ
+tdlqwme2AC4AwKYdWzc3Ysw8lES/ubMa+lP1Eh9aI8edpHIlC5nYNLVTWa4Xw6Ct
+AvqzKtNNJzwypbR3fcDXaWvvO3GY3wOHVC/wyCsL8SXc7QIDAQABo4IBcjCCAW4w
CQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2Vy
-dGlmaWNhdGUwHQYDVR0OBBYEFEM665UV1uNuXuoj2Lq4YvUig8fMMB8GA1UdIwQY
-MBaAFIUaybHx/YmS/2ezwl7DkezTHoBBMDMGA1UdHwQsMCowKKAmoCSGImh0dHA6
+dGlmaWNhdGUwHQYDVR0OBBYEFFvDi47v5xJKOsgQo8MP4JzY6cC/MB8GA1UdIwQY
+MBaAFAZTcBkYXxqFUSel8cHkRMBbm4gHMDMGA1UdHwQsMCowKKAmoCSGImh0dHA6
Ly9sb2NhbGhvc3Q6NTI4MC9kYXRhL2NybC5kZXIwNgYIKwYBBQUHAQEEKjAoMCYG
CCsGAQUFBzABhhpodHRwOi8vbG9jYWxob3N0OjUyODAvb2NzcDALBgNVHQ8EBAMC
BeAwJwYDVR0lBCAwHgYIKwYBBQUHAwkGCCsGAQUFBwMBBggrBgEFBQcDAjBQBgNV
HREESTBHggsqLmxvY2FsaG9zdKA4BggrBgEFBQcIBaAsDCp0ZXN0X3NpbmdsZSEj
JCVeKigpYH4rLTtfPVtde318XEBsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEB
-AGYMvXw1GZak2blxC2gr7p68MTjrPYAs26yKDuLR0Wpg1wMgnAelsMPrjjuSxWQX
-MlVFW1FD7OPIsgxgCZaOJiITEK6TgPa+XsSZa4H2o9fpIUd9Dy18sxJIjEpQMYa3
-L5Uq5tMTlxK9tovH3wNbdnW24MZ0nlWP/uzspbXqk7F/C6AbLX6tHLfJhcpyg94Z
-UY/pZG9IP6MME784eEubP5I0mxSM2JN5JiPKsk14/a4veUBJXq+vcMVCfuxNCWVA
-RRk9MJ9U31W3D6G8Y7Es53I2mmEHoN1mFHKmW3El3gtXJ7aUenQP0ayGTXlnFuGd
-fPrgVCvv3ykkKd6cizhB1O8=
+AEW8qvdyBMOSjCwJ1G178xsxf8Adw/9QN2ftBGKCo1C3YtmP5CvipChq5FTrOvRz
+XjoQxbKhlqEumkZQkfmLiM/DLbkFeNqGWpuy14lkyIPUknaLKNCJX++pXsJrPLGR
+btWnlB0cb+pLIB/UkG8OIpW07pNOZxHdHoHInRMMs89kgsmhIpn5OamzPWK/bqTB
+YjAPIdmdkYk9oxWfgjpJ4BG2PbGS6CnjA29j7vebuQ4ebVpFBMI9w77PY3NcuMK7
+ML6MV6ez/+nPpz+E4zRxsVxmVAbSaiFDW3G3efAybDeT5QW1x/oJm2SpsJNIGHcp
+RecYNo9esOTG+Bg6wypg4WA=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
-MIIEowIBAAKCAQEAwLATNFRSFKTfWd0HHoX4uw9lWw5uuq/bAIx9yYKYTaOr/tQK
-gzS2EXSnvMngrywJlP1HUzLrp3WNR0CiPIQ0YfeAP3xiSrotEWObRb/0YwOZhCbO
-i0WpWPgZh3ct/XaEHhbnKYtblya1wVrCN/yur3ck+ru4Mka2lfxouKF9y8kZn2qF
-0CEbR/bbJiOy0Cr+q+LCIOiD+iwKHhju6ks+OqmFwXhglE62b7yKZtcuSddnsimF
-6n0VXkS2Vm5Kg901Sed7QPtVR6EtIBh7WiwU6iLoOofFjOg3B7v3K8hX66Scj5V/
-mz++m2W9BTqkoYRmszPHXnFeF8ZhAzc2ncUWrQIDAQABAoIBAD0Ly5nJmROXRHe4
-8dd2xF2nPApEIUqeCjk7SWK9MDlwwD4Euavl2EThe37xSnDU99gvAhjWyhgMwwyu
-6ndXvyzGd+glJRLdtweKUhgf/3IbItjeXpN7J3Lbb2MenABwOt05jlT4JLVXez/d
-3ChWspU+nwWc0XeENbaA+EB+UVdtOfJhRk5l/LPENHBwEjMgRsbPO+upa4v/YEbj
-GwDaaC1HsI8jqGxSu2IRFqEeEOniPjnAi6SYfl41ZBj+cUrYA3xT94Ksk403iwax
-Ln7FJC2oNo/Okunf6V9ucgzLAYIA/LW/BJ6+dcAzH6OGYuvI6a229W2pEbL7gwiw
-7pouoyECgYEA/T3RIktzeCvVz4g2cy8IkzxBt95tpcJd5weMfyn2qYiQVxPZa9dW
-VE7hKKQAOphSj+38Rg/W1tMefPzk8PcnxdI7hi1RF7xk1rIH6SwLE2rywz7Oh0UG
-5OuL5AOu0rnIXKBWO/o7mPq8ix1eOGRumT29rfKKecIUGwxPagdrJnkCgYEAwslm
-SUwU6KoRoXzm13Q5XcYVkhiwzr05nGuxN5chzpyfLsRBZFJOBCRgnprx2tNANvzx
-9raIw76BUueBZ+9OEnX9QshUdd4/+Mu+Htl3Q7jDOMv4I+CleD3M83dwrLkyUGXc
-iU5+N2xlrg6Zs6MqB59M2t2cppys6OK45+7ZtNUCgYEA7toAR91sUnrd1jk+ShOh
-feWGgprrRj11/fKzxDjbKnng0hCpsDpRdYFUgtV0Vc/Xh2NK3vYPh11m0zJf6Rrk
-S4QHmn1hkAakAY+3QGjHJZBo2liByToEz6OOtQf4O075OMwNqdJRpe9QJ9ISTRQE
-8Mo3/jnV/BNejUhmGrZWjNkCgYAGWHdLlf3sYnX2k9IVXMTtqCFVxBYmdgWlceCx
-FexVBcctx9j1grTl76VyJUwRu+YQpIbhaark7ZTa9Y3CrAoYEd3xSgNuX5In8cM8
-0ArRmvhJJmPsD0p0s1a068Qw7EuWUOsxUexMs+xQEkuxjXQ2EVt4mdWbm+kKITCy
-5ZYUDQKBgHlWfvgXk3G2l8Pa07KW8Eo4Rvsp//L5aKG73kEQfmbD7ArJUX39T98n
-06wuN0yQsMkkgReQ/0q1jebZ1q27GrkIpWLIbL5qpUE9R+2PenHcbcVotvwc0cCk
-rSi8bYoVxJvtLPs08GahDEKgyBE2VL5Zu2kn70ij14uw3Gy4ug+O
+MIIEpgIBAAKCAQEA1oQMN4MZ/wEf4SM7chwHZ+ymQ5Knt45VZ0jmgpnKFx0p+eJo
+NegvvwY/80NWTmcgbGnqruJiOh5AEUNDtCD5G/70oz2WHgZBZkuLsopEa/2sDmwx
+vUbv1f/mD8iHcDaWUvKAy4TUHFeHDQL28HJom9E7bgYadeuhebwZcsbulPFePw+f
+WM7jLWxkMYClfsdzsBrgerbZVPnAuj77cGXZSQ6p96jOPiJ/mjOVCwWJtdlqwme2
+AC4AwKYdWzc3Ysw8lES/ubMa+lP1Eh9aI8edpHIlC5nYNLVTWa4Xw6CtAvqzKtNN
+JzwypbR3fcDXaWvvO3GY3wOHVC/wyCsL8SXc7QIDAQABAoIBAQDUwGX1cHsJ5C2f
+9ndwtsfJlHVZs0vPysR9CVpE0Q4TWoNVJ+0++abRB/vI4lHotHL90xZEmJXfGj1k
+YZf2QHWQBI7Qj7Yg1Qdr0yUbz/IIQLCyJTA3jvEzBvc/VByveBQi9Aw0zOopqc1x
+ZC1RT8bcMumEN11q8mVV/O4oXZAl+mQIbRRt6JIsRtoW8hpB1e2ipHItDMNpSnzA
+6PqcddDyDDePgi5lMOaeV9un60A6pI/+uvmw16R1Io+DyYRnxds3HJ/ccI0Co1P1
+khA75QLdnoniYO+oQrq/wGvm+Uq1seh6iuj+SOWvCdB03vPmGYxPKMSW9AtX8xbJ
+J9lboi3pAoGBAPBaiUYn9F+Zt9oJTHhAimZgs1ub5xVEFwVhYJtFBT3E1rQWRKuf
+kiU1JRq7TB3MGaC4zGi2ql12KV3AqFhwLKG6sKtlo/IJhJfe3DgWmBVYBBifkgYs
+mxmA6opgyjbjDEMn6RA+Jov5H267AsnaB4cCB1Jjra6GIdIoMvPghHZXAoGBAOR6
+7VC6E+YX5VJPCZiN0h0aBT+Hl4drYQKvZHp5N8RIBkvmcQHEJgsrUKdirFZEXW6y
+WvepwI4C/Xl61y64/DZ7rum/gpAEPdzSkefKysHAiqkMRcIpjiRxTPJ547ZJycjP
+E+jzcYfLwQvCW9ZiYl+KdYRbpqBFQC8aWqixFxRbAoGBAJQTsy79vpiHY7V4tRwA
+50NboCR4UE3RvT0bWSFPzILZmk0oyvXRQYCa1Vk6uxJAhCl4sLZyk1MxURrpbs3N
+jjG1itKNtAuRwZavPo1vnhLIPv3MkXIsWQHFYroOF4bpKszU8cmIAMeLm8nkfTtO
+kASlQ02HC6HSEVQgYAPP9svRAoGBANiOnwKl7Bhpy8TQ/zJmMaG9uP23IeuL3l4y
+KdVfsXjMH5OvLqtS5BAwFPkiMGBv2fMC/+/AKK8xrFiJEw3I7d0iK+6Hw1OHga8c
+soh1kOpF+ecyp6fZxU1LSniFCU0M8UHw7Fke7RueBzKDHJK9m6oczTgPuoYsPSKo
+IwfDGjIDAoGBAMJVkInntV8oDPT1WYpOAZ3Z0myCDZVBbjxx8kE4RSJIsFeNSiTO
+nhLWCqoG11PVTUzhpYItCjp4At/dG8OQY7WWm0DJJQB38fEqA6JKWpgeWwUdkk8j
+anCrNUBEuzt3UPSZ17DGCw2+J+mwsg1nevaFIXy0gN2zPtTBWtacznPL
-----END RSA PRIVATE KEY-----
diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml
index 51cb85a5a..e720ac103 100644
--- a/test/ejabberd_SUITE_data/ejabberd.yml
+++ b/test/ejabberd_SUITE_data/ejabberd.yml
@@ -525,6 +525,8 @@ shaper:
fast: 50000
normal: 10000
+new_sql_schema: @@new_schema@@
+
api_permissions:
"public commands":
who: all
diff --git a/test/ejabberd_SUITE_data/gencerts.sh b/test/ejabberd_SUITE_data/gencerts.sh
index b4dd8aa94..6975fe422 100755
--- a/test/ejabberd_SUITE_data/gencerts.sh
+++ b/test/ejabberd_SUITE_data/gencerts.sh
@@ -7,7 +7,7 @@ touch ssl/index.txt
echo 01 > ssl/serial
echo 1000 > ssl/crlnumber
openssl genrsa -out ca.key 2048
-openssl req -new -x509 -key ca.key -out ca.pem -batch
+openssl req -new -days 10000 -x509 -key ca.key -out ca.pem -batch
openssl genrsa -out ssl/client.key
openssl req -new -key ssl/client.key -out ssl/client.csr -config openssl.cnf -batch -subj /C=AU/ST=Some-State/O=Internet\ Widgits\ Pty\ Ltd/CN=localhost
openssl ca -keyfile ca.key -cert ca.pem -in ssl/client.csr -out ssl/client.crt -config openssl.cnf -days 10000 -batch -notext -policy policy_anything
diff --git a/test/ejabberd_SUITE_data/self-signed-cert.pem b/test/ejabberd_SUITE_data/self-signed-cert.pem
index adc7c631b..29fc38d36 100644
--- a/test/ejabberd_SUITE_data/self-signed-cert.pem
+++ b/test/ejabberd_SUITE_data/self-signed-cert.pem
@@ -1,47 +1,47 @@
-----BEGIN CERTIFICATE-----
-MIIDOTCCAiECFDJe+zeyS7+WcT5pKRNIHFM6CeVSMA0GCSqGSIb3DQEBCwUAMFkx
+MIIDOTCCAiECFHMoNo36Xx0BWkzS8nwvCPGnHnHRMA0GCSqGSIb3DQEBCwUAMFkx
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
-cm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xODA4
-MjQwOTMwNTZaFw00NjAxMDkwOTMwNTZaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQI
+cm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xODA5
+MjQxMzE4MjRaFw00NjAyMDkxMzE4MjRaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQI
DApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQx
EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
-ggEBAMCwEzRUUhSk31ndBx6F+LsPZVsObrqv2wCMfcmCmE2jq/7UCoM0thF0p7zJ
-4K8sCZT9R1My66d1jUdAojyENGH3gD98Ykq6LRFjm0W/9GMDmYQmzotFqVj4GYd3
-Lf12hB4W5ymLW5cmtcFawjf8rq93JPq7uDJGtpX8aLihfcvJGZ9qhdAhG0f22yYj
-stAq/qviwiDog/osCh4Y7upLPjqphcF4YJROtm+8imbXLknXZ7Iphep9FV5EtlZu
-SoPdNUnne0D7VUehLSAYe1osFOoi6DqHxYzoNwe79yvIV+uknI+Vf5s/vptlvQU6
-pKGEZrMzx15xXhfGYQM3Np3FFq0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAdOPI
-8g9eI8d6Op86mOxKVIoqTjrQM3Edtbox/So0J4OyKMqw7TM34l2b+1PE0VrrJiHv
-i69O3oN+ZajdmdE8LmfWLCXVRZk1hx4d0JKVzitV9ZX3tcws34AqL8cBSvUIPizo
-lEZ7XO6K+7ylt7IkDklnTln02Z2wuW26+TvKQkNC+mpPTput1ypWTTkUApiA2S91
-LGtymTAlixgXbgHhARlMnCHwPwmK+W5yR0HNT0OC0fVzF+rUUnGFh3WcJ3rjUY7b
-ZGP0V8lndZDz20rpsoSd4sx1W1HZFsX8R7goq7MK6va2IlRFh3kXsY/fD6C7+/Vm
-J56LwRdbLd7fG95akw==
+ggEBANaEDDeDGf8BH+EjO3IcB2fspkOSp7eOVWdI5oKZyhcdKfniaDXoL78GP/ND
+Vk5nIGxp6q7iYjoeQBFDQ7Qg+Rv+9KM9lh4GQWZLi7KKRGv9rA5sMb1G79X/5g/I
+h3A2llLygMuE1BxXhw0C9vByaJvRO24GGnXroXm8GXLG7pTxXj8Pn1jO4y1sZDGA
+pX7Hc7Aa4Hq22VT5wLo++3Bl2UkOqfeozj4if5ozlQsFibXZasJntgAuAMCmHVs3
+N2LMPJREv7mzGvpT9RIfWiPHnaRyJQuZ2DS1U1muF8OgrQL6syrTTSc8MqW0d33A
+12lr7ztxmN8Dh1Qv8MgrC/El3O0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAhM+Q
+qt4IlM1SMb74L5GO2JKGVSUbZmaFJEZcjlrcHkw+Tfc5SMxaj7JpTPg7OGNY1L/3
+HnUDdaDRZ5xVOxUF7gTBWDAgkO7En5YfvvEYXUYUk7wwpFrqUqQpluqQIxr+Zf6l
+pZFLhKIANa4wayKtZ9v4uBtRjnm9Hj7gQHeWN9sueIq7d4HO5lubYlzu1+6qeP+L
+M0ciNhsUPypCwVcLPB+1Eo925QBwAhXsvPD9yKFQg1M7XxcJSy0w3DwWQsTTsEbk
+8c/vIF/IhkOJHQDTKa+VSJM+hZgmx/PsyVdbWRSCAusiZpjHKhzzTCNEloGp/Vbm
+5y/OeAK2TGPTg9I91w==
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
-MIIEowIBAAKCAQEAwLATNFRSFKTfWd0HHoX4uw9lWw5uuq/bAIx9yYKYTaOr/tQK
-gzS2EXSnvMngrywJlP1HUzLrp3WNR0CiPIQ0YfeAP3xiSrotEWObRb/0YwOZhCbO
-i0WpWPgZh3ct/XaEHhbnKYtblya1wVrCN/yur3ck+ru4Mka2lfxouKF9y8kZn2qF
-0CEbR/bbJiOy0Cr+q+LCIOiD+iwKHhju6ks+OqmFwXhglE62b7yKZtcuSddnsimF
-6n0VXkS2Vm5Kg901Sed7QPtVR6EtIBh7WiwU6iLoOofFjOg3B7v3K8hX66Scj5V/
-mz++m2W9BTqkoYRmszPHXnFeF8ZhAzc2ncUWrQIDAQABAoIBAD0Ly5nJmROXRHe4
-8dd2xF2nPApEIUqeCjk7SWK9MDlwwD4Euavl2EThe37xSnDU99gvAhjWyhgMwwyu
-6ndXvyzGd+glJRLdtweKUhgf/3IbItjeXpN7J3Lbb2MenABwOt05jlT4JLVXez/d
-3ChWspU+nwWc0XeENbaA+EB+UVdtOfJhRk5l/LPENHBwEjMgRsbPO+upa4v/YEbj
-GwDaaC1HsI8jqGxSu2IRFqEeEOniPjnAi6SYfl41ZBj+cUrYA3xT94Ksk403iwax
-Ln7FJC2oNo/Okunf6V9ucgzLAYIA/LW/BJ6+dcAzH6OGYuvI6a229W2pEbL7gwiw
-7pouoyECgYEA/T3RIktzeCvVz4g2cy8IkzxBt95tpcJd5weMfyn2qYiQVxPZa9dW
-VE7hKKQAOphSj+38Rg/W1tMefPzk8PcnxdI7hi1RF7xk1rIH6SwLE2rywz7Oh0UG
-5OuL5AOu0rnIXKBWO/o7mPq8ix1eOGRumT29rfKKecIUGwxPagdrJnkCgYEAwslm
-SUwU6KoRoXzm13Q5XcYVkhiwzr05nGuxN5chzpyfLsRBZFJOBCRgnprx2tNANvzx
-9raIw76BUueBZ+9OEnX9QshUdd4/+Mu+Htl3Q7jDOMv4I+CleD3M83dwrLkyUGXc
-iU5+N2xlrg6Zs6MqB59M2t2cppys6OK45+7ZtNUCgYEA7toAR91sUnrd1jk+ShOh
-feWGgprrRj11/fKzxDjbKnng0hCpsDpRdYFUgtV0Vc/Xh2NK3vYPh11m0zJf6Rrk
-S4QHmn1hkAakAY+3QGjHJZBo2liByToEz6OOtQf4O075OMwNqdJRpe9QJ9ISTRQE
-8Mo3/jnV/BNejUhmGrZWjNkCgYAGWHdLlf3sYnX2k9IVXMTtqCFVxBYmdgWlceCx
-FexVBcctx9j1grTl76VyJUwRu+YQpIbhaark7ZTa9Y3CrAoYEd3xSgNuX5In8cM8
-0ArRmvhJJmPsD0p0s1a068Qw7EuWUOsxUexMs+xQEkuxjXQ2EVt4mdWbm+kKITCy
-5ZYUDQKBgHlWfvgXk3G2l8Pa07KW8Eo4Rvsp//L5aKG73kEQfmbD7ArJUX39T98n
-06wuN0yQsMkkgReQ/0q1jebZ1q27GrkIpWLIbL5qpUE9R+2PenHcbcVotvwc0cCk
-rSi8bYoVxJvtLPs08GahDEKgyBE2VL5Zu2kn70ij14uw3Gy4ug+O
+MIIEpgIBAAKCAQEA1oQMN4MZ/wEf4SM7chwHZ+ymQ5Knt45VZ0jmgpnKFx0p+eJo
+NegvvwY/80NWTmcgbGnqruJiOh5AEUNDtCD5G/70oz2WHgZBZkuLsopEa/2sDmwx
+vUbv1f/mD8iHcDaWUvKAy4TUHFeHDQL28HJom9E7bgYadeuhebwZcsbulPFePw+f
+WM7jLWxkMYClfsdzsBrgerbZVPnAuj77cGXZSQ6p96jOPiJ/mjOVCwWJtdlqwme2
+AC4AwKYdWzc3Ysw8lES/ubMa+lP1Eh9aI8edpHIlC5nYNLVTWa4Xw6CtAvqzKtNN
+JzwypbR3fcDXaWvvO3GY3wOHVC/wyCsL8SXc7QIDAQABAoIBAQDUwGX1cHsJ5C2f
+9ndwtsfJlHVZs0vPysR9CVpE0Q4TWoNVJ+0++abRB/vI4lHotHL90xZEmJXfGj1k
+YZf2QHWQBI7Qj7Yg1Qdr0yUbz/IIQLCyJTA3jvEzBvc/VByveBQi9Aw0zOopqc1x
+ZC1RT8bcMumEN11q8mVV/O4oXZAl+mQIbRRt6JIsRtoW8hpB1e2ipHItDMNpSnzA
+6PqcddDyDDePgi5lMOaeV9un60A6pI/+uvmw16R1Io+DyYRnxds3HJ/ccI0Co1P1
+khA75QLdnoniYO+oQrq/wGvm+Uq1seh6iuj+SOWvCdB03vPmGYxPKMSW9AtX8xbJ
+J9lboi3pAoGBAPBaiUYn9F+Zt9oJTHhAimZgs1ub5xVEFwVhYJtFBT3E1rQWRKuf
+kiU1JRq7TB3MGaC4zGi2ql12KV3AqFhwLKG6sKtlo/IJhJfe3DgWmBVYBBifkgYs
+mxmA6opgyjbjDEMn6RA+Jov5H267AsnaB4cCB1Jjra6GIdIoMvPghHZXAoGBAOR6
+7VC6E+YX5VJPCZiN0h0aBT+Hl4drYQKvZHp5N8RIBkvmcQHEJgsrUKdirFZEXW6y
+WvepwI4C/Xl61y64/DZ7rum/gpAEPdzSkefKysHAiqkMRcIpjiRxTPJ547ZJycjP
+E+jzcYfLwQvCW9ZiYl+KdYRbpqBFQC8aWqixFxRbAoGBAJQTsy79vpiHY7V4tRwA
+50NboCR4UE3RvT0bWSFPzILZmk0oyvXRQYCa1Vk6uxJAhCl4sLZyk1MxURrpbs3N
+jjG1itKNtAuRwZavPo1vnhLIPv3MkXIsWQHFYroOF4bpKszU8cmIAMeLm8nkfTtO
+kASlQ02HC6HSEVQgYAPP9svRAoGBANiOnwKl7Bhpy8TQ/zJmMaG9uP23IeuL3l4y
+KdVfsXjMH5OvLqtS5BAwFPkiMGBv2fMC/+/AKK8xrFiJEw3I7d0iK+6Hw1OHga8c
+soh1kOpF+ecyp6fZxU1LSniFCU0M8UHw7Fke7RueBzKDHJK9m6oczTgPuoYsPSKo
+IwfDGjIDAoGBAMJVkInntV8oDPT1WYpOAZ3Z0myCDZVBbjxx8kE4RSJIsFeNSiTO
+nhLWCqoG11PVTUzhpYItCjp4At/dG8OQY7WWm0DJJQB38fEqA6JKWpgeWwUdkk8j
+anCrNUBEuzt3UPSZ17DGCw2+J+mwsg1nevaFIXy0gN2zPtTBWtacznPL
-----END RSA PRIVATE KEY-----
diff --git a/test/ejabberd_admin_test.exs b/test/ejabberd_admin_test.exs
deleted file mode 100644
index 1090eff17..000000000
--- a/test/ejabberd_admin_test.exs
+++ /dev/null
@@ -1,88 +0,0 @@
-# ----------------------------------------------------------------------
-#
-# ejabberd, Copyright (C) 2002-2017 ProcessOne
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 2 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# ----------------------------------------------------------------------
-
-defmodule EjabberdAdminTest do
- use ExUnit.Case, async: false
-
- @author "jsautret@process-one.net"
-
- setup_all do
- :mnesia.start
- :ejabberd_mnesia.start
- # For some myterious reason, :ejabberd_commands.init mays
- # sometimes fails if module is not loaded before
- :ejabberd_config.start(["domain"], [])
- {:module, :ejabberd_commands} = Code.ensure_loaded(:ejabberd_commands)
- :ejabberd_hooks.start_link
- {:ok, _} = :acl.start_link
- {:ok, _} = :ejabberd_access_permissions.start_link()
- :ejabberd_commands.start_link
- :ejabberd_admin.start_link
- :ok
- end
-
- setup do
- :ok
- end
-
- test "Logvel can be set and retrieved" do
- :ejabberd_logger.start()
-
- assert :lager == call_command(:set_loglevel, [1])
- assert {1, :critical, 'Critical'} ==
- call_command(:get_loglevel, [])
-
- assert :lager == call_command(:set_loglevel, [2])
- assert {2, :error, 'Error'} ==
- call_command(:get_loglevel, [])
-
- assert :lager == call_command(:set_loglevel, [3])
- assert {3, :warning, 'Warning'} ==
- call_command(:get_loglevel, [])
-
-# assert {:wrong_loglevel, 6} ==
-# catch_throw call_command(:set_loglevel, [6])
-# assert {3, :warning, 'Warning'} ==
-# call_command(:get_loglevel, [])
-
- assert :lager == call_command(:set_loglevel, [4])
- assert {4, :info, 'Info'} ==
- call_command(:get_loglevel, [])
-
- assert :lager == call_command(:set_loglevel, [5])
- assert {5, :debug, 'Debug'} ==
- call_command(:get_loglevel, [])
-
- assert :lager == call_command(:set_loglevel, [0])
- assert {0, :no_log, 'No log'} ==
- call_command(:get_loglevel, [])
-
- end
-
- defp call_command(name, args) do
- :ejabberd_commands.execute_command2(name, args, %{:caller_module => :ejabberd_ctl})
- end
-
- test "command status works with ejabberd stopped" do
- assert :ejabberd_not_running ==
- elem(call_command(:status, []), 0)
- end
-
-end
diff --git a/test/ejabberd_auth_mock.exs b/test/ejabberd_auth_mock.exs
deleted file mode 100644
index 10daf03dc..000000000
--- a/test/ejabberd_auth_mock.exs
+++ /dev/null
@@ -1,74 +0,0 @@
-# ----------------------------------------------------------------------
-#
-# ejabberd, Copyright (C) 2002-2017 ProcessOne
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 2 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# ----------------------------------------------------------------------
-
-defmodule EjabberdAuthMock do
-
- @author "jsautret@process-one.net"
- @agent __MODULE__
-
- def init do
- try do
- Agent.stop(@agent)
- catch
- :exit, _e -> :ok
- end
-
- {:ok, _pid} = Agent.start_link(fn -> %{} end, name: @agent)
-
- mock(:ejabberd_auth, :user_exists,
- fn (user, domain) ->
- Agent.get(@agent, fn users -> Map.get(users, {user, domain}) end) != nil
- end)
- mock(:ejabberd_auth, :get_password_s,
- fn (user, domain) ->
- Agent.get(@agent, fn users -> Map.get(users, {user, domain}, "") end )
- end)
- mock(:ejabberd_auth, :check_password,
- fn (user, _authzid, domain, password) ->
- Agent.get(@agent, fn users ->
- Map.get(users, {user, domain}) end) == password
- end)
- mock(:ejabberd_auth, :set_password,
- fn (user, domain, password) ->
- Agent.update(@agent, fn users ->
- Map.put(users, {user, domain}, password) end)
- end)
- end
-
- def create_user(user, domain, password) do
- Agent.update(@agent, fn users -> Map.put(users, {user, domain}, password) end)
- end
-
- ####################################################################
- # Helpers
- ####################################################################
-
- # TODO refactor: Move to ejabberd_test_mock
- def mock(module, function, fun) do
- try do
- :meck.new(module)
- catch
- :error, {:already_started, _pid} -> :ok
- end
-
- :meck.expect(module, function, fun)
- end
-
-end
diff --git a/test/ejabberd_hooks_test.exs b/test/ejabberd_hooks_test.exs
deleted file mode 100644
index 90624147c..000000000
--- a/test/ejabberd_hooks_test.exs
+++ /dev/null
@@ -1,203 +0,0 @@
-# ----------------------------------------------------------------------
-#
-# ejabberd, Copyright (C) 2002-2017 ProcessOne
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 2 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# ----------------------------------------------------------------------
-
-# Notes on the tests:
-#
-# This test suite will print out errors in logs for tests:
-#
-# test "Error in run_fold is ignored"
-# test "Throw in run_fold is ignored"
-# test "Exit in run_fold is ignored"
-#
-# Those tests are not failing and we can safely ignore those errors in
-# log as we are exercising hook handler recovery from that situation.
-
-defmodule EjabberdHooksTest do
- use ExUnit.Case, async: false
-
- @author "mremond@process-one.net"
- @host <<"domain.net">>
- @self __MODULE__
-
- setup_all do
- {:ok, _pid} = :ejabberd_hooks.start_link
- :ok
- end
-
- setup do
- :meck.unload
- :true = :ejabberd_hooks.delete_all_hooks
- :ok
- end
-
- test "An anonymous function can be added as a hook" do
- hookname = :test_fun_hook
- :ok = :ejabberd_hooks.add(hookname, @host, fn _ -> :ok end, 50)
- [{50, :undefined, _}] = :ejabberd_hooks.get_handlers(hookname, @host)
- end
-
- test "A module function can be added as a hook" do
- hookname = :test_mod_hook
- callback = :hook_callback
- :ok = :ejabberd_hooks.add(hookname, @host, @self, callback, 40)
- [{40, @self, _callback}] = :ejabberd_hooks.get_handlers(hookname, @host)
- end
-
- test "An anonymous function can be removed from hook handlers" do
- hookname = :test_fun_hook
- anon_fun = fn _ -> :ok end
- :ok = :ejabberd_hooks.add(hookname, @host, anon_fun, 50)
- :ok = :ejabberd_hooks.delete(hookname, @host, anon_fun, 50)
- [] = :ejabberd_hooks.get_handlers(hookname, @host)
- end
-
- test "An module function can be removed from hook handlers" do
- hookname = :test_mod_hook
- callback = :hook_callback
- :ok = :ejabberd_hooks.add(hookname, @host, @self, callback, 40)
- :ok = :ejabberd_hooks.delete(hookname, @host, @self, callback, 40)
- [] = :ejabberd_hooks.get_handlers(hookname, @host)
- # TODO: Check that removed function is not call anymore
- end
-
- test "'Run hook' call registered handler once" do
- test_result = :hook_result
- run_hook([], fn -> test_result end, test_result)
- end
-
- test "'Run hook' can call registered handler with parameters" do
- test_result = :hook_result_with_params
- run_hook([:hook_params], fn _ -> test_result end, test_result)
- end
-
- # TODO test "Several handlers are run in order by hook"
-
- test "Hook run chain is stopped when handler return 'stop'" do
- # setup test
- hookname = :test_mod_hook
- modulename = :hook_module
- mock(modulename, :hook_callback1, fn _ -> :stop end)
- mock(modulename, :hook_callback2, fn _ -> :end_result end)
-
- :ok = :ejabberd_hooks.add(hookname, @host, modulename, :hook_callback1, 40)
- :ok = :ejabberd_hooks.add(hookname, @host, modulename, :hook_callback1, 50)
-
- :ok = :ejabberd_hooks.run(hookname, @host, [:hook_params])
- # callback2 is never run:
- [{_pid, {^modulename, _callback, [:hook_params]}, :stop}] = :meck.history(modulename)
- end
-
- test "Run fold hooks accumulate state in correct order through handlers" do
- # setup test
- hookname = :test_mod_hook
- modulename = :hook_module
- mock(modulename, :hook_callback1, fn(list, user) -> [user|list] end)
- mock(modulename, :hook_callback2, fn(list, _user) -> ["jid2"|list] end)
-
- :ok = :ejabberd_hooks.add(hookname, @host, modulename, :hook_callback1, 40)
- :ok = :ejabberd_hooks.add(hookname, @host, modulename, :hook_callback2, 50)
-
- ["jid2", "jid1"] = :ejabberd_hooks.run_fold(hookname, @host, [], ["jid1"])
- end
-
- test "Hook run_fold are executed based on priority order, not registration order" do
- # setup test
- hookname = :test_mod_hook
- modulename = :hook_module
- mock(modulename, :hook_callback1, fn(_acc) -> :first end)
- mock(modulename, :hook_callback2, fn(_acc) -> :second end)
-
- :ok = :ejabberd_hooks.add(hookname, @host, modulename, :hook_callback2, 50)
- :ok = :ejabberd_hooks.add(hookname, @host, modulename, :hook_callback1, 40)
-
- :second = :ejabberd_hooks.run_fold(hookname, @host, :started, [])
- # Both module have been called:
- 2 = length(:meck.history(modulename))
- end
-
- # TODO: Test with ability to stop and return a value
- test "Hook run_fold chain is stopped when handler return 'stop'" do
- # setup test
- hookname = :test_mod_hook
- modulename = :hook_module
- mock(modulename, :hook_callback1, fn(_acc) -> :stop end)
- mock(modulename, :hook_callback2, fn(_acc) -> :executed end)
-
- :ok = :ejabberd_hooks.add(hookname, @host, modulename, :hook_callback1, 40)
- :ok = :ejabberd_hooks.add(hookname, @host, modulename, :hook_callback2, 50)
-
- :stopped = :ejabberd_hooks.run_fold(hookname, @host, :started, [])
- # Only one module has been called
- [{_pid, {^modulename, :hook_callback1, [:started]}, :stop}] = :meck.history(modulename)
- end
-
- test "Error in run_fold is ignored" do
- run_fold_crash(fn(_acc) -> raise "crashed" end)
- end
-
- test "Throw in run_fold is ignored" do
- run_fold_crash(fn(_acc) -> throw :crashed end)
- end
-
- test "Exit in run_fold is ignored" do
- run_fold_crash(fn(_acc) -> exit :crashed end)
- end
-
- # test for run hook with various number of params
- def run_hook(params, fun, result) do
- # setup test
- hookname = :test_mod_hook
- modulename = :hook_module
- callback = :hook_callback
- mock(modulename, callback, fun)
-
- # Then check
- :ok = :ejabberd_hooks.add(hookname, @host, modulename, callback, 40)
- :ok = :ejabberd_hooks.run(hookname, @host, params)
- [{_pid, {^modulename, ^callback, ^params}, ^result}] = :meck.history(modulename)
- end
-
- def run_fold_crash(crash_fun) do
- # setup test
- hookname = :test_mod_hook
- modulename = :hook_module
- mock(modulename, :hook_callback1, crash_fun)
- mock(modulename, :hook_callback2, fn(_acc) -> :final end)
-
- :ok = :ejabberd_hooks.add(hookname, @host, modulename, :hook_callback1, 40)
- :ok = :ejabberd_hooks.add(hookname, @host, modulename, :hook_callback2, 50)
-
- :final = :ejabberd_hooks.run_fold(hookname, @host, :started, [])
- # Both handlers were called
- 2 = length(:meck.history(modulename))
- end
-
- # TODO refactor: Move to ejabberd_test_mock
- def mock(module, function, fun) do
- try do
- :meck.new(module, [:non_strict])
- catch
- :error, {:already_started, _pid} -> :ok
- end
-
- :meck.expect(module, function, fun)
- end
-
-end
diff --git a/test/ejabberd_oauth_mock.exs b/test/ejabberd_oauth_mock.exs
deleted file mode 100644
index 8f1f11843..000000000
--- a/test/ejabberd_oauth_mock.exs
+++ /dev/null
@@ -1,50 +0,0 @@
-# ----------------------------------------------------------------------
-#
-# ejabberd, Copyright (C) 2002-2017 ProcessOne
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 2 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# ----------------------------------------------------------------------
-
-defmodule EjabberdOauthMock do
-
- @author "jsautret@process-one.net"
-
- def init() do
- :mnesia.start
- :mnesia.create_table(:oauth_token,
- [ram_copies: [node],
- attributes: [:oauth_token, :us, :scope, :expire]])
- :application.start(:cache_tab)
- :cache_tab.new(:oauth_token,
- [{:max_size, 1000}, {:life_time, 3600}])
- end
-
- def get_token(user, domain, command, expiration \\ 3600) do
- now = {megasecs, secs, _} = :os.timestamp
- expire = 1000000 * megasecs + secs + expiration
- :random.seed now
- token = to_string :random.uniform(100000000)
-
- {:ok, _} = :ejabberd_oauth.associate_access_token(token,
- [{"resource_owner",
- {:user, user, domain}},
- {"scope", [to_string command]},
- {"expiry_time", expire}],
- [])
- token
- end
-
-end
diff --git a/test/ejabberd_sm_mock.exs b/test/ejabberd_sm_mock.exs
deleted file mode 100644
index 9ac739ba5..000000000
--- a/test/ejabberd_sm_mock.exs
+++ /dev/null
@@ -1,121 +0,0 @@
-# ----------------------------------------------------------------------
-#
-# ejabberd, Copyright (C) 2002-2017 ProcessOne
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 2 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# ----------------------------------------------------------------------
-
-defmodule EjabberdSmMock do
- @author "jsautret@process-one.net"
-
- require Record
- Record.defrecord :session, Record.extract(:session, from_lib: "ejabberd/include/ejabberd_sm.hrl")
- Record.defrecord :jid, Record.extract(:jid, from_lib: "xmpp/include/jid.hrl")
-
- @agent __MODULE__
-
- def init do
- ModLastMock.init
-
- try do
- Agent.stop(@agent)
- catch
- :exit, _e -> :ok
- end
-
- {:ok, _pid} = Agent.start_link(fn -> [] end, name: @agent)
-
- mock(:ejabberd_sm, :get_user_resources,
- fn (user, domain) -> for s <- get_sessions(user, domain), do: s.resource end)
-
- mock(:ejabberd_sm, :route,
- fn (to, {:exit, _reason}) ->
- user = jid(to, :user)
- domain = jid(to, :server)
- resource = jid(to, :resource)
- disconnect_resource(user, domain, resource)
- :ok
- (_, _) -> :ok
- end)
-
- end
-
- def connect_resource(user, domain, resource,
- opts \\ [priority: 1, conn: :c2s]) do
- Agent.update(@agent, fn sessions ->
- session = %{user: user, domain: domain, resource: resource,
- timestamp: :os.timestamp, pid: self, node: node,
- auth_module: :ejabberd_auth, ip: :undefined,
- priority: opts[:priority], conn: opts[:conn]}
- [session | sessions]
- end)
- end
-
- def disconnect_resource(user, domain, resource) do
- disconnect_resource(user, domain, resource, ModLastMock.now)
- end
-
- def disconnect_resource(user, domain, resource, timestamp) do
- Agent.update(@agent, fn sessions ->
- for s <- sessions,
- s.user != user or s.domain != domain or s.resource != resource, do: s
- end)
- ModLastMock.set_last user, domain, "", timestamp
- end
-
- def get_sessions() do
- Agent.get(@agent, fn sessions -> sessions end)
- end
-
- def get_sessions(user, domain) do
- Agent.get(@agent, fn sessions ->
- for s <- sessions, s.user == user, s.domain == domain, do: s
- end)
- end
-
- def get_session(user, domain, resource) do
- Agent.get(@agent, fn sessions ->
- for s <- sessions,
- s.user == user, s.domain == domain, s.resource == resource, do: s
- end)
- end
-
- def to_record(s) do
- session(usr: {s.user, s.domain, s.ressource},
- us: {s.user, s.domain},
- sid: {s.timestamp, s.pid},
- priority: s.priority,
- info: [conn: s.conn, ip: s.ip, node: s.node,
- oor: false, auth_module: s.auth_module])
- end
-
- ####################################################################
- # Helpers
- ####################################################################
-
-
- # TODO refactor: Move to ejabberd_test_mock
- def mock(module, function, fun) do
- try do
- :meck.new(module)
- catch
- :error, {:already_started, _pid} -> :ok
- end
-
- :meck.expect(module, function, fun)
- end
-
-end
diff --git a/test/elixir_SUITE.erl b/test/elixir_SUITE.erl
deleted file mode 100644
index b869e00f7..000000000
--- a/test/elixir_SUITE.erl
+++ /dev/null
@@ -1,119 +0,0 @@
-%%%-------------------------------------------------------------------
-%%% Author : Mickael Remond <mremond@process-one.net>
-%%% Created : 19 Feb 2015 by Mickael Remond <mremond@process-one.net>
-%%%
-%%%
-%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
-%%%
-%%% This program is free software; you can redistribute it and/or
-%%% modify it under the terms of the GNU General Public License as
-%%% published by the Free Software Foundation; either version 2 of the
-%%% License, or (at your option) any later version.
-%%%
-%%% This program is distributed in the hope that it will be useful,
-%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
-%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%%% General Public License for more details.
-%%%
-%%% You should have received a copy of the GNU General Public License along
-%%% with this program; if not, write to the Free Software Foundation, Inc.,
-%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-%%%
-%%%----------------------------------------------------------------------
-
-%%% This is a common test wrapper to run our ejabberd tests written in
-%%% Elixir from standard common test code.
-%%%
-%%% Example: Is run with:
-%%% ./rebar skip_deps=true ct suites=elixir
-%%% or from ejabber overall test suite:
-%%% make quicktest
-
--module(elixir_SUITE).
-
--compile(export_all).
-
-init_per_suite(Config) ->
- suite:setup_ejabberd_lib_path(Config),
- check_meck(),
- code:add_pathz(filename:join(test_dir(), "../include")),
- Config.
-
-end_per_suite(_Config) ->
- ok.
-
-init_per_testcase(_TestCase, Config) ->
- process_flag(error_handler, ?MODULE),
- Config.
-
-all() ->
- case is_elixir_available() of
- true ->
- Dir = test_dir(),
- filelib:fold_files(Dir, ".*test\.exs$", false,
- fun(Filename, Acc) -> [list_to_atom(filename:basename(Filename)) | Acc] end,
- []);
- false ->
- []
- end.
-
-check_meck() ->
- case catch meck:module_info(module) of
- meck ->
- ok;
- {'EXIT',{undef, _}} ->
- ct:print("meck is not available. Please make sure you configured ejabberd with --enable-elixir --enable-tools"),
- ok
- end.
-
-is_elixir_available() ->
- case catch elixir:module_info() of
- {'EXIT',{undef,_}} ->
- ct:print("ejabberd has not been build with Elixir support, skipping Elixir tests."),
- false;
- ModInfo when is_list(ModInfo) ->
- true
- end.
-
-undefined_function(?MODULE, Func, Args) ->
- case lists:suffix(".exs", atom_to_list(Func)) of
- true ->
- run_elixir_test(Func);
- false ->
- error_handler:undefined_function(?MODULE, Func, Args)
- end;
-undefined_function(Module, Func, Args) ->
- error_handler:undefined_function(Module, Func,Args).
-
-run_elixir_test(Func) ->
- %% Elixir tests can be tagged as follow to be ignored (place before test start)
- %% @tag pending: true
- 'Elixir.ExUnit':start([{exclude, [{pending, true}]},
- {formatters,
- ['Elixir.ExUnit.CLIFormatter',
- 'Elixir.ExUnit.CTFormatter']},
- {autorun, false}]),
-
- filelib:fold_files(test_dir(), ".*mock\.exs\$", true,
- fun (File, N) ->
- 'Elixir.Code':load_file(list_to_binary(File)),
- N+1
- end, 0),
-
- 'Elixir.Code':load_file(list_to_binary(filename:join(test_dir(), atom_to_list(Func)))),
- %% I did not use map syntax, so that this file can still be build under R16
- catch 'Elixir.ExUnit.Server':cases_loaded(),
- ResultMap = 'Elixir.ExUnit':run(),
- case maps:find(failures, ResultMap) of
- {ok, 0} ->
- %% Zero failures
- ok;
- {ok, Failures} ->
- ct:print("Tests failed in module '~s': ~.10B failures.~nSee logs for details", [Func, Failures]),
- ct:fail(elixir_test_failure),
- error
- end.
-
-test_dir() ->
- {ok, CWD} = file:get_cwd(),
- filename:join(CWD, "../../test").
diff --git a/test/jid_test.exs b/test/jid_test.exs
deleted file mode 100644
index aa3563bea..000000000
--- a/test/jid_test.exs
+++ /dev/null
@@ -1,45 +0,0 @@
-# ----------------------------------------------------------------------
-#
-# ejabberd, Copyright (C) 2002-2017 ProcessOne
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 2 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# ----------------------------------------------------------------------
-
-defmodule JidTest do
- @author "mremond@process-one.net"
-
- use ExUnit.Case, async: true
-
- require Record
- Record.defrecord :jid, Record.extract(:jid, from_lib: "xmpp/include/jid.hrl")
-
- setup_all do
- :stringprep.start
- :jid.start
- :ok
- end
-
- test "create a jid from a binary" do
- jid = :jid.from_string("test@localhost/resource")
- assert jid(jid, :user) == "test"
- assert jid(jid, :server) == "localhost"
- assert jid(jid, :resource) == "resource"
- end
-
- test "Check that sending a list to from_string/1 does not crash the jid process" do
- {:error, :need_jid_as_binary} = :jid.from_string('test@localhost/resource')
- end
-end
diff --git a/test/mix_tests.erl b/test/mix_tests.erl
deleted file mode 100644
index 864150661..000000000
--- a/test/mix_tests.erl
+++ /dev/null
@@ -1,154 +0,0 @@
-%%%-------------------------------------------------------------------
-%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% Created : 16 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%%
-%%%
-%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
-%%%
-%%% This program is free software; you can redistribute it and/or
-%%% modify it under the terms of the GNU General Public License as
-%%% published by the Free Software Foundation; either version 2 of the
-%%% License, or (at your option) any later version.
-%%%
-%%% This program is distributed in the hope that it will be useful,
-%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
-%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%%% General Public License for more details.
-%%%
-%%% You should have received a copy of the GNU General Public License along
-%%% with this program; if not, write to the Free Software Foundation, Inc.,
-%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-%%%
-%%%----------------------------------------------------------------------
-
--module(mix_tests).
-
-%% API
--compile(export_all).
--import(suite, [mix_jid/1, mix_room_jid/1, my_jid/1, is_feature_advertised/3,
- disconnect/1, send_recv/2, recv_message/1, send/2,
- put_event/2, get_event/1]).
--include("suite.hrl").
-
-%%%===================================================================
-%%% API
-%%%===================================================================
-%%%===================================================================
-%%% Single user tests
-%%%===================================================================
-single_cases() ->
- {mix_single, [sequence],
- [single_test(feature_enabled)]}.
-
-feature_enabled(Config) ->
- MIX = mix_jid(Config),
- ct:comment("Checking if ~s is set", [?NS_MIX_0]),
- true = is_feature_advertised(Config, ?NS_MIX_0, MIX),
- disconnect(Config).
-
-%%%===================================================================
-%%% Master-slave tests
-%%%===================================================================
-master_slave_cases() ->
- {mix_master_slave, [sequence],
- [master_slave_test(all)]}.
-
-all_master(Config) ->
- MIX = mix_jid(Config),
- Room = mix_room_jid(Config),
- MyJID = my_jid(Config),
- MyBareJID = jid:remove_resource(MyJID),
- #iq{type = result,
- sub_els =
- [#disco_info{
- identities = [#identity{category = <<"conference">>,
- type = <<"text">>}],
- xdata = [#xdata{type = result, fields = XFields}]}]} =
- send_recv(Config, #iq{type = get, to = MIX, sub_els = [#disco_info{}]}),
- true = lists:any(
- fun(#xdata_field{var = <<"FORM_TYPE">>,
- values = [?NS_MIX_SERVICEINFO_0]}) -> true;
- (_) -> false
- end, XFields),
- %% Joining
- Nodes = [?NS_MIX_NODES_MESSAGES, ?NS_MIX_NODES_PRESENCE,
- ?NS_MIX_NODES_PARTICIPANTS, ?NS_MIX_NODES_SUBJECT,
- ?NS_MIX_NODES_CONFIG],
- #iq{type = result,
- sub_els = [#mix_join{subscribe = Nodes, jid = MyBareJID}]} =
- send_recv(Config, #iq{type = set, to = Room,
- sub_els = [#mix_join{subscribe = Nodes}]}),
- #message{from = Room,
- sub_els =
- [#ps_event{
- items = #ps_items{
- node = ?NS_MIX_NODES_PARTICIPANTS,
- items = [#ps_item{
- id = ParticipantID,
- sub_els = [PXML]}]}}]} =
- recv_message(Config),
- #mix_participant{jid = MyBareJID} = xmpp:decode(PXML),
- %% Coming online
- PresenceID = p1_rand:get_string(),
- Presence = xmpp:encode(#presence{}),
- #iq{type = result,
- sub_els =
- [#pubsub{
- publish = #ps_publish{
- node = ?NS_MIX_NODES_PRESENCE,
- items = [#ps_item{id = PresenceID}]}}]} =
- send_recv(
- Config,
- #iq{type = set, to = Room,
- sub_els =
- [#pubsub{
- publish = #ps_publish{
- node = ?NS_MIX_NODES_PRESENCE,
- items = [#ps_item{
- id = PresenceID,
- sub_els = [Presence]}]}}]}),
- #message{from = Room,
- sub_els =
- [#ps_event{
- items = #ps_items{
- node = ?NS_MIX_NODES_PRESENCE,
- items = [#ps_item{
- id = PresenceID,
- sub_els = [Presence]}]}}]} =
- recv_message(Config),
- %% Coming offline
- send(Config, #presence{type = unavailable, to = Room}),
- %% Receiving presence retract event
- #message{from = Room,
- sub_els = [#ps_event{
- items = #ps_items{
- node = ?NS_MIX_NODES_PRESENCE,
- retract = PresenceID}}]} =
- recv_message(Config),
- %% Leaving
- #iq{type = result, sub_els = []} =
- send_recv(Config, #iq{type = set, to = Room, sub_els = [#mix_leave{}]}),
- #message{from = Room,
- sub_els =
- [#ps_event{
- items = #ps_items{
- node = ?NS_MIX_NODES_PARTICIPANTS,
- retract = ParticipantID}}]} =
- recv_message(Config),
- put_event(Config, disconnect),
- disconnect(Config).
-
-all_slave(Config) ->
- disconnect = get_event(Config),
- disconnect(Config).
-
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
-single_test(T) ->
- list_to_atom("mix_" ++ atom_to_list(T)).
-
-master_slave_test(T) ->
- {list_to_atom("mix_" ++ atom_to_list(T)), [parallel],
- [list_to_atom("mix_" ++ atom_to_list(T) ++ "_master"),
- list_to_atom("mix_" ++ atom_to_list(T) ++ "_slave")]}.
diff --git a/test/mod_admin_extra_test.exs b/test/mod_admin_extra_test.exs
deleted file mode 100644
index 7b1bc21de..000000000
--- a/test/mod_admin_extra_test.exs
+++ /dev/null
@@ -1,374 +0,0 @@
-# ----------------------------------------------------------------------
-#
-# ejabberd, Copyright (C) 2002-2017 ProcessOne
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 2 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# ----------------------------------------------------------------------
-
-defmodule EjabberdModAdminExtraTest do
- use ExUnit.Case, async: false
-
- require EjabberdAuthMock
- require EjabberdSmMock
- require ModLastMock
- require ModRosterMock
-
- @author "jsautret@process-one.net"
-
- @user "user"
- @domain "domain"
- @password "password"
- @resource "resource"
-
- require Record
- Record.defrecord :jid, Record.extract(:jid, from_lib: "xmpp/include/jid.hrl")
-
- setup_all do
- try do
- :jid.start
- :stringprep.start
- :mnesia.start
- :ejabberd_mnesia.start
- :p1_sha.load_nif
- :ejabberd_hooks.start_link
- rescue
- _ -> :ok
- end
- :ok = :ejabberd_config.start(["domain"], [])
- :gen_mod.start_link
- :acl.start_link
- :ejabberd_access_permissions.start_link()
- :ejabberd_commands.start_link
- :mod_admin_extra.start(@domain, [])
- :ejabberd_hooks.start_link
- :ok
- end
-
- setup do
- :meck.unload
- EjabberdAuthMock.init
- EjabberdSmMock.init
- ModRosterMock.init(@domain, :mod_admin_extra)
- :ok
- end
-
- ###################### Accounts
- test "check_account works" do
- EjabberdAuthMock.create_user @user, @domain, @password
-
- assert call_command(:check_account, [@user, @domain])
- refute call_command(:check_account, [@user, "bad_domain"])
- refute call_command(:check_account, ["bad_user", @domain])
-
- assert :meck.validate :ejabberd_auth
- end
-
- test "check_password works" do
-
- EjabberdAuthMock.create_user @user, @domain, @password
-
- assert call_command(:check_password,
- [@user, @domain, @password])
- refute call_command(:check_password,
- [@user, @domain, "bad_password"])
- refute call_command(:check_password,
- [@user, "bad_domain", @password])
- refute call_command(:check_password,
- ["bad_user", @domain, @password])
-
- assert :meck.validate :ejabberd_auth
-
- end
-
- test "check_password_hash works" do
-
- EjabberdAuthMock.create_user @user, @domain, @password
- hash = "5F4DCC3B5AA765D61D8327DEB882CF99" # echo -n password|md5
-
- assert call_command(:check_password_hash,
- [@user, @domain, hash, "md5"])
- refute call_command(:check_password_hash,
- [@user, @domain, "bad_hash", "md5"])
- refute call_command(:check_password_hash,
- [@user, "bad_domain", hash, "md5"])
- refute call_command(:check_password_hash,
- ["bad_user", @domain, hash, "md5"])
-
- hash = "5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8" # echo -n password|shasum
- assert call_command(:check_password_hash,
- [@user, @domain, hash, "sha"])
-
- assert :unkown_hash_method ==
- catch_throw call_command(:check_password_hash,
- [@user, @domain, hash, "bad_method"])
-
- assert :meck.validate :ejabberd_auth
-
- end
-
- test "set_password works" do
- EjabberdAuthMock.create_user @user, @domain, @password
-
- assert call_command(:change_password,
- [@user, @domain, "new_password"])
- refute call_command(:check_password,
- [@user, @domain, @password])
- assert call_command(:check_password,
- [@user, @domain, "new_password"])
- assert {:not_found, 'unknown_user'} ==
- catch_throw call_command(:change_password,
- ["bad_user", @domain,
- @password])
- assert :meck.validate :ejabberd_auth
- end
-
- ###################### Sessions
-
- test "num_resources works" do
- assert 0 == call_command(:num_resources,
- [@user, @domain])
-
- EjabberdSmMock.connect_resource @user, @domain, @resource
- assert 1 == call_command(:num_resources,
- [@user, @domain])
-
- EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
- assert 2 == call_command(:num_resources,
- [@user, @domain])
-
- EjabberdSmMock.connect_resource @user<>"1", @domain, @resource
- assert 2 == call_command(:num_resources,
- [@user, @domain])
-
- EjabberdSmMock.disconnect_resource @user, @domain, @resource
- assert 1 == call_command(:num_resources,
- [@user, @domain])
-
- assert :meck.validate :ejabberd_sm
- end
-
- test "resource_num works" do
- EjabberdSmMock.connect_resource @user, @domain, @resource<>"3"
- EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
- EjabberdSmMock.connect_resource @user, @domain, @resource<>"1"
-
- assert :bad_argument ==
- elem(catch_throw(call_command(:resource_num,
- [@user, @domain, 0])), 0)
- assert @resource<>"1" ==
- call_command(:resource_num, [@user, @domain, 1])
- assert @resource<>"3" ==
- call_command(:resource_num, [@user, @domain, 3])
- assert :bad_argument ==
- elem(catch_throw(call_command(:resource_num,
- [@user, @domain, 4])), 0)
- assert :meck.validate :ejabberd_sm
- end
-
- test "kick_session works" do
- EjabberdSmMock.connect_resource @user, @domain, @resource<>"1"
- EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
- EjabberdSmMock.connect_resource @user, @domain, @resource<>"3"
-
- assert 3 == length EjabberdSmMock.get_sessions @user, @domain
- assert 1 == length EjabberdSmMock.get_session @user, @domain, @resource<>"2"
-
- assert :ok ==
- call_command(:kick_session,
- [@user, @domain,
- @resource<>"2", "kick"])
-
- assert 2 == length EjabberdSmMock.get_sessions @user, @domain
- assert 0 == length EjabberdSmMock.get_session @user, @domain, @resource<>"2"
-
- assert :meck.validate :ejabberd_sm
- end
-
- ###################### Last
-
- test "get_last works" do
-
- assert {_, 'NOT FOUND'} =
- call_command(:get_last, [@user, @domain])
-
- EjabberdSmMock.connect_resource @user, @domain, @resource<>"1"
- EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
-
- assert {_, 'ONLINE'} =
- call_command(:get_last, [@user, @domain])
-
- EjabberdSmMock.disconnect_resource @user, @domain, @resource<>"1"
-
- assert {_, 'ONLINE'} =
- call_command(:get_last, [@user, @domain])
-
- now = {megasecs, secs, _microsecs} = :os.timestamp
- timestamp = megasecs * 1000000 + secs
- EjabberdSmMock.disconnect_resource(@user, @domain, @resource<>"2",
- timestamp)
- {{year, month, day}, {hour, minute, second}} = :calendar.now_to_universal_time now
- result = IO.iodata_to_binary(:io_lib.format(
- "~w-~.2.0w-~.2.0wT~.2.0w:~.2.0w:~.2.0wZ",
- [year, month, day, hour, minute, second]))
- assert {result, ""} ==
- call_command(:get_last, [@user, @domain])
-
- assert :meck.validate :mod_last
- end
-
- ###################### Roster
-
- @tag :skip
- test "add_rosteritem and delete_rosteritem work" do
- # Connect user
- # Add user1 & user2 to user's roster
- # Remove user1 & user2 from user's roster
-
- EjabberdSmMock.connect_resource @user, @domain, @resource
-
- assert [] == ModRosterMock.get_roster(@user, @domain)
-
- assert :ok ==
- call_command(:add_rosteritem, [@user, @domain,
- @user<>"1", @domain,
- "nick1",
- "group1",
- "both"])
- # Check that user1 is the only item of the user's roster
- result = ModRosterMock.get_roster(@user, @domain)
- assert 1 == length result
- [{{@user, @domain, jid}, opts}] = result
- assert @user<>"1@"<>@domain == jid
- assert "nick1" == opts.nick
- assert ["group1"] == opts.groups
- assert :both == opts.subs
-
- # Check that the item roster user1 was pushed with subscription
- # 'both' to user online ressource
- jid = :jlib.make_jid(@user, @domain, @resource)
- assert 1 ==
- :meck.num_calls(:ejabberd_sm, :route,
- [jid,
- {:item, {@user<>"1", @domain, ""}, :both}])
-
- assert :ok ==
- call_command(:add_rosteritem, [@user, @domain,
- @user<>"2", @domain,
- "nick2",
- "group2",
- "both"])
- result = ModRosterMock.get_roster(@user, @domain)
- assert 2 == length result
-
-
- # Check that the item roster user2 was pushed with subscription
- # 'both' to user online ressource
- assert 1 ==
- :meck.num_calls(:ejabberd_sm, :route,
- [jid,
- {:item, {@user<>"2", @domain, ""}, :both}])
-
-
- call_command(:delete_rosteritem, [@user, @domain,
- @user<>"1", @domain])
- result = ModRosterMock.get_roster(@user, @domain)
- assert 1 == length result
- [{{@user, @domain, jid}, opts}] = result
- assert @user<>"2@"<>@domain == jid
- assert "nick2" == opts.nick
- assert ["group2"] == opts.groups
- assert :both == opts.subs
-
- # Check that the item roster user1 was pushed with subscription
- # 'none' to user online ressource
- jid = :jlib.make_jid(@user, @domain, @resource)
- assert 1 ==
- :meck.num_calls(:ejabberd_sm, :route,
- [jid,
- {:item, {@user<>"1", @domain, ""}, :none}])
-
- call_command(:delete_rosteritem, [@user, @domain,
- @user<>"2", @domain])
-
- # Check that the item roster user2 was pushed with subscription
- # 'none' to user online ressource
- assert 1 ==
- :meck.num_calls(:ejabberd_sm, :route,
- [jid,
- {:item, {@user<>"2", @domain, ""}, :none}])
-
- # Check that nothing else was pushed to user resource
- jid = jid(user: @user, server: @domain, resource: :_,
- luser: @user, lserver: @domain, lresource: :_)
- assert 4 ==
- :meck.num_calls(:ejabberd_sm, :route,
- [jid,
- {:item, :_, :_}])
-
- assert [] == ModRosterMock.get_roster(@user, @domain)
- assert :meck.validate :ejabberd_sm
-
- end
-
- @tag :skip
- test "get_roster works" do
- assert [] == ModRosterMock.get_roster(@user, @domain)
- assert [] == call_command(:get_roster, [@user, @domain],
- :admin)
-
- assert :ok ==
- call_command(:add_rosteritem, [@user, @domain,
- @user<>"1", @domain,
- "nick1",
- "group1",
- "both"])
- assert [{@user<>"1@"<>@domain, "", 'both', 'none', "group1"}] ==
- call_command(:get_roster, [@user, @domain], :admin)
- assert :ok ==
- call_command(:add_rosteritem, [@user, @domain,
- @user<>"2", @domain,
- "nick2",
- "group2",
- "none"])
- result = call_command(:get_roster, [@user, @domain], :admin)
- assert 2 == length result
- assert Enum.member?(result, {@user<>"1@"<>@domain, "", 'both', 'none', "group1"})
- assert Enum.member?(result, {@user<>"2@"<>@domain, "", 'none', 'none', "group2"})
-
- end
-
- defp call_command(name, args) do
- :ejabberd_commands.execute_command2(name, args, %{:caller_module => :ejabberd_ctl})
- end
-
- defp call_command(name, args, mode) do
- call_command(name, args)
- end
-
-# kick_user command is defined in ejabberd_sm, move to extra?
-# test "kick_user works" do
-# assert 0 == call_command(:num_resources,
-# [@user, @domain])
-# EjabberdSmMock.connect_resource(@user, @domain, @resource<>"1")
-# EjabberdSmMock.connect_resource(@user, @domain, @resource<>"2")
-# assert 2 ==
-# call_command(:kick_user, [@user, @domain])
-# assert 0 == call_command(:num_resources,
-# [@user, @domain])
-# assert :meck.validate :ejabberd_sm
-# end
-
-end
diff --git a/test/mod_http_api_mock_test.exs b/test/mod_http_api_mock_test.exs
deleted file mode 100644
index ceda2bb0f..000000000
--- a/test/mod_http_api_mock_test.exs
+++ /dev/null
@@ -1,270 +0,0 @@
-# ----------------------------------------------------------------------
-#
-# ejabberd, Copyright (C) 2002-2017 ProcessOne
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 2 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# ----------------------------------------------------------------------
-
-defmodule ModHttpApiMockTest do
- use ExUnit.Case, async: false
-
- @author "jsautret@process-one.net"
-
- # Admin user
- @admin "admin"
- @adminpass "adminpass"
- # Non admin user
- @user "user"
- @userpass "userpass"
- # XMPP domain
- @domain "domain"
- # mocked command
- @command "command_test"
- @acommand String.to_atom(@command)
- # default API version
- @version 0
-
- require Record
- Record.defrecord :request, Record.extract(:request, from_lib: "ejabberd/include/ejabberd_http.hrl")
-
- setup_all do
- try do
- :jid.start
- :mnesia.start
- :ejabberd_mnesia.start
- :stringprep.start
- :ejabberd_hooks.start_link
- :ejabberd_config.start([@domain], [])
- {:ok, _} = :ejabberd_access_permissions.start_link()
- :ejabberd_commands.start_link
- rescue
- _ -> :ok
- end
- :mod_http_api.start(@domain, [])
- EjabberdOauthMock.init
- :ok
- end
-
- setup do
- :meck.unload
- :meck.new :ejabberd_commands
- :meck.new(:acl, [:passthrough]) # Need to fake acl to allow oauth
- EjabberdAuthMock.init
- :ok
- end
-
- test "HTTP GET simple command call with Basic Auth" do
- EjabberdAuthMock.create_user @user, @domain, @userpass
-
- # Mock a simple command() -> :ok
- :meck.expect(:ejabberd_commands, :get_command_format,
- fn (@acommand, %{usr: {@user, @domain, _}}, @version) ->
- {[], {:res, :rescode}}
- end)
- :meck.expect(:ejabberd_commands, :get_exposed_commands,
- fn () -> [@acommand] end)
- :meck.expect(:ejabberd_commands, :execute_command2,
- fn (@acommand, [], %{usr: {@user, @domain, _}}, @version) ->
- :ok
- end)
-
- :ejabberd_config.add_local_option(:commands, [[{:add_commands, [@acommand]}]])
-
- # Correct Basic Auth call
- req = request(method: :GET,
- path: ["api", @command],
- q: [nokey: ""],
- # Basic auth
- auth: {@user<>"@"<>@domain, @userpass},
- ip: {{127,0,0,1},60000},
- host: @domain)
- result = :mod_http_api.process([@command], req)
-
- # history = :meck.history(:ejabberd_commands)
-
- assert 200 == elem(result, 0) # HTTP code
- assert "0" == elem(result, 2) # command result
-
- # Bad password
- req = request(method: :GET,
- path: ["api", @command],
- q: [nokey: ""],
- # Basic auth
- auth: {@user<>"@"<>@domain, @userpass<>"bad"},
- ip: {{127,0,0,1},60000},
- host: @domain)
- result = :mod_http_api.process([@command], req)
- assert 401 == elem(result, 0) # HTTP code
-
- # Check that the command was executed only once
- assert 1 ==
- :meck.num_calls(:ejabberd_commands, :execute_command2, :_)
-
- assert :meck.validate :ejabberd_auth
- assert :meck.validate :ejabberd_commands
- end
-
- test "HTTP GET simple command call with OAuth" do
- EjabberdAuthMock.create_user @user, @domain, @userpass
-
- # Mock a simple command() -> :ok
- :meck.expect(:ejabberd_commands, :get_command_format,
- fn (@acommand, %{usr: {@user, @domain, _}}, @version) ->
- {[], {:res, :rescode}}
- end)
- :meck.expect(:ejabberd_commands, :get_exposed_commands,
- fn () -> [@acommand] end)
- :meck.expect(:ejabberd_commands, :execute_command2,
- fn (@acommand, [], %{usr: {@user, @domain, _}, oauth_scope: ["ejabberd:user"]}, @version) ->
- :ok
- (@acommand, [], %{usr: {@user, @domain, _}, oauth_scope: [@command]}, @version) ->
- :ok
- (@acommand, [], %{usr: {@user, @domain, _}, oauth_scope: _}, @version) ->
- throw({:error, :access_rules_unauthorized})
- end)
-
-
- # Correct OAuth call using specific scope
- token = EjabberdOauthMock.get_token @user, @domain, @command
- req = request(method: :GET,
- path: ["api", @command],
- q: [nokey: ""],
- # OAuth
- auth: {:oauth, token, []},
- ip: {{127,0,0,1},60000},
- host: @domain)
- result = :mod_http_api.process([@command], req)
- assert 200 == elem(result, 0) # HTTP code
- assert "0" == elem(result, 2) # command result
-
- # Correct OAuth call using specific ejabberd:user scope
- token = EjabberdOauthMock.get_token @user, @domain, "ejabberd:user"
- req = request(method: :GET,
- path: ["api", @command],
- q: [nokey: ""],
- # OAuth
- auth: {:oauth, token, []},
- ip: {{127,0,0,1},60000},
- host: @domain)
- result = :mod_http_api.process([@command], req)
- assert 200 == elem(result, 0) # HTTP code
- assert "0" == elem(result, 2) # command result
-
- # Wrong OAuth token
- req = request(method: :GET,
- path: ["api", @command],
- q: [nokey: ""],
- # OAuth
- auth: {:oauth, "bad"<>token, []},
- ip: {{127,0,0,1},60000},
- host: @domain)
- result = :mod_http_api.process([@command], req)
- assert 401 == elem(result, 0) # HTTP code
-
- # Expired OAuth token
- token = EjabberdOauthMock.get_token @user, @domain, @command, 1
- :timer.sleep 1500
- req = request(method: :GET,
- path: ["api", @command],
- q: [nokey: ""],
- # OAuth
- auth: {:oauth, token, []},
- ip: {{127,0,0,1},60000},
- host: @domain)
- result = :mod_http_api.process([@command], req)
- assert 401 == elem(result, 0) # HTTP code
-
- # Wrong OAuth scope
- token = EjabberdOauthMock.get_token @user, @domain, "bad_command"
- :timer.sleep 1500
- req = request(method: :GET,
- path: ["api", @command],
- q: [nokey: ""],
- # OAuth
- auth: {:oauth, token, []},
- ip: {{127,0,0,1},60000},
- host: @domain)
- result = :mod_http_api.process([@command], req)
- assert 403 == elem(result, 0) # HTTP code
-
- # Check that the command was executed twice
- assert 3 ==
- :meck.num_calls(:ejabberd_commands, :execute_command2, :_)
-
- assert :meck.validate :ejabberd_auth
- #assert :meck.validate :ejabberd_commands
- #assert :ok = :meck.history(:ejabberd_commands)
- end
-
- test "Request oauth token, resource owner password credentials" do
- EjabberdAuthMock.create_user @user, @domain, @userpass
- :application.set_env(:oauth2, :backend, :ejabberd_oauth)
- :application.start(:oauth2)
-
- # Mock a simple command() -> :ok
- :meck.expect(:ejabberd_commands, :get_command_format,
- fn (@acommand, {@user, @domain, {:oauth, _token}, false}, @version) ->
- {[], {:res, :rescode}}
- end)
- :meck.expect(:ejabberd_commands, :get_exposed_commands,
- fn () -> [@acommand] end)
-
- #Mock acl to allow oauth authorizations
- :meck.expect(:acl, :match_rule, fn(_Server, _Access, _Jid) -> :allow end)
-
-
- # Correct password
- req = request(method: :POST,
- path: ["oauth", "token"],
- q: [{"grant_type", "password"}, {"scope", @command}, {"username", @user<>"@"<>@domain}, {"ttl", "4000"}, {"password", @userpass}],
- ip: {{127,0,0,1},60000},
- host: @domain)
- result = :ejabberd_oauth.process([], req)
- assert 200 = elem(result, 0) #http code
- {kv} = :jiffy.decode(elem(result,2))
- assert {_, "bearer"} = List.keyfind(kv, "token_type", 0)
- assert {_, @command} = List.keyfind(kv, "scope", 0)
- assert {_, 4000} = List.keyfind(kv, "expires_in", 0)
- {"access_token", _token} = List.keyfind(kv, "access_token", 0)
-
- #missing grant_type
- req = request(method: :POST,
- path: ["oauth", "token"],
- q: [{"scope", @command}, {"username", @user<>"@"<>@domain}, {"password", @userpass}],
- ip: {{127,0,0,1},60000},
- host: @domain)
- result = :ejabberd_oauth.process([], req)
- assert 400 = elem(result, 0) #http code
- {kv} = :jiffy.decode(elem(result,2))
- assert {_, "unsupported_grant_type"} = List.keyfind(kv, "error", 0)
-
-
- # incorrect user/pass
- req = request(method: :POST,
- path: ["oauth", "token"],
- q: [{"grant_type", "password"}, {"scope", @command}, {"username", @user<>"@"<>@domain}, {"password", @userpass<>"aa"}],
- ip: {{127,0,0,1},60000},
- host: @domain)
- result = :ejabberd_oauth.process([], req)
- assert 400 = elem(result, 0) #http code
- {kv} = :jiffy.decode(elem(result,2))
- assert {_, "invalid_grant"} = List.keyfind(kv, "error", 0)
-
- assert :meck.validate :ejabberd_auth
- assert :meck.validate :ejabberd_commands
- end
-
-end
diff --git a/test/mod_http_api_test.exs b/test/mod_http_api_test.exs
deleted file mode 100644
index 29405a3ec..000000000
--- a/test/mod_http_api_test.exs
+++ /dev/null
@@ -1,127 +0,0 @@
-# ----------------------------------------------------------------------
-#
-# ejabberd, Copyright (C) 2002-2017 ProcessOne
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 2 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# ----------------------------------------------------------------------
-
-defmodule ModHttpApiTest do
- @author "mremond@process-one.net"
-
- use ExUnit.Case, async: true
-
- require Record
- Record.defrecord :request, Record.extract(:request, from_lib: "ejabberd/include/ejabberd_http.hrl")
- Record.defrecord :ejabberd_commands, Record.extract(:ejabberd_commands, from_lib: "ejabberd/include/ejabberd_commands.hrl")
-
- setup_all do
- :ok = :mnesia.start
- :ejabberd_mnesia.start
- :stringprep.start
- :ejabberd_hooks.start_link
- :ok = :ejabberd_config.start(["localhost"], [])
- :acl.start_link
- {:ok, _} = :ejabberd_access_permissions.start_link()
- {:ok, _} = :ejabberd_commands.start_link
- :ok = :ejabberd_commands.register_commands(cmds)
- on_exit fn ->
- :meck.unload
- unregister_commands(cmds) end
- end
-
- test "We can expose several commands to API at a time" do
- setup_mocks()
- assert :ok == :ejabberd_commands.expose_commands([:open_cmd, :user_cmd])
- commands = :ejabberd_commands.get_exposed_commands()
- assert Enum.member?(commands, :open_cmd)
- assert Enum.member?(commands, :user_cmd)
- end
-
-# test "We can call open commands without authentication" do
-# setup_mocks()
-# :ejabberd_commands.expose_commands([:open_cmd])
-# request = request(method: :POST, ip: {{127,0,0,1},50000}, data: "[]")
-# {200, _, _} = :mod_http_api.process(["open_cmd"], request)
-# end
-
- # This related to the commands config file option
- test "Attempting to access a command that is not exposed as HTTP API returns 403" do
- setup_mocks()
- assert :ok == :ejabberd_commands.expose_commands([])
- request = request(method: :POST, ip: {{127,0,0,1},50000}, data: "[]")
- {403, _, _} = :mod_http_api.process(["open_cmd"], request)
- end
-
- test "Call to user, admin or restricted commands without authentication are rejected" do
- setup_mocks()
- assert :ok == :ejabberd_commands.expose_commands([:user_cmd, :admin_cmd, :restricted])
- request = request(method: :POST, ip: {{127,0,0,1},50000}, data: "[]")
- {403, _, _} = :mod_http_api.process(["user_cmd"], request)
- {403, _, _} = :mod_http_api.process(["admin_cmd"], request)
- {403, _, _} = :mod_http_api.process(["restricted_cmd"], request)
- end
-
- @tag pending: true
- test "If admin_ip_access is enabled, we can call restricted API without authentication from that IP" do
- setup_mocks()
- end
-
- # Define a set of test commands that we expose through API
- # We define one for each policy type
- defp cmds do
- [:open, :user, :admin, :restricted]
- |> Enum.map(&({&1, String.to_atom(to_string(&1) <> "_cmd")}))
- |> Enum.map(fn({cmd_type, cmd}) ->
- ejabberd_commands(name: cmd, tags: [:test],
- policy: cmd_type,
- module: __MODULE__,
- function: cmd,
- args: [],
- result: {:res, :rescode})
- end)
- end
-
- def open_cmd, do: :ok
- def user_cmd(_, _), do: :ok
- def admin_cmd, do: :ok
- def restricted_cmd, do: :ok
-
- defp setup_mocks() do
- :meck.unload
- mock(:gen_mod, :get_module_opt,
- fn (_server, :mod_http_api, _admin_ip_access, _, _) ->
- [{:allow, [{:ip, {{127,0,0,2}, 32}}]}]
- end)
- end
-
- defp mock(module, function, fun) do
- try do
- :meck.new(module)
- catch
- :error, {:already_started, _pid} -> :ok
- end
- :meck.expect(module, function, fun)
- end
-
- defp unregister_commands(commands) do
- try do
- :ejabberd_commands.unregister_commands(commands)
- catch
- _,_ -> :ok
- end
- end
-
-end
diff --git a/test/mod_last_mock.exs b/test/mod_last_mock.exs
deleted file mode 100644
index 25f2bd473..000000000
--- a/test/mod_last_mock.exs
+++ /dev/null
@@ -1,79 +0,0 @@
-# ----------------------------------------------------------------------
-#
-# ejabberd, Copyright (C) 2002-2017 ProcessOne
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 2 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# ----------------------------------------------------------------------
-
-defmodule ModLastMock do
-
- require Record
- Record.defrecord :session, Record.extract(:session, from_lib: "ejabberd/include/ejabberd_sm.hrl")
- Record.defrecord :jid, Record.extract(:jid, from_lib: "xmpp/include/jid.hrl")
-
- @author "jsautret@process-one.net"
- @agent __MODULE__
-
- def init do
- try do
- Agent.stop(@agent)
- catch
- :exit, _e -> :ok
- end
-
- {:ok, _pid} = Agent.start_link(fn -> %{} end, name: @agent)
-
- mock(:mod_last, :get_last_info,
- fn (user, domain) ->
- Agent.get(@agent, fn last ->
- case Map.get(last, {user, domain}, :not_found) do
- {ts, status} -> {:ok, ts, status}
- result -> result
- end
- end)
- end)
- end
-
- def set_last(user, domain, status) do
- set_last(user, domain, status, now)
- end
-
- def set_last(user, domain, status, timestamp) do
- Agent.update(@agent, fn last ->
- Map.put(last, {user, domain}, {timestamp, status})
- end)
- end
-
- ####################################################################
- # Helpers
- ####################################################################
- def now() do
- {megasecs, secs, _microsecs} = :os.timestamp
- megasecs * 1000000 + secs
- end
-
- # TODO refactor: Move to ejabberd_test_mock
- def mock(module, function, fun) do
- try do
- :meck.new(module)
- catch
- :error, {:already_started, _pid} -> :ok
- end
-
- :meck.expect(module, function, fun)
- end
-
-end
diff --git a/test/mod_roster_mock.exs b/test/mod_roster_mock.exs
deleted file mode 100644
index 70f273898..000000000
--- a/test/mod_roster_mock.exs
+++ /dev/null
@@ -1,159 +0,0 @@
-# ----------------------------------------------------------------------
-#
-# ejabberd, Copyright (C) 2002-2017 ProcessOne
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; either version 2 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# ----------------------------------------------------------------------
-
-defmodule ModRosterMock do
- @author "jsautret@process-one.net"
-
- require Record
- Record.defrecord :roster, Record.extract(:roster, from_lib: "ejabberd/include/mod_roster.hrl")
- Record.defrecord :roster_version, Record.extract(:roster_version, from_lib: "ejabberd/include/mod_roster.hrl")
-
- @agent __MODULE__
-
- def init(domain, module) do
- try do
- Agent.stop(@agent)
- catch
- :exit, _e -> :ok
- end
-
- {:ok, _pid} = Agent.start_link(fn -> %{} end, name: @agent)
-
- mock_with_meck
-
- :ejabberd_mnesia.create(:mod_roster_mnesia, :roster,
- [ram_copies: [node()],
- attributes: Keyword.keys(roster(roster())),
- index: [:us]])
- :ejabberd_mnesia.create(:mod_roster_mnesia, :roster_version,
- [ram_copies: [node()],
- attributes: Keyword.keys(roster_version(roster_version()))])
- #:mod_roster.stop(domain)
- :gen_mod.start_module(domain, :mod_roster)
- end
-
- def mock_with_meck do
-# mock(:gen_mod, :db_type,
-# fn (_server, :mod_roster_mnesia) ->
-# :mnesia
-# end)
-#
-# mock(:mnesia, :transaction,
-# fn (_server, function) ->
-# {:atomic, function.()}
-# end)
-#
-# mock(:mnesia, :write,
-# fn (Item) ->
-# throw Item
-# {:atomic, :ok}
-# end)
-
- mock(:mod_roster_mnesia, :init,
- fn (_server, _opts) ->
- :ok
- end)
- mock(:mod_roster_mnesia, :transaction,
- fn (_server, function) ->
- {:atomic, function.()}
- end)
-
- mock(:mod_roster_mnesia, :update_roster_t,
- fn (user, domain, {u, d, _r}, item) ->
- add_roster_item(user, domain, u<>"@"<>d,
- roster(item, :name),
- roster(item, :subscription),
- roster(item, :groups),
- roster(item, :ask),
- roster(item, :askmessage))
- end)
-
- mock(:mod_roster_mnesia, :invalidate_roster_cache,
- fn (_user, _server) ->
- :ok
- end)
-
- end
-
- def add_roster_item(user, domain, jid, nick, subs \\ :none, groups \\ [],
- ask \\ :none, askmessage \\ "")
- when is_binary(user) and byte_size(user) > 0
- and is_binary(domain) and byte_size(domain) > 0
- and is_binary(jid) and byte_size(jid) > 0
- and is_binary(nick)
- and is_atom(subs)
- and is_list(groups)
- and is_atom(ask)
- and is_binary(askmessage)
- do
- Agent.update(@agent, fn roster ->
- Map.put(roster, {user, domain, jid}, %{nick: nick,
- subs: subs, groups: groups,
- ask: ask, askmessage: askmessage})
- end)
- end
-
- def remove_roster_item(user, domain, jid) do
- Agent.update(@agent, fn roster ->
- Map.delete(roster, {user, domain, jid})
- end)
- end
-
- def get_rosters() do
- Agent.get(@agent, fn roster -> roster end)
- end
-
- def get_roster(user, domain) do
- Agent.get(@agent, fn roster ->
- for {u, d, jid} <- Map.keys(roster), u == user, d == domain,
- do: {{u, d, jid}, Map.fetch!(roster, {u, d, jid})}
- end)
- end
-
- def to_record({{user, domain, jid}, r}) do
- roster(usj: {user, domain, jid},
- us: {user, domain},
- jid: :jid.from_string(jid),
- subscription: r.subs,
- ask: r.ask,
- groups: r.groups,
- askmessage: r.askmessage
- )
- end
- def to_records(rosters) do
- for item <- rosters, do: to_record(item)
- end
-
-####################################################################
-# Helpers
-####################################################################
-
- # TODO refactor: Move to ejabberd_test_mock
- def mock(module, function, fun) do
- try do
- :meck.new(module, [:non_strict, :passthrough, :unstick])
- catch
- :error, {:already_started, _pid} -> :ok
- end
-
- :meck.expect(module, function, fun)
- end
-
-end
diff --git a/test/private_tests.erl b/test/private_tests.erl
new file mode 100644
index 000000000..506608670
--- /dev/null
+++ b/test/private_tests.erl
@@ -0,0 +1,105 @@
+%%%-------------------------------------------------------------------
+%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% Created : 23 Nov 2018 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2018 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(private_tests).
+
+%% API
+-compile(export_all).
+-import(suite, [my_jid/1, is_feature_advertised/3,
+ send_recv/2, disconnect/1]).
+
+-include("suite.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%%===================================================================
+%%% Single user tests
+%%%===================================================================
+single_cases() ->
+ {private_single, [sequence],
+ [single_test(test_features),
+ single_test(test_no_namespace),
+ single_test(test_set_get),
+ single_test(test_published)]}.
+
+test_features(Config) ->
+ MyJID = my_jid(Config),
+ true = is_feature_advertised(Config, ?NS_BOOKMARKS_CONVERSION_0,
+ jid:remove_resource(MyJID)),
+ disconnect(Config).
+
+test_no_namespace(Config) ->
+ WrongEl = #xmlel{name = <<"wrong">>},
+ #iq{type = error} =
+ send_recv(Config, #iq{type = get,
+ sub_els = [#private{sub_els = [WrongEl]}]}),
+ disconnect(Config).
+
+test_set_get(Config) ->
+ Storage = bookmark_storage(),
+ StorageXMLOut = xmpp:encode(Storage),
+ #iq{type = result, sub_els = []} =
+ send_recv(
+ Config, #iq{type = set,
+ sub_els = [#private{sub_els = [StorageXMLOut]}]}),
+ #iq{type = result,
+ sub_els = [#private{sub_els = [StorageXMLIn]}]} =
+ send_recv(
+ Config,
+ #iq{type = get,
+ sub_els = [#private{sub_els = [xmpp:encode(
+ #bookmark_storage{})]}]}),
+ Storage = xmpp:decode(StorageXMLIn),
+ disconnect(Config).
+
+test_published(Config) ->
+ Storage = bookmark_storage(),
+ Node = xmpp:get_ns(Storage),
+ #iq{type = result,
+ sub_els = [#pubsub{items = #ps_items{node = Node, items = Items}}]} =
+ send_recv(
+ Config,
+ #iq{type = get,
+ sub_els = [#pubsub{items = #ps_items{node = Node}}]}),
+ [#ps_item{sub_els = [StorageXMLIn]}] = Items,
+ Storage = xmpp:decode(StorageXMLIn),
+ #iq{type = result, sub_els = []} =
+ send_recv(Config,
+ #iq{type = set,
+ sub_els = [#pubsub_owner{delete = {Node, <<>>}}]}),
+ disconnect(Config).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+ list_to_atom("private_" ++ atom_to_list(T)).
+
+conference_bookmark() ->
+ #bookmark_conference{
+ name = <<"Some name">>,
+ autojoin = true,
+ jid = jid:make(<<"some">>, <<"some.conference.org">>)}.
+
+bookmark_storage() ->
+ #bookmark_storage{conference = [conference_bookmark()]}.
diff --git a/test/suite.erl b/test/suite.erl
index efe587253..62c394a55 100644
--- a/test/suite.erl
+++ b/test/suite.erl
@@ -55,6 +55,7 @@ init_config(Config) ->
CfgContent = process_config_tpl(CfgContentTpl, [
{c2s_port, 5222},
{loglevel, 4},
+ {new_schema, false},
{s2s_port, 5269},
{component_port, 5270},
{web_port, 5280},
@@ -471,7 +472,9 @@ wait_auth_SASL_result(Config, ShouldFail) ->
NS = if Type == client -> ?NS_CLIENT;
Type == server -> ?NS_SERVER
end,
- receive #stream_start{xmlns = NS, version = {1,0}} -> ok end,
+ Config2 = receive #stream_start{id = ID, xmlns = NS, version = {1,0}} ->
+ set_opt(stream_id, ID, Config)
+ end,
receive #stream_features{sub_els = Fs} ->
if Type == client ->
#xmpp_session{optional = true} =
@@ -488,7 +491,7 @@ wait_auth_SASL_result(Config, ShouldFail) ->
set_opt(rosterver, true, ConfigAcc);
(_, ConfigAcc) ->
ConfigAcc
- end, Config, Fs)
+ end, Config2, Fs)
end;
#sasl_challenge{text = ClientIn} ->
{Response, SASL} = (?config(sasl, Config))(ClientIn),
diff --git a/test/suite.hrl b/test/suite.hrl
index adc14e93a..b2515d8ce 100644
--- a/test/suite.hrl
+++ b/test/suite.hrl
@@ -66,12 +66,14 @@
end)()).
-define(match(Pattern, Result),
+ (fun() ->
case Result of
Pattern ->
- Pattern;
+ ok;
Mismatch ->
suite:match_failure([Mismatch], [??Pattern])
- end).
+ end
+ end)()).
-define(COMMON_VHOST, <<"localhost">>).
-define(MNESIA_VHOST, <<"mnesia.localhost">>).
diff --git a/test/test_helper.exs b/test/test_helper.exs
deleted file mode 100644
index 454f2338a..000000000
--- a/test/test_helper.exs
+++ /dev/null
@@ -1,7 +0,0 @@
-Code.require_file "ejabberd_auth_mock.exs", __DIR__
-Code.require_file "ejabberd_oauth_mock.exs", __DIR__
-Code.require_file "ejabberd_sm_mock.exs", __DIR__
-Code.require_file "mod_last_mock.exs", __DIR__
-Code.require_file "mod_roster_mock.exs", __DIR__
-
-ExUnit.start
diff --git a/tools/xml_compress_gen.erl b/tools/xml_compress_gen.erl
new file mode 100644
index 000000000..4dad71a43
--- /dev/null
+++ b/tools/xml_compress_gen.erl
@@ -0,0 +1,417 @@
+%% File : xml_compress_gen.erl
+%% Author : Pawel Chmielowski
+%% Purpose :
+%% Created : 14 Sep 2018 Pawel Chmielowski
+%%
+%%
+%% ejabberd, Copyright (C) 2002-2018 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(xml_compress_gen).
+-author("pawel@process-one.net").
+
+-include("xmpp.hrl").
+
+%% API
+-export([archive_analyze/3, process_stats/1, gen_code/3]).
+
+-record(el_stats, {count = 0, empty_count = 0, only_text_count = 0, attrs = #{}, text_stats = #{}}).
+-record(attr_stats, {count = 0, vals = #{}}).
+
+archive_analyze(Host, Table, EHost) ->
+ case ejabberd_sql:sql_query(Host, <<"select username, peer, kind, xml from ", Table/binary>>) of
+ {selected, _, Res} ->
+ lists:foldl(
+ fun([U, P, K, X], Stats) ->
+ M = case K of
+ <<"groupchat">> ->
+ U;
+ _ ->
+ <<U/binary, "@", EHost/binary>>
+ end,
+ El = fxml_stream:parse_element(X),
+ analyze_element({El, <<"stream">>, <<"jabber:client">>, M, P}, Stats)
+ end, {0, #{}}, Res);
+ _ ->
+ none
+ end.
+
+encode_id(Num) when Num < 64 ->
+ iolist_to_binary(io_lib:format("~p:8", [Num])).
+
+gen_code(_File, _Rules, $<) ->
+ {error, <<"Invalid version">>};
+gen_code(File, Rules, Ver) when Ver < 64 ->
+ {Data, _} = lists:foldl(
+ fun({Ns, El, Attrs, Text}, {Acc, Id}) ->
+ NsC = case lists:keyfind(Ns, 1, Acc) of
+ false -> [];
+ {_, L} -> L
+ end,
+ {AttrsE, _} = lists:mapfoldl(
+ fun({AName, AVals}, Id2) ->
+ {AD, Id3} = lists:mapfoldl(
+ fun(AVal, Id3) ->
+ {{AVal, encode_id(Id3)}, Id3 + 1}
+ end, Id2, AVals),
+ {{AName, AD ++ [encode_id(Id3)]}, Id3 + 1}
+ end, 3, Attrs),
+ {TextE, Id5} = lists:mapfoldl(
+ fun(TextV, Id4) ->
+ {{TextV, encode_id(Id4)}, Id4 + 1}
+ end, Id + 1, Text),
+ {lists:keystore(Ns, 1, Acc, {Ns, NsC ++ [{El, encode_id(Id), AttrsE, TextE}]}), Id5}
+ end, {[], 5}, Rules),
+ {ok, Dev} = file:open(File, write),
+ Mod = filename:basename(File, ".erl"),
+ io:format(Dev, "-module(~s).~n-export([encode/3, decode/3]).~n~n", [Mod]),
+ RulesS = iolist_to_binary(io_lib:format("~p", [Rules])),
+ RulesS2 = binary:replace(RulesS, <<"\n">>, <<"\n% ">>, [global]),
+ io:format(Dev, "% This file was generated by xml_compress_gen~n%~n"
+ "% Rules used:~n%~n% ~s~n~n", [RulesS2]),
+ VerId = iolist_to_binary(io_lib:format("~p:8", [Ver])),
+ gen_encode(Dev, Data, VerId),
+ gen_decode(Dev, Data, VerId),
+ file:close(Dev),
+ Data.
+
+gen_decode(Dev, Data, VerId) ->
+ io:format(Dev, "decode(<<$<, _/binary>> = Data, _J1, _J2) ->~n"
+ " fxml_stream:parse_element(Data);~n"
+ "decode(<<~s, Rest/binary>>, J1, J2) ->~n"
+ " {El, _} = decode(Rest, <<\"jabber:client\">>, J1, J2),~n"
+ " El.~n~n", [VerId]),
+ io:format(Dev, "decode_string(Data) ->~n"
+ " case Data of~n"
+ " <<0:2, L:6, Str:L/binary, Rest/binary>> ->~n"
+ " {Str, Rest};~n"
+ " <<1:2, L1:6, 0:2, L2:6, Rest/binary>> ->~n"
+ " L = L2*64 + L1,~n"
+ " <<Str:L/binary, Rest2/binary>> = Rest,~n"
+ " {Str, Rest2};~n"
+ " <<1:2, L1:6, 1:2, L2:6, L3:8, Rest/binary>> ->~n"
+ " L = (L3*64 + L2)*64 + L1,~n"
+ " <<Str:L/binary, Rest2/binary>> = Rest,~n"
+ " {Str, Rest2}~n"
+ " end.~n~n", []),
+ io:format(Dev, "decode_child(<<1:8, Rest/binary>>, _PNs, _J1, _J2) ->~n"
+ " {Text, Rest2} = decode_string(Rest),~n"
+ " {{xmlcdata, Text}, Rest2};~n", []),
+ io:format(Dev, "decode_child(<<2:8, Rest/binary>>, PNs, J1, J2) ->~n"
+ " {Name, Rest2} = decode_string(Rest),~n"
+ " {Attrs, Rest3} = decode_attrs(Rest2),~n"
+ " {Children, Rest4} = decode_children(Rest3, PNs, J1, J2),~n"
+ " {{xmlel, Name, Attrs, Children}, Rest4};~n", []),
+ io:format(Dev, "decode_child(<<3:8, Rest/binary>>, PNs, J1, J2) ->~n"
+ " {Name, Rest2} = decode_string(Rest),~n"
+ " {Ns, Rest3} = decode_string(Rest2),~n"
+ " {Attrs, Rest4} = decode_attrs(Rest3),~n"
+ " {Children, Rest5} = decode_children(Rest4, Ns, J1, J2),~n"
+ " {{xmlel, Name, add_ns(PNs, Ns, Attrs), Children}, Rest5};~n", []),
+ io:format(Dev, "decode_child(<<4:8, Rest/binary>>, _PNs, _J1, _J2) ->~n"
+ " {stop, Rest};~n", []),
+ io:format(Dev, "decode_child(Other, PNs, J1, J2) ->~n"
+ " decode(Other, PNs, J1, J2).~n~n", []),
+ io:format(Dev, "decode_children(Data, PNs, J1, J2) ->~n"
+ " prefix_map(fun(Data2) -> decode(Data2, PNs, J1, J2) end, Data).~n~n", []),
+ io:format(Dev, "decode_attr(<<1:8, Rest/binary>>) ->~n"
+ " {Name, Rest2} = decode_string(Rest),~n"
+ " {Val, Rest3} = decode_string(Rest2),~n"
+ " {{Name, Val}, Rest3};~n", []),
+ io:format(Dev, "decode_attr(<<2:8, Rest/binary>>) ->~n"
+ " {stop, Rest}.~n~n", []),
+ io:format(Dev, "decode_attrs(Data) ->~n"
+ " prefix_map(fun decode_attr/1, Data).~n~n", []),
+ io:format(Dev, "prefix_map(F, Data) ->~n"
+ " prefix_map(F, Data, []).~n~n", []),
+ io:format(Dev, "prefix_map(F, Data, Acc) ->~n"
+ " case F(Data) of~n"
+ " {stop, Rest} ->~n"
+ " {lists:reverse(Acc), Rest};~n"
+ " {Val, Rest} ->~n"
+ " prefix_map(F, Rest, [Val | Acc])~n"
+ " end.~n~n", []),
+ io:format(Dev, "add_ns(Ns, Ns, Attrs) ->~n"
+ " Attrs;~n"
+ "add_ns(_, Ns, Attrs) ->~n"
+ " [{<<\"xmlns\">>, Ns} | Attrs].~n~n", []),
+ lists:foreach(
+ fun({Ns, Els}) ->
+ lists:foreach(
+ fun({Name, Id, Attrs, Text}) ->
+ io:format(Dev, "decode(<<~s, Rest/binary>>, PNs, J1, J2) ->~n"
+ " Ns = ~p,~n", [Id, Ns]),
+ case Attrs of
+ [] ->
+ io:format(Dev, " {Attrs, Rest2} = decode_attrs(Rest),~n", []);
+ _ ->
+ io:format(Dev, " {Attrs, Rest2} = prefix_map(fun~n", []),
+ lists:foreach(
+ fun({AName, AVals}) ->
+ lists:foreach(
+ fun({j1, AId}) ->
+ io:format(Dev, " (<<~s, Rest3/binary>>) ->~n"
+ " {{~p, J1}, Rest3};~n", [AId, AName]);
+ ({j2, AId}) ->
+ io:format(Dev, " (<<~s, Rest3/binary>>) ->~n"
+ " {{~p, J2}, Rest3};~n", [AId, AName]);
+ ({{j1}, AId}) ->
+ io:format(Dev, " (<<~s, Rest3/binary>>) ->~n"
+ " {AVal, Rest4} = decode_string(Rest3),~n"
+ " {{~p, <<J1/binary, AVal/binary>>}, Rest4};~n",
+ [AId, AName]);
+ ({{j2}, AId}) ->
+ io:format(Dev, " (<<~s, Rest3/binary>>) ->~n"
+ " {AVal, Rest4} = decode_string(Rest3),~n"
+ " {{~p, <<J2/binary, AVal/binary>>}, Rest4};~n",
+ [AId, AName]);
+ ({AVal, AId}) ->
+ io:format(Dev, " (<<~s, Rest3/binary>>) ->~n"
+ " {{~p, ~p}, Rest3};~n",
+ [AId, AName, AVal]);
+ (AId) ->
+ io:format(Dev, " (<<~s, Rest3/binary>>) ->~n"
+ " {AVal, Rest4} = decode_string(Rest3),~n"
+ " {{~p, AVal}, Rest4};~n",
+ [AId, AName])
+ end, AVals)
+ end, Attrs),
+ io:format(Dev, " (<<2:8, Rest3/binary>>) ->~n"
+ " {stop, Rest3};~n"
+ " (Data) ->~n"
+ " decode_attr(Data)~n"
+ " end, Rest),~n", [])
+ end,
+ case Text of
+ [] ->
+ io:format(Dev, " {Children, Rest6} = decode_children(Rest2, Ns, J1, J2),~n", []);
+ _ ->
+ io:format(Dev, " {Children, Rest6} = prefix_map(fun", []),
+ lists:foreach(
+ fun({TextS, TId}) ->
+ io:format(Dev, " (<<~s, Rest5/binary>>) ->~n"
+ " {{xmlcdata, ~p}, Rest5};~n",
+ [TId, TextS])
+ end, Text),
+
+ io:format(Dev, " (Other) ->~n"
+ " decode_child(Other, Ns, J1, J2)~n"
+ " end, Rest2),~n", [])
+ end,
+ io:format(Dev, " {{xmlel, ~p, add_ns(PNs, Ns, Attrs), Children}, Rest6};~n", [Name])
+ end, Els)
+ end, Data),
+ io:format(Dev, "decode(Other, PNs, J1, J2) ->~n"
+ " decode_child(Other, PNs, J1, J2).~n~n", []).
+
+
+gen_encode(Dev, Data, VerId) ->
+ io:format(Dev, "encode(El, J1, J2) ->~n"
+ " encode_child(El, <<\"jabber:client\">>,~n"
+ " J1, J2, byte_size(J1), byte_size(J2), <<~s>>).~n~n", [VerId]),
+ io:format(Dev, "encode_attr({<<\"xmlns\">>, _}, Acc) ->~n"
+ " Acc;~n"
+ "encode_attr({N, V}, Acc) ->~n"
+ " <<Acc/binary, 1:8, (encode_string(N))/binary,~n"
+ " (encode_string(V))/binary>>.~n~n", []),
+ io:format(Dev, "encode_attrs(Attrs, Acc) ->~n"
+ " lists:foldl(fun encode_attr/2, Acc, Attrs).~n~n", []),
+ io:format(Dev, "encode_el(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->~n"
+ " E1 = if~n"
+ " PNs == Ns -> encode_attrs(Attrs, <<Pfx/binary, 2:8, (encode_string(Name))/binary>>);~n"
+ " true -> encode_attrs(Attrs, <<Pfx/binary, 3:8, "
+ "(encode_string(Ns))/binary, (encode_string(Name))/binary>>)~n"
+ " end,~n"
+ " E2 = encode_children(Children, Ns, J1, J2, J1L, J2L, <<E1/binary, 2:8>>),~n"
+ " <<E2/binary, 4:8>>.~n~n", []),
+ io:format(Dev, "encode_child({xmlel, Name, Attrs, Children}, PNs, J1, J2, J1L, J2L, Pfx) ->~n"
+ " case lists:keyfind(<<\"xmlns\">>, 1, Attrs) of~n"
+ " false ->~n"
+ " encode(PNs, PNs, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx);~n"
+ " {_, Ns} ->~n"
+ " encode(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx)~n"
+ " end;~n"
+ "encode_child({xmlcdata, Data}, _PNs, _J1, _J2, _J1L, _J2L, Pfx) ->~n"
+ " <<Pfx/binary, 1:8, (encode_string(Data))/binary>>.~n~n", []),
+ io:format(Dev, "encode_children(Children, PNs, J1, J2, J1L, J2L, Pfx) ->~n"
+ " lists:foldl(~n"
+ " fun(Child, Acc) ->~n"
+ " encode_child(Child, PNs, J1, J2, J1L, J2L, Acc)~n"
+ " end, Pfx, Children).~n~n", []),
+ io:format(Dev, "encode_string(Data) ->~n"
+ " <<V1:4, V2:6, V3:6>> = <<(byte_size(Data)):16/unsigned-big-integer>>,~n"
+ " case {V1, V2, V3} of~n"
+ " {0, 0, V3} ->~n"
+ " <<V3:8, Data/binary>>;~n"
+ " {0, V2, V3} ->~n"
+ " <<(V3 bor 64):8, V2:8, Data/binary>>;~n"
+ " _ ->~n"
+ " <<(V3 bor 64):8, (V2 bor 64):8, V1:8, Data/binary>>~n"
+ " end.~n~n", []),
+ lists:foreach(
+ fun({Ns, Els}) ->
+ io:format(Dev, "encode(PNs, ~p = Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->~n"
+ " case Name of~n", [Ns]),
+ lists:foreach(
+ fun({ElN, Id, Attrs, Text}) ->
+ io:format(Dev, " ~p ->~n", [ElN]),
+ case Attrs of
+ [] ->
+ io:format(Dev, " E = encode_attrs(Attrs, <<Pfx/binary, ~s>>),~n", [Id]);
+ _ ->
+ io:format(Dev, " E = lists:foldl(fun~n", []),
+ lists:foreach(
+ fun({AName, AVals}) ->
+ case AVals of
+ [AIdS] when is_binary(AIdS) ->
+ io:format(Dev, " ({~p, AVal}, Acc) ->~n"
+ " <<Acc/binary, ~s, (encode_string(AVal))/binary>>;~n",
+ [AName, AIdS]);
+ _ ->
+ io:format(Dev, " ({~p, AVal}, Acc) ->~n"
+ " case AVal of~n", [AName]),
+ lists:foreach(
+ fun({j1, AId}) ->
+ io:format(Dev, " J1 -> <<Acc/binary, ~s>>;~n",
+ [AId]);
+ ({j2, AId}) ->
+ io:format(Dev, " J2 -> <<Acc/binary, ~s>>;~n",
+ [AId]);
+ ({{j1}, AId}) ->
+ io:format(Dev, " <<J1:J1L/binary, Rest/binary>> -> "
+ "<<Acc/binary, ~s, (encode_string(Rest))/binary>>;~n",
+ [AId]);
+ ({{j2}, AId}) ->
+ io:format(Dev, " <<J2:J2L/binary, Rest/binary>> -> "
+ "<<Acc/binary, ~s, (encode_string(Rest))/binary>>;~n",
+ [AId]);
+ ({AVal, AId}) ->
+ io:format(Dev, " ~p -> <<Acc/binary, ~s>>;~n",
+ [AVal, AId]);
+ (AId) ->
+ io:format(Dev, " _ -> <<Acc/binary, ~s, "
+ "(encode_string(AVal))/binary>>~n",
+ [AId])
+ end, AVals),
+ io:format(Dev, " end;~n", [])
+ end
+ end, Attrs),
+ io:format(Dev, " (Attr, Acc) -> encode_attr(Attr, Acc)~n", []),
+ io:format(Dev, " end, <<Pfx/binary, ~s>>, Attrs),~n", [Id])
+ end,
+ case Text of
+ [] ->
+ io:format(Dev, " E2 = encode_children(Children, Ns, "
+ "J1, J2, J1L, J2L, <<E/binary, 2:8>>),~n", []);
+ _ ->
+ io:format(Dev, " E2 = lists:foldl(fun~n", []),
+ lists:foreach(
+ fun({TextV, TId}) ->
+ io:format(Dev, " ({xmlcdata, ~p}, Acc) -> <<Acc/binary, ~s>>;~n", [TextV, TId])
+ end, Text),
+ io:format(Dev, " (El, Acc) -> encode_child(El, Ns, J1, J2, J1L, J2L, Acc)~n", []),
+ io:format(Dev, " end, <<E/binary, 2:8>>, Children),~n", [])
+ end,
+ io:format(Dev, " <<E2/binary, 4:8>>;~n", [])
+ end, Els),
+ io:format(Dev, " _ -> encode_el(PNs, Ns, Name, Attrs, Children, "
+ "J1, J2, J1L, J2L, Pfx)~nend;~n", [])
+ end, Data),
+ io:format(Dev, "encode(PNs, Ns, Name, Attrs, Children, J1, J2, J1L, J2L, Pfx) ->~n"
+ " encode_el(PNs, Ns, Name, Attrs, Children, "
+ "J1, J2, J1L, J2L, Pfx).~n~n", []).
+
+process_stats({_Counts, Stats}) ->
+ SStats = lists:sort(
+ fun({_, #el_stats{count = C1}}, {_, #el_stats{count = C2}}) ->
+ C1 >= C2
+ end, maps:to_list(Stats)),
+ lists:map(
+ fun({Name, #el_stats{count = C, attrs = A, text_stats = T}}) ->
+ [Ns, El] = binary:split(Name, <<"<">>),
+ Attrs = lists:filtermap(
+ fun({AN, #attr_stats{count = AC, vals = AV}}) ->
+ if
+ AC*5 < C ->
+ false;
+ true ->
+ AVC = AC div min(maps:size(AV)*2, 10),
+ AVA = [N || {N, C2} <- maps:to_list(AV), C2 > AVC],
+ {true, {AN, AVA}}
+ end
+ end, maps:to_list(A)),
+ Text = [TE || {TE, TC} <- maps:to_list(T), TC > C/2],
+ {Ns, El, Attrs, Text}
+ end, SStats).
+
+analyze_elements(Elements, Stats, PName, PNS, J1, J2) ->
+ lists:foldl(fun analyze_element/2, Stats, lists:map(fun(V) -> {V, PName, PNS, J1, J2} end, Elements)).
+
+maps_update(Key, F, InitVal, Map) ->
+ case maps:is_key(Key, Map) of
+ true ->
+ maps:update_with(Key, F, Map);
+ _ ->
+ maps:put(Key, F(InitVal), Map)
+ end.
+
+analyze_element({{xmlcdata, Data}, PName, PNS, _J1, _J2}, {ElCount, Stats}) ->
+ Stats2 = maps_update(<<PNS/binary, "<", PName/binary>>,
+ fun(#el_stats{text_stats = TS} = E) ->
+ TS2 = maps_update(Data, fun(C) -> C + 1 end, 0, TS),
+ E#el_stats{text_stats = TS2}
+ end, #el_stats{}, Stats),
+ {ElCount, Stats2};
+analyze_element({#xmlel{name = Name, attrs = Attrs, children = Children}, _PName, PNS, J1, J2}, {ElCount, Stats}) ->
+ XMLNS = case lists:keyfind(<<"xmlns">>, 1, Attrs) of
+ {_, NS} ->
+ NS;
+ false ->
+ PNS
+ end,
+ NStats = maps_update(<<XMLNS/binary, "<", Name/binary>>,
+ fun(#el_stats{count = C, empty_count = EC, only_text_count = TC, attrs = A} = ES) ->
+ A2 = lists:foldl(
+ fun({<<"xmlns">>, _}, AMap) ->
+ AMap;
+ ({AName, AVal}, AMap) ->
+ J1S = size(J1),
+ J2S = size(J2),
+ AVal2 = case AVal of
+ J1 ->
+ j1;
+ J2 ->
+ j2;
+ <<J1:J1S/binary, _Rest/binary>> ->
+ {j1};
+ <<J2:J2S/binary, _Rest/binary>> ->
+ {j2};
+ Other ->
+ Other
+ end,
+ maps_update(AName, fun(#attr_stats{count = AC, vals = AV}) ->
+ AV2 = maps_update(AVal2, fun(C2) -> C2 + 1 end, 0, AV),
+ #attr_stats{count = AC + 1, vals = AV2}
+ end, #attr_stats{}, AMap)
+ end, A, Attrs),
+ ES#el_stats{count = C + 1,
+ empty_count = if Children == [] -> EC + 1; true ->
+ EC end,
+ only_text_count = case Children of [{xmlcdata, _}] -> TC + 1; _ -> TC end,
+ attrs = A2}
+ end, #el_stats{}, Stats),
+ analyze_elements(Children, {ElCount + 1, NStats}, Name, XMLNS, J1, J2).
diff --git a/vars.config.in b/vars.config.in
index 9878ee2b4..a20151be6 100644
--- a/vars.config.in
+++ b/vars.config.in
@@ -39,7 +39,6 @@
{riak, @riak@}.
{redis, @redis@}.
{elixir, @elixir@}.
-{iconv, @iconv@}.
{stun, @stun@}.
{sip, @sip@}.