diff options
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 -========================== - -[](https://travis-ci.org/processone/ejabberd) [](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 +========================== + +[](https://travis-ci.org/processone/ejabberd) [](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 @@ -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 @@ -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@}. |