summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE15
-rw-r--r--.github/PULL_REQUEST_TEMPLATE20
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml28
-rw-r--r--.travis/mysql_repo_key.asc96
-rw-r--r--Makefile.in4
-rw-r--r--README6
-rw-r--r--cover.spec5
-rw-r--r--ejabberd.service.template2
-rw-r--r--ejabberd.yml.example72
-rwxr-xr-xejabberdctl.template152
-rw-r--r--include/ejabberd_commands.hrl5
-rw-r--r--include/ejabberd_http.hrl7
-rw-r--r--include/ejabberd_sql_pt.hrl33
-rw-r--r--include/jlib.hrl4
-rw-r--r--include/logger.hrl7
-rw-r--r--include/mod_announce.hrl5
-rw-r--r--include/mod_caps.hrl4
-rw-r--r--include/mod_carboncopy.hrl4
-rw-r--r--include/mod_irc.hrl15
-rw-r--r--include/mod_last.hrl3
-rw-r--r--include/mod_mam.hrl15
-rw-r--r--include/mod_private.hrl4
-rw-r--r--include/mod_shared_roster.hrl5
-rw-r--r--include/mod_vcard.hrl8
-rw-r--r--include/mod_vcard_xupdate.hrl2
-rw-r--r--include/ns.hrl8
-rw-r--r--include/pubsub.hrl2
-rw-r--r--lib/ct_formatter.ex130
-rw-r--r--lib/ejabberd.ex2
-rw-r--r--mix.exs51
-rw-r--r--mix.lock28
-rw-r--r--priv/msgs/cs.msg30
-rw-r--r--priv/msgs/de.msg16
-rw-r--r--priv/msgs/de.po37
-rw-r--r--priv/msgs/gl.po248
-rw-r--r--priv/msgs/he.msg1
-rw-r--r--priv/msgs/ja.msg28
-rw-r--r--priv/msgs/pl.msg12
-rw-r--r--priv/msgs/pt-br.msg84
-rw-r--r--priv/msgs/pt-br.po75
-rw-r--r--priv/msgs/uk.msg48
-rw-r--r--priv/msgs/wa.msg128
-rw-r--r--priv/msgs/zh.msg140
-rw-r--r--rebar.config110
-rw-r--r--rebar.config.script27
-rw-r--r--sql/lite.sql17
-rw-r--r--sql/mssql.sqlbin49868 -> 20928 bytes
-rw-r--r--sql/mysql.sql9
-rw-r--r--sql/pg.sql5
-rw-r--r--src/acl.erl36
-rw-r--r--src/adhoc.erl12
-rw-r--r--src/cyrsasl.erl6
-rw-r--r--src/cyrsasl_anonymous.erl5
-rw-r--r--src/cyrsasl_digest.erl14
-rw-r--r--src/cyrsasl_plain.erl13
-rw-r--r--src/cyrsasl_scram.erl3
-rw-r--r--src/ejabberd.erl2
-rw-r--r--src/ejabberd_admin.erl21
-rw-r--r--src/ejabberd_app.erl51
-rw-r--r--src/ejabberd_auth.erl40
-rw-r--r--src/ejabberd_auth_anonymous.erl12
-rw-r--r--src/ejabberd_auth_external.erl54
-rw-r--r--src/ejabberd_auth_internal.erl28
-rw-r--r--src/ejabberd_auth_ldap.erl12
-rw-r--r--src/ejabberd_auth_pam.erl14
-rw-r--r--src/ejabberd_auth_riak.erl28
-rw-r--r--src/ejabberd_auth_sql.erl (renamed from src/ejabberd_auth_odbc.erl)239
-rw-r--r--src/ejabberd_c2s.erl389
-rw-r--r--src/ejabberd_captcha.erl2
-rw-r--r--src/ejabberd_commands.erl320
-rw-r--r--src/ejabberd_commands_doc.erl11
-rw-r--r--src/ejabberd_config.erl128
-rw-r--r--src/ejabberd_ctl.erl146
-rw-r--r--src/ejabberd_frontend_socket.erl16
-rw-r--r--src/ejabberd_http.erl13
-rw-r--r--src/ejabberd_http_bind.erl52
-rw-r--r--src/ejabberd_http_ws.erl20
-rw-r--r--src/ejabberd_local.erl17
-rw-r--r--src/ejabberd_logger.erl56
-rw-r--r--src/ejabberd_oauth.erl10
-rw-r--r--src/ejabberd_piefxis.erl115
-rw-r--r--src/ejabberd_rdbms.erl34
-rw-r--r--src/ejabberd_receiver.erl30
-rw-r--r--src/ejabberd_riak.erl25
-rw-r--r--src/ejabberd_riak_sup.erl43
-rw-r--r--src/ejabberd_router.erl111
-rw-r--r--src/ejabberd_s2s.erl10
-rw-r--r--src/ejabberd_s2s_in.erl46
-rw-r--r--src/ejabberd_s2s_out.erl66
-rw-r--r--src/ejabberd_service.erl30
-rw-r--r--src/ejabberd_sm.erl127
-rw-r--r--src/ejabberd_sm_redis.erl4
-rw-r--r--src/ejabberd_sm_sql.erl (renamed from src/ejabberd_sm_odbc.erl)38
-rw-r--r--src/ejabberd_socket.erl16
-rw-r--r--src/ejabberd_sql.erl (renamed from src/ejabberd_odbc.erl)320
-rw-r--r--src/ejabberd_sql_pt.erl544
-rw-r--r--src/ejabberd_sql_sup.erl (renamed from src/ejabberd_odbc_sup.erl)44
-rw-r--r--src/ejabberd_stun.erl4
-rw-r--r--src/ejabberd_system_monitor.erl12
-rw-r--r--src/ejabberd_web_admin.erl47
-rw-r--r--src/ejabberd_websocket.erl6
-rw-r--r--src/ejabberd_xmlrpc.erl8
-rw-r--r--src/ejd2sql.erl (renamed from src/ejd2odbc.erl)53
-rw-r--r--src/elixir_logger_backend.erl122
-rw-r--r--src/ext_mod.erl9
-rw-r--r--src/gen_mod.erl72
-rw-r--r--src/jd2ejd.erl14
-rw-r--r--src/jid.erl10
-rw-r--r--src/jlib.erl54
-rw-r--r--src/mod_adhoc.erl9
-rw-r--r--src/mod_admin_extra.erl204
-rw-r--r--src/mod_announce.erl437
-rw-r--r--src/mod_announce_mnesia.erl129
-rw-r--r--src/mod_announce_riak.erl87
-rw-r--r--src/mod_announce_sql.erl132
-rw-r--r--src/mod_blocking.erl277
-rw-r--r--src/mod_blocking_mnesia.erl85
-rw-r--r--src/mod_blocking_riak.erl98
-rw-r--r--src/mod_blocking_sql.erl87
-rw-r--r--src/mod_caps.erl184
-rw-r--r--src/mod_caps_mnesia.erl73
-rw-r--r--src/mod_caps_riak.erl38
-rw-r--r--src/mod_caps_sql.erl71
-rw-r--r--src/mod_carboncopy.erl71
-rw-r--r--src/mod_carboncopy_mnesia.erl68
-rw-r--r--src/mod_client_state.erl2
-rw-r--r--src/mod_configure.erl387
-rw-r--r--src/mod_configure2.erl31
-rw-r--r--src/mod_disco.erl52
-rw-r--r--src/mod_echo.erl9
-rw-r--r--src/mod_http_api.erl294
-rw-r--r--src/mod_http_upload.erl87
-rw-r--r--src/mod_http_upload_quota.erl5
-rw-r--r--src/mod_irc.erl257
-rw-r--r--src/mod_irc_connection.erl36
-rw-r--r--src/mod_irc_mnesia.erl69
-rw-r--r--src/mod_irc_riak.erl49
-rw-r--r--src/mod_irc_sql.erl91
-rw-r--r--src/mod_last.erl185
-rw-r--r--src/mod_last_mnesia.erl72
-rw-r--r--src/mod_last_riak.erl53
-rw-r--r--src/mod_last_sql.erl75
-rw-r--r--src/mod_mam.erl693
-rw-r--r--src/mod_mam_mnesia.erl178
-rw-r--r--src/mod_mam_sql.erl309
-rw-r--r--src/mod_metrics.erl5
-rw-r--r--src/mod_mix.erl348
-rw-r--r--src/mod_muc.erl519
-rw-r--r--src/mod_muc_admin.erl74
-rw-r--r--src/mod_muc_log.erl16
-rw-r--r--src/mod_muc_mnesia.erl163
-rw-r--r--src/mod_muc_riak.erl117
-rw-r--r--src/mod_muc_room.erl391
-rw-r--r--src/mod_muc_sql.erl202
-rw-r--r--src/mod_multicast.erl53
-rw-r--r--src/mod_offline.erl1061
-rw-r--r--src/mod_offline_mnesia.erl232
-rw-r--r--src/mod_offline_riak.erl153
-rw-r--r--src/mod_offline_sql.erl252
-rw-r--r--src/mod_ping.erl5
-rw-r--r--src/mod_pres_counter.erl2
-rw-r--r--src/mod_privacy.erl834
-rw-r--r--src/mod_privacy_mnesia.erl198
-rw-r--r--src/mod_privacy_riak.erl160
-rw-r--r--src/mod_privacy_sql.erl397
-rw-r--r--src/mod_private.erl254
-rw-r--r--src/mod_private_mnesia.erl97
-rw-r--r--src/mod_private_riak.erl67
-rw-r--r--src/mod_private_sql.erl97
-rw-r--r--src/mod_proxy65_service.erl30
-rw-r--r--src/mod_proxy65_stream.erl2
-rw-r--r--src/mod_pubsub.erl353
-rw-r--r--src/mod_register.erl75
-rw-r--r--src/mod_register_web.erl34
-rw-r--r--src/mod_roster.erl708
-rw-r--r--src/mod_roster_mnesia.erl171
-rw-r--r--src/mod_roster_riak.erl113
-rw-r--r--src/mod_roster_sql.erl308
-rw-r--r--src/mod_shared_roster.erl508
-rw-r--r--src/mod_shared_roster_ldap.erl120
-rw-r--r--src/mod_shared_roster_mnesia.erl167
-rw-r--r--src/mod_shared_roster_riak.erl139
-rw-r--r--src/mod_shared_roster_sql.erl212
-rw-r--r--src/mod_sic.erl21
-rw-r--r--src/mod_stats.erl28
-rw-r--r--src/mod_time.erl5
-rw-r--r--src/mod_vcard.erl776
-rw-r--r--src/mod_vcard_ldap.erl32
-rw-r--r--src/mod_vcard_mnesia.erl213
-rw-r--r--src/mod_vcard_riak.erl151
-rw-r--r--src/mod_vcard_sql.erl268
-rw-r--r--src/mod_vcard_xupdate.erl149
-rw-r--r--src/mod_vcard_xupdate_mnesia.erl69
-rw-r--r--src/mod_vcard_xupdate_riak.erl44
-rw-r--r--src/mod_vcard_xupdate_sql.erl79
-rw-r--r--src/mod_version.erl5
-rw-r--r--src/node_buddy.erl3
-rw-r--r--src/node_club.erl3
-rw-r--r--src/node_dag.erl3
-rw-r--r--src/node_dispatch.erl3
-rw-r--r--src/node_flat.erl3
-rw-r--r--src/node_flat_sql.erl (renamed from src/node_flat_odbc.erl)154
-rw-r--r--src/node_hometree_sql.erl (renamed from src/node_hometree_odbc.erl)66
-rw-r--r--src/node_mb.erl3
-rw-r--r--src/node_mix.erl168
-rw-r--r--src/node_mix_sql.erl171
-rw-r--r--src/node_online.erl3
-rw-r--r--src/node_pep.erl5
-rw-r--r--src/node_pep_sql.erl (renamed from src/node_pep_odbc.erl)98
-rw-r--r--src/node_private.erl3
-rw-r--r--src/node_public.erl3
-rw-r--r--src/nodetree_dag.erl12
-rw-r--r--src/nodetree_tree.erl14
-rw-r--r--src/nodetree_tree_sql.erl (renamed from src/nodetree_tree_odbc.erl)87
-rw-r--r--src/odbc_queries.erl659
-rw-r--r--src/prosody2ejabberd.erl341
-rw-r--r--src/pubsub_db_sql.erl (renamed from src/pubsub_db_odbc.erl)90
-rw-r--r--src/pubsub_subscription.erl35
-rw-r--r--src/pubsub_subscription_sql.erl (renamed from src/pubsub_subscription_odbc.erl)41
-rw-r--r--src/sql_queries.erl644
-rw-r--r--test/README-quicktest.md33
-rw-r--r--test/acl_test.exs128
-rw-r--r--test/ejabberd_SUITE.erl423
-rw-r--r--test/ejabberd_SUITE_data/ejabberd.ldif17
-rw-r--r--test/ejabberd_SUITE_data/ejabberd.yml181
-rw-r--r--test/ejabberd_admin_test.exs79
-rw-r--r--test/ejabberd_auth_mock.exs74
-rw-r--r--test/ejabberd_commands_mock_test.exs423
-rw-r--r--test/ejabberd_commands_test.exs84
-rw-r--r--test/ejabberd_cyrsasl_test.exs153
-rw-r--r--test/ejabberd_hooks_test.exs53
-rw-r--r--test/ejabberd_oauth_mock.exs47
-rw-r--r--test/ejabberd_sm_mock.exs121
-rw-r--r--test/elixir_SUITE.erl60
-rw-r--r--test/jid_test.exs44
-rw-r--r--test/mod_admin_extra_test.exs356
-rw-r--r--test/mod_http_api_mock_test.exs197
-rw-r--r--test/mod_http_api_test.exs101
-rw-r--r--test/mod_last_mock.exs79
-rw-r--r--test/mod_roster_mock.exs208
-rw-r--r--test/suite.erl50
-rw-r--r--test/suite.hrl3
-rw-r--r--tools/ejabberdctl.bc4
-rw-r--r--tools/xmpp_codec.erl416
-rw-r--r--tools/xmpp_codec.hrl20
-rw-r--r--tools/xmpp_codec.spec66
247 files changed, 17672 insertions, 9364 deletions
diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE
new file mode 100644
index 00000000..d31eb5bb
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE
@@ -0,0 +1,15 @@
+> 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/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE
new file mode 100644
index 00000000..643d025d
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE
@@ -0,0 +1,20 @@
+We are open to contributions for ejabberd, as GitHub pull requests (PR).
+Here are a few points to consider before submitting your PR. (You can
+remove the whole text after reading.)
+
+1. Does this PR address an issue? Please reference it in the PR
+ description.
+
+2. Have you properly described the proposed change?
+
+3. Please make sure the change is atomic and does only touch the needed
+ modules. If you have other changes/fixes to provide, please submit
+ them as separate PRs.
+
+4. If your change or new feature involves storage backends, did you make
+ sure your change works with all backends?
+
+5. Do you provide tests? How can we check the behavior of the code?
+
+6. Did you consider documentation changes in the
+ processone/docs.ejabberd.im repository?
diff --git a/.gitignore b/.gitignore
index b4950e01..bf5fe286 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,8 +39,10 @@ XmppAddr.hrl
/vars.config
/dialyzer/
/test/*.beam
+/test/*.ctc
/logs/
/priv/sql
/rel/ejabberd
/_build
/mnesiadb
+/.rebar
diff --git a/.travis.yml b/.travis.yml
index 5379f848..d30afbb1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,11 +1,12 @@
language: erlang
otp_release:
- - 17.1
- 17.5
+ - 18.3
services:
- riak
+ - redis-server
before_install:
#
@@ -16,15 +17,26 @@ before_install:
# See: https://github.com/travis-ci/travis-ci/issues/1986
#
- sudo sed -i -e s/table_cache/table_open_cache/ -e /log_slow_queries/d /etc/mysql/my.cnf
- - sudo apt-key adv --keyserver pgp.mit.edu --recv-keys 5072E1F5
+ - sudo apt-key adv --import .travis/mysql_repo_key.asc
- sudo add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ precise mysql-5.6'
- sudo apt-get -qq update
- sudo apt-get -qq -o Dpkg::Options::=--force-confold install mysql-server-5.6
+ # /END MYSQL 5.6
+ - pip install --user coveralls-merge
install:
- sudo apt-get -qq install libexpat1-dev libyaml-dev libpam0g-dev libsqlite3-dev
before_script:
+ # Ulimit: See Travis-CI issue report: https://github.com/travis-ci/travis-ci/issues/3328
+ - echo 'ulimit -n 4096' > riak
+ - sudo mv riak /etc/default/riak
+ - mkdir "$PWD/ebin"
+ - echo "[{riak_kv, [{add_paths, [\"$PWD/ebin/\"]}]}]." > advanced.config
+ - sudo mv advanced.config /etc/riak/advanced.config
+ - sudo service riak restart
+ - sudo riak-admin wait-for-service riak_kv 'riak@127.0.0.1'
+ - sudo riak-admin test
- 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';"
@@ -34,18 +46,26 @@ before_script:
script:
- ./autogen.sh
- - ./configure --prefix=/tmp/ejabberd --enable-all --disable-odbc --disable-elixir
+ - ./configure --prefix=/tmp/ejabberd --enable-all --disable-odbc
- make
- make install
- make xref
- - ERL_LIBS=$PWD make test
+ - sed -i -e 's/ct:pal/ct:log/' test/suite.erl
+ - ln -sf ../sql priv/
+ - escript ./rebar skip_deps=true ct -v
- grep -q 'TEST COMPLETE, \([[:digit:]]*\) ok, .* of \1 ' logs/raw.log
after_script:
- find logs -name suite.log -exec cat '{}' ';'
after_failure:
+ - find logs -name exunit.log -exec cat '{}' ';'
+ # Try checking Riak database logs
+ - tail -n 100000 /var/log/riak/*.log
- find logs -name ejabberd.log -exec cat '{}' ';'
+after_success:
+ - coveralls-merge erlang.json
+
notifications:
email: false
diff --git a/.travis/mysql_repo_key.asc b/.travis/mysql_repo_key.asc
new file mode 100644
index 00000000..063f5148
--- /dev/null
+++ b/.travis/mysql_repo_key.asc
@@ -0,0 +1,96 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.9 (SunOS)
+
+mQGiBD4+owwRBAC14GIfUfCyEDSIePvEW3SAFUdJBtoQHH/nJKZyQT7h9bPlUWC3
+RODjQReyCITRrdwyrKUGku2FmeVGwn2u2WmDMNABLnpprWPkBdCk96+OmSLN9brZ
+fw2vOUgCmYv2hW0hyDHuvYlQA/BThQoADgj8AW6/0Lo7V1W9/8VuHP0gQwCgvzV3
+BqOxRznNCRCRxAuAuVztHRcEAJooQK1+iSiunZMYD1WufeXfshc57S/+yeJkegNW
+hxwR9pRWVArNYJdDRT+rf2RUe3vpquKNQU/hnEIUHJRQqYHo8gTxvxXNQc7fJYLV
+K2HtkrPbP72vwsEKMYhhr0eKCbtLGfls9krjJ6sBgACyP/Vb7hiPwxh6rDZ7ITnE
+kYpXBACmWpP8NJTkamEnPCia2ZoOHODANwpUkP43I7jsDmgtobZX9qnrAXw+uNDI
+QJEXM6FSbi0LLtZciNlYsafwAPEOMDKpMqAK6IyisNtPvaLd8lH0bPAnWqcyefep
+rv0sxxqUEMcM3o7wwgfN83POkDasDbs3pjwPhxvhz6//62zQJ7Q2TXlTUUwgUmVs
+ZWFzZSBFbmdpbmVlcmluZyA8bXlzcWwtYnVpbGRAb3NzLm9yYWNsZS5jb20+iGkE
+ExECACkCGyMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAIZAQUCUwHUZgUJGmbLywAK
+CRCMcY07UHLh9V+DAKCjS1gGwgVI/eut+5L+l2v3ybl+ZgCcD7ZoA341HtoroV3U
+6xRD09fUgeq0O015U1FMIFBhY2thZ2Ugc2lnbmluZyBrZXkgKHd3dy5teXNxbC5j
+b20pIDxidWlsZEBteXNxbC5jb20+iG8EMBECAC8FAk53Pa0oHSBidWlsZEBteXNx
+bC5jb20gd2lsbCBzdG9wIHdvcmtpbmcgc29vbgAKCRCMcY07UHLh9bU9AJ9xDK0o
+xJFL9vTl9OSZC4lX0K9AzwCcCrS9cnJyz79eaRjL0s2r/CcljdyIZQQTEQIAHQUC
+R6yUtAUJDTBYqAULBwoDBAMVAwIDFgIBAheAABIJEIxxjTtQcuH1B2VHUEcAAQGu
+kgCffz4GUEjzXkOi71VcwgCxASTgbe0An34LPr1j9fCbrXWXO14msIADfb5piEwE
+ExECAAwFAj4+o9EFgwlmALsACgkQSVDhKrJykfIk4QCfWbEeKN+3TRspe+5xKj+k
+QJSammIAnjUz0xFWPlVx0f8o38qNG1bq0cU9iEwEExECAAwFAj5CggMFgwliIokA
+CgkQtvXNTca6JD+WkQCgiGmnoGjMojynp5ppvMXkyUkfnykAoK79E6h8rwkSDZou
+iz7nMRisH8uyiEYEEBECAAYFAj+s468ACgkQr8UjSHiDdA/2lgCg21IhIMMABTYd
+p/IBiUsP/JQLiEoAnRzMywEtujQz/E9ono7H1DkebDa4iEYEEBECAAYFAj+0Q3cA
+CgkQhZavqzBzTmbGwwCdFqD1frViC7WRt8GKoOS7hzNN32kAnirlbwpnT7a6NOsQ
+83nk11a2dePhiEYEEBECAAYFAkNbs+oACgkQi9gubzC5S1x/dACdELKoXQKkwJN0
+gZztsM7kjsIgyFMAnRRMbHQ7V39XC90OIpaPjk3a01tgiEYEExECAAYFAkTxMyYA
+CgkQ9knE9GCTUwwKcQCgibak/SwhxWH1ijRhgYCo5GtM4vcAnAhtzL57wcw1Kg1X
+m7nVGetUqJ7fiEwEEBECAAwFAkGBywEFgwYi2YsACgkQGFnQH2d7oexCjQCcD8sJ
+NDc/mS8m8OGDUOx9VMWcnGkAnj1YWOD+Qhxo3mI/Ul9oEAhNkjcfiEwEEBECAAwF
+AkGByzQFgwYi2VgACgkQgcL36+ITtpIiIwCdFVNVUB8xe8mFXoPm4d9Z54PTjpMA
+niSPA/ZsfJ3oOMLKar4F0QPPrdrGiEwEEBECAAwFAkGBy2IFgwYi2SoACgkQa3Ds
+2V3D9HMJqgCbBYzr5GPXOXgP88jKzmdbjweqXeEAnRss4G2G/3qD7uhTL1SPT1SH
+jWUXiEwEEBECAAwFAkHQkyQFgwXUEWgACgkQfSXKCsEpp8JiVQCghvWvkPqowsw8
+w7WSseTcw1tflvkAni+vLHl/DqIly0LkZYn5jzK1dpvfiEwEEBECAAwFAkIrW7oF
+gwV5SNIACgkQ5hukiRXruavzEwCgkzL5QkLSypcw9LGHcFSx1ya0VL4An35nXkum
+g6cCJ1NP8r2I4NcZWIrqiEwEEhECAAwFAkAqWToFgwd6S1IACgkQPKEfNJT6+GEm
+XACcD+A53A5OGM7w750W11ukq4iZ9ckAnRMvndAqn3YTOxxlLPj2UPZiSgSqiEwE
+EhECAAwFAkA9+roFgwdmqdIACgkQ8tdcY+OcZZyy3wCgtDcwlaq20w0cNuXFLLNe
+EUaFFTwAni6RHN80moSVAdDTRkzZacJU3M5QiEwEEhECAAwFAkEOCoQFgwaWmggA
+CgkQOcor9D1qil/83QCeITZ9wIo7XAMjC6y4ZWUL4m+edZsAoMOhRIRi42fmrNFu
+vNZbnMGej81viEwEEhECAAwFAkKApTQFgwUj/1gACgkQBA3AhXyDn6jjJACcD1A4
+UtXk84J13JQyoH9+dy24714Aniwlsso/9ndICJOkqs2j5dlHFq6oiEwEExECAAwF
+Aj5NTYQFgwlXVwgACgkQLbt2v63UyTMFDACglT5G5NVKf5Mj65bFSlPzb92zk2QA
+n1uc2h19/IwwrsbIyK/9POJ+JMP7iEwEExECAAwFAkHXgHYFgwXNJBYACgkQZu/b
+yM2C/T4/vACfXe67xiSHB80wkmFZ2krb+oz/gBAAnjR2ucpbaonkQQgnC3GnBqmC
+vNaJiEwEExECAAwFAkIYgQ4FgwWMI34ACgkQdsEDHKIxbqGg7gCfQi2HcrHn+yLF
+uNlH1oSOh48ZM0oAn3hKV0uIRJphonHaUYiUP1ttWgdBiGUEExECAB0FCwcKAwQD
+FQMCAxYCAQIXgAUCS3AvygUJEPPzpwASB2VHUEcAAQEJEIxxjTtQcuH1sNsAniYp
+YBGqy/HhMnw3WE8kXahOOR5KAJ4xUmWPGYP4l3hKxyNK9OAUbpDVYIh7BDARAgA7
+BQJCdzX1NB0AT29wcy4uLiBzaG91bGQgaGF2ZSBiZWVuIGxvY2FsISBJJ20gKnNv
+KiBzdHVwaWQuLi4ACgkQOcor9D1qil/vRwCdFo08f66oKLiuEAqzlf9iDlPozEEA
+n2EgvCYLCCHjfGosrkrU3WK5NFVgiI8EMBECAE8FAkVvAL9IHQBTaG91bGQgaGF2
+ZSBiZWVuIGEgbG9jYWwgc2lnbmF0dXJlLCBvciBzb21ldGhpbmcgLSBXVEYgd2Fz
+IEkgdGhpbmtpbmc/AAoJEDnKK/Q9aopfoPsAn3BVqKOalJeF0xPSvLR90PsRlnmG
+AJ44oisY7Tl3NJbPgZal8W32fbqgbIkCIgQQAQIADAUCQYHLhQWDBiLZBwAKCRCq
+4+bOZqFEaKgvEACCErnaHGyUYa0wETjj6DLEXsqeOiXad4i9aBQxnD35GUgcFofC
+/nCY4XcnCMMEnmdQ9ofUuU3OBJ6BNJIbEusAabgLooebP/3KEaiCIiyhHYU5jarp
+ZAh+Zopgs3Oc11mQ1tIaS69iJxrGTLodkAsAJAeEUwTPq9fHFFzC1eGBysoyFWg4
+bIjz/zClI+qyTbFA5g6tRoiXTo8ko7QhY2AA5UGEg+83Hdb6akC04Z2QRErxKAqr
+phHzj8XpjVOsQAdAi/qVKQeNKROlJ+iq6+YesmcWGfzeb87dGNweVFDJIGA0qY27
+pTb2lExYjsRFN4Cb13NfodAbMTOxcAWZ7jAPCxAPlHUG++mHMrhQXEToZnBFE4nb
+nC7vOBNgWdjUgXcpkUCkop4b17BFpR+k8ZtYLSS8p2LLz4uAeCcSm2/msJxT7rC/
+FvoH8428oHincqs2ICo9zO/Ud4HmmO0O+SsZdVKIIjinGyOVWb4OOzkAlnnhEZ3o
+6hAHcREIsBgPwEYVTj/9ZdC0AO44Nj9cU7awaqgtrnwwfr/o4V2gl8bLSkltZU27
+/29HeuOeFGjlFe0YrDd/aRNsxbyb2O28H4sG1CVZmC5uK1iQBDiSyA7Q0bbdofCW
+oQzm5twlpKWnY8Oe0ub9XP5p/sVfck4FceWFHwv+/PC9RzSl33lQ6vM2wIkCIgQT
+AQIADAUCQp8KHAWDBQWacAAKCRDYwgoJWiRXzyE+D/9uc7z6fIsalfOYoLN60ajA
+bQbI/uRKBFugyZ5RoaItusn9Z2rAtn61WrFhu4uCSJtFN1ny2RERg40f56pTghKr
+D+YEt+Nze6+FKQ5AbGIdFsR/2bUk+ZZRSt83e14Lcb6ii/fJfzkoIox9ltkifQxq
+Y7Tvk4noKu4oLSc8O1Wsfc/y0B9sYUUCmUfcnq58DEmGie9ovUslmyt5NPnveXxp
+5UeaRc5Rqt9tK2B4A+7/cqENrdZJbAMSunt2+2fkYiRunAFPKPBdJBsY1sxeL/A9
+aKe0viKEXQdAWqdNZKNCi8rd/oOP99/9lMbFudAbX6nL2DSb1OG2Z7NWEqgIAzjm
+pwYYPCKeVz5Q8R+if9/fe5+STY/55OaI33fJ2H3v+U435VjYqbrerWe36xJItcJe
+qUzW71fQtXi1CTEl3w2ch7VF5oj/QyjabLnAlHgSlkSi6p7By5C2MnbCHlCfPnIi
+nPhFoRcRGPjJe9nFwGs+QblvS/Chzc2WX3s/2SWm4gEUKRX4zsAJ5ocyfa/vkxCk
+SxK/erWlCPf/J1T70+i5waXDN/E3enSet/WL7h94pQKpjz8OdGL4JSBHuAVGA+a+
+dknqnPF0KMKLhjrgV+L7O84FhbmAP7PXm3xmiMPriXf+el5fZZequQoIagf8rdRH
+HhRJxQgI0HNknkaOqs8dtrkCDQQ+PqMdEAgA7+GJfxbMdY4wslPnjH9rF4N2qfWs
+EN/lxaZoJYc3a6M02WCnHl6ahT2/tBK2w1QI4YFteR47gCvtgb6O1JHffOo2HfLm
+RDRiRjd1DTCHqeyX7CHhcghj/dNRlW2Z0l5QFEcmV9U0Vhp3aFfWC4Ujfs3LU+hk
+AWzE7zaD5cH9J7yv/6xuZVw411x0h4UqsTcWMu0iM1BzELqX1DY7LwoPEb/O9Rkb
+f4fmLe11EzIaCa4PqARXQZc4dhSinMt6K3X4BrRsKTfozBu74F47D8Ilbf5vSYHb
+uE5p/1oIDznkg/p8kW+3FxuWrycciqFTcNz215yyX39LXFnlLzKUb/F5GwADBQf+
+Lwqqa8CGrRfsOAJxim63CHfty5mUc5rUSnTslGYEIOCR1BeQauyPZbPDsDD9MZ1Z
+aSafanFvwFG6Llx9xkU7tzq+vKLoWkm4u5xf3vn55VjnSd1aQ9eQnUcXiL4cnBGo
+TbOWI39EcyzgslzBdC++MPjcQTcA7p6JUVsP6oAB3FQWg54tuUo0Ec8bsM8b3Ev4
+2LmuQT5NdKHGwHsXTPtl0klk4bQk4OajHsiy1BMahpT27jWjJlMiJc+IWJ0mghkK
+Ht926s/ymfdf5HkdQ1cyvsz5tryVI3Fx78XeSYfQvuuwqp2H139pXGEkg0n6KdUO
+etdZWhe70YGNPw1yjWJT1IhUBBgRAgAMBQJOdz3tBQkT+wG4ABIHZUdQRwABAQkQ
+jHGNO1By4fUUmwCbBYr2+bBEn/L2BOcnw9Z/QFWuhRMAoKVgCFm5fadQ3Afi+UQl
+AcOphrnJ
+=443I
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/Makefile.in b/Makefile.in
index 08cd8379..0d913448 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -110,7 +110,7 @@ edoc:
spec:
$(ERL) -noinput +B -pa ebin -pa deps/*/ebin -eval \
- 'case xml_gen:compile("tools/xmpp_codec.spec") of ok -> halt(0); _ -> halt(1) end.'
+ 'case fxml_gen:compile("tools/xmpp_codec.spec") of ok -> halt(0); _ -> halt(1) end.'
JOIN_PATHS=$(if $(wordlist 2,1000,$(1)),$(firstword $(1))/$(call JOIN_PATHS,$(wordlist 2,1000,$(1))),$(1))
@@ -137,7 +137,7 @@ MAIN_FILES=$(filter-out %/configure.beam,$(call FILES_WILDCARD,ebin/*.beam ebin/
MAIN_DIRS=$(sort $(dir $(MAIN_FILES)) priv/bin priv/sql)
define DEP_VERSION_template
-DEP_$(1)_VERSION:=$(shell $(SED) -e '/vsn/!d;s/.*,"/$(1)-/;s/".*//' $(2) 2>/dev/null)
+DEP_$(1)_VERSION:=$(shell $(SED) -e '/vsn/!d;s/.*, *"/$(1)-/;s/".*//' $(2) 2>/dev/null)
endef
$(foreach DEP,$(DEPS),$(eval $(call DEP_VERSION_template,$(DEP),deps/$(DEP)/ebin/$(DEP).app)))
diff --git a/README b/README
index 44026f67..b0dff072 100644
--- a/README
+++ b/README
@@ -1,5 +1,7 @@
-ejabberd Community Edition [![Build Status](https://travis-ci.org/processone/ejabberd.svg?branch=master)](https://travis-ci.org/processone/ejabberd)
-=========================================
+ejabberd Community Edition
+==========================
+
+[![Build Status](https://travis-ci.org/processone/ejabberd.svg?branch=master)](https://travis-ci.org/processone/ejabberd) [![Hex version](https://img.shields.io/hexpm/v/ejabberd.svg "Hex version")](https://hex.pm/packages/ejabberd)
ejabberd is a distributed, fault-tolerant technology that allows the creation
of large-scale instant messaging applications. The server can reliably support
diff --git a/cover.spec b/cover.spec
new file mode 100644
index 00000000..7d504d55
--- /dev/null
+++ b/cover.spec
@@ -0,0 +1,5 @@
+{level, details}.
+{incl_dirs, ["src", "ebin"]}.
+{excl_mods, [eldap, 'ELDAPv3']}.
+{export, "logs/all.coverdata"}.
+
diff --git a/ejabberd.service.template b/ejabberd.service.template
index 79c2a644..80b15adb 100644
--- a/ejabberd.service.template
+++ b/ejabberd.service.template
@@ -7,7 +7,7 @@ User=ejabberd
Group=ejabberd
LimitNOFILE=16000
RestartSec=5
-ExecStart=/bin/sh @ctlscriptpath@/ejabberdctl start
+ExecStart=@ctlscriptpath@/ejabberdctl start
ExecStop=@ctlscriptpath@/ejabberdctl stop
ExecReload=@ctlscriptpath@/ejabberdctl reload_config
Type=oneshot
diff --git a/ejabberd.yml.example b/ejabberd.yml.example
index e20dc0df..b0f62214 100644
--- a/ejabberd.yml.example
+++ b/ejabberd.yml.example
@@ -254,10 +254,10 @@ auth_method: internal
## extauth_program: "/path/to/authentication/script"
##
-## Authentication using ODBC
+## Authentication using SQL
## Remember to setup a database in the next section.
##
-## auth_method: odbc
+## auth_method: sql
##
## Authentication using PAM
@@ -330,26 +330,26 @@ auth_method: internal
##
## MySQL server:
##
-## odbc_type: mysql
-## odbc_server: "server"
-## odbc_database: "database"
-## odbc_username: "username"
-## odbc_password: "password"
+## sql_type: mysql
+## sql_server: "server"
+## sql_database: "database"
+## sql_username: "username"
+## sql_password: "password"
##
## If you want to specify the port:
-## odbc_port: 1234
+## sql_port: 1234
##
## PostgreSQL server:
##
-## odbc_type: pgsql
-## odbc_server: "server"
-## odbc_database: "database"
-## odbc_username: "username"
-## odbc_password: "password"
+## sql_type: pgsql
+## sql_server: "server"
+## sql_database: "database"
+## sql_username: "username"
+## sql_password: "password"
##
## If you want to specify the port:
-## odbc_port: 1234
+## sql_port: 1234
##
## If you use PostgreSQL, have a large database, and need a
## faster but inexact replacement for "select count(*) from users"
@@ -359,25 +359,25 @@ auth_method: internal
##
## SQLite:
##
-## odbc_type: sqlite
-## odbc_database: "/path/to/database.db"
+## sql_type: sqlite
+## sql_database: "/path/to/database.db"
##
## ODBC compatible or MSSQL server:
##
-## odbc_type: odbc
-## odbc_server: "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"
+## sql_type: odbc
+## sql_server: "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"
##
## Number of connections to open to the database for each virtual host
##
-## odbc_pool_size: 10
+## sql_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
+## sql_keepalive_interval: undefined
###. ===============
###' TRAFFIC SHAPERS
@@ -616,45 +616,39 @@ modules:
- "flat"
- "hometree"
- "pep" # pep requires mod_caps
- mod_register:
+ ## mod_register:
##
## Protect In-Band account registrations with CAPTCHA.
##
- ## captcha_protected: true
-
+ ## captcha_protected: true
##
## Set the minimum informational entropy for passwords.
##
- ## password_strength: 32
-
+ ## 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.
-
+ ## 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"
-
+ ## registration_watchers:
+ ## - "admin1@example.org"
##
## Only clients in the server machine can register accounts
##
- ip_access: trusted_network
-
+ ## ip_access: trusted_network
##
## Local c2s or remote s2s users cannot register accounts
##
- ## access_from: deny
-
- access: register
+ ## access_from: deny
+ ## access: register
mod_roster: {}
mod_shared_roster: {}
mod_stats: {}
diff --git a/ejabberdctl.template b/ejabberdctl.template
index d585d66f..224650fe 100755
--- a/ejabberdctl.template
+++ b/ejabberdctl.template
@@ -31,14 +31,14 @@ if [ "$INSTALLUSER" != "" ] ; then
fi
done
if [ `id -g` -eq `id -g $INSTALLUSER` ] ; then
- EXEC_CMD="sh -c"
+ EXEC_CMD="bash -c"
fi
if [ "$EXEC_CMD" = "false" ] ; then
echo "This command can only be run by root or the user $INSTALLUSER" >&2
exit 4
fi
else
- EXEC_CMD="sh -c"
+ EXEC_CMD="bash -c"
fi
# parse command line parameters
@@ -159,11 +159,24 @@ export CONTRIB_MODULES_PATH
export CONTRIB_MODULES_CONF_DIR
export ERL_LIBS
+shell_escape_str()
+{
+ if test $# -eq 0; then
+ printf '"" '
+ else
+ shell_escape "$@"
+ fi
+}
+
shell_escape()
{
local RES=()
for i in "$@"; do
- printf '%q ' "$i"
+ if test -z "$i"; then
+ printf '"" '
+ else
+ printf '%q ' "$i"
+ fi
done
}
@@ -186,8 +199,8 @@ start()
debug()
{
debugwarning
- TTY=`tty | sed -e 's/.*\///g'`
- CMD="`shell_escape \"$ERL\" \"$NAME\" \"debug-${TTY}-${ERLANG_NODE}\"` \
+ NID=$(uid debug)
+ CMD="`shell_escape \"$ERL\" \"$NAME\" \"$NID\"` \
-remsh $ERLANG_NODE \
-hidden \
$KERNEL_OPTS \
@@ -200,14 +213,14 @@ debug()
iexdebug()
{
debugwarning
- TTY=`tty | sed -e 's/.*\///g'`
# Elixir shell is hidden as default
- CMD="`shell_escape \"$IEX\" \"$IEXNAME\" \"debug-${TTY}-${ERLANG_NODE}\"` \
+ NID=$(uid debug)
+ CMD="`shell_escape \"$IEX\" \"$IEXNAME\" \"$NID\"` \
-remsh $ERLANG_NODE \
- --erl \"`shell_escape \"$KERNEL_OPTS\"\" \
- --erl \"`shell_escape \"$ERLANG_OPTS\"\" \
- --erl \"`shell_escape \"${ARGS[@]}\"\" \
- --erl \"`shell_escape \"$@\"\""
+ --erl `shell_escape \"$KERNEL_OPTS\"` \
+ --erl `shell_escape \"$ERLANG_OPTS\"` \
+ --erl `shell_escape \"${ARGS[@]}\"` \
+ --erl `shell_escape_str \"$@\"`"
$EXEC_CMD "$CMD"
}
@@ -229,14 +242,15 @@ live()
iexlive()
{
livewarning
+ echo $@
CMD="`shell_escape \"$IEX\" \"$IEXNAME\" \"${ERLANG_NODE}\"` \
--erl \"-mnesia dir \\\"$SPOOL_DIR\\\"\" \
--erl \"`shell_escape \"$KERNEL_OPTS\"`\" \
--erl \"`shell_escape \"$EJABBERD_OPTS\"`\" \
--app ejabberd \
- --erl \"`shell_escape \"$ERLANG_OPTS\"`\" \
- --erl \"`shell_escape \"${ARGS[@]}\"`\" \
- --erl \"`shell_escape \"$@\"`\""
+ --erl `shell_escape \"$ERLANG_OPTS\"` \
+ --erl `shell_escape \"${ARGS[@]}\"` \
+ --erl `shell_escape_str \"$@\"`"
$EXEC_CMD "$CMD"
}
@@ -305,20 +319,27 @@ livewarning()
etop()
{
- TTY=`tty | sed -e 's/.*\///g'`
+ NID=$(uid top)
$EXEC_CMD "$ERL \
- $NAME debug-${TTY}-${ERLANG_NODE} \
+ $NAME $NID \
-hidden -s etop -s erlang halt -output text -node $ERLANG_NODE"
}
ping()
{
- TTY=`tty | sed -e 's/.*\///g'`
+ [ -z "$1" ] && PEER=${ERLANG_NODE} || PEER=$1
+ if [ "$PEER" = "${PEER%.*}" ] ; then
+ PING_NAME="-sname"
+ PING_NODE=$(hostname -s)
+ else
+ PING_NAME="-name"
+ PING_NODE=$(hostname)
+ fi
+ NID=$(uid ping ${PING_NODE})
$EXEC_CMD "$ERL \
- $NAME ping-${TTY}-${ERLANG_NODE} \
- -hidden \
- $KERNEL_OPTS $ERLANG_OPTS \
- -eval 'io:format(\"~p~n\",[net_adm:ping($1)])' \
+ $PING_NAME $NID \
+ -hidden $KERNEL_OPTS $ERLANG_OPTS \
+ -eval 'io:format(\"~p~n\",[net_adm:ping('\"'\"'$PEER'\"'\"')])' \
-s erlang halt -output text -noinput"
}
@@ -346,85 +367,30 @@ help()
# common control function
ctl()
{
- # Control number of connections identifiers
- # using flock if available. Expects a linux-style
- # flock that can lock a file descriptor.
- MAXCONNID=100
- CONNLOCKDIR={{localstatedir}}/lock/ejabberdctl
- FLOCK=/usr/bin/flock
- if [ ! -x "$FLOCK" ] || [ ! -d "$CONNLOCKDIR" ] ; then
- JOT=/usr/bin/jot
- if [ ! -x "$JOT" ] ; then
- # no flock or jot, simply invoke ctlexec()
- CTL_CONN="ctl-${ERLANG_NODE}"
- ctlexec $CTL_CONN "$@"
- result=$?
- else
- # no flock, but at least there is jot
- RAND=`jot -r 1 0 $MAXCONNID`
- CTL_CONN="ctl-${RAND}-${ERLANG_NODE}"
- ctlexec $CTL_CONN "$@"
- result=$?
- fi
- else
- # we have flock so we get a lock
- # on one of a limited number of
- # conn names -- this allows
- # concurrent invocations using a bound
- # number of atoms
- for N in `seq 1 $MAXCONNID`; do
- CTL_CONN="ejabberdctl-$N"
- CTL_LOCKFILE="$CONNLOCKDIR/$CTL_CONN"
- (
- exec 8>"$CTL_LOCKFILE"
- if flock --nb 8; then
- ctlexec $CTL_CONN "$@"
- ssresult=$?
- # segregate from possible flock exit(1)
- ssresult=`expr $ssresult \* 10`
- exit $ssresult
- else
- exit 1
- fi
- )
- result=$?
- if [ $result -eq 1 ] ; then
- # means we errored out in flock
- # rather than in the exec - stay in the loop
- # trying other conn names...
- badlock=1
- else
- badlock=""
- break;
- fi
- done
- result=`expr $result / 10`
- fi
-
- if [ "$badlock" ] ;then
- echo "Ran out of connections to try. Your ejabberd processes" >&2
- echo "may be stuck or this is a very busy server. For very" >&2
- echo "busy servers, consider raising MAXCONNID in ejabberdctl">&2
- exit 1;
- fi
-
+ NID=$(uid ctl)
+ CMD="`shell_escape \"$ERL\" \"$NAME\" \"$NID\"` \
+ -noinput -hidden $KERNEL_OPTS -s ejabberd_ctl \
+ -extra `shell_escape \"$ERLANG_NODE\"` $EJABBERD_NO_TIMEOUT \
+ `shell_escape \"$@\"`"
+ $EXEC_CMD "$CMD"
+ result=$?
case $result in
- 0) :;;
- 1) :;;
2) help;;
3) help;;
+ *) :;;
esac
return $result
}
-ctlexec()
+uid()
{
- CONN_NAME=$1; shift
- CMD="`shell_escape \"$ERL\" \"$NAME\" \"$CONN_NAME\"` \
- -noinput -hidden $KERNEL_OPTS -s ejabberd_ctl \
- -extra `shell_escape \"$ERLANG_NODE\"` $EJABBERD_NO_TIMEOUT \
- `shell_escape \"$@\"`"
- $EXEC_CMD "$CMD"
+ uuid=$(uuidgen 2>/dev/null)
+ [ -z "$uuid" -a -f /proc/sys/kernel/random/uuid ] && uuid=$(</proc/sys/kernel/random/uuid)
+ [ -z "$uuid" ] && uuid=$(printf "%X" $RANDOM$(date +%M%S)$$)
+ uuid=${uuid%%-*}
+ [ $# -eq 0 ] && echo ${uuid}-${ERLANG_NODE}
+ [ $# -eq 1 ] && echo ${uuid}-${1}-${ERLANG_NODE}
+ [ $# -eq 2 ] && echo ${uuid}-${1}@${2}
}
# stop epmd if there is no other running node
@@ -481,7 +447,7 @@ case "${ARGS[0]}" in
'live') live;;
'iexlive') iexlive;;
'foreground') foreground;;
- 'ping'*) ping ${ARGS# ping};;
+ 'ping'*) ping ${ARGS[1]};;
'etop') etop;;
'started') wait_for_status 0 30 2;; # wait 30x2s before timeout
'stopped') wait_for_status 3 15 2 && stop_epmd;; # wait 15x2s before timeout
diff --git a/include/ejabberd_commands.hrl b/include/ejabberd_commands.hrl
index 0742e3ba..81be06dc 100644
--- a/include/ejabberd_commands.hrl
+++ b/include/ejabberd_commands.hrl
@@ -31,8 +31,9 @@
tags = [] :: [atom()] | '_' | '$2',
desc = "" :: string() | '_' | '$3',
longdesc = "" :: string() | '_',
- module :: atom(),
- function :: atom(),
+ version = 0 :: integer(),
+ module :: atom() | '_',
+ function :: atom() | '_',
args = [] :: [aterm()] | '_' | '$1' | '$2',
policy = restricted :: open | restricted | admin | user,
result = {res, rescode} :: rterm() | '_' | '$2',
diff --git a/include/ejabberd_http.hrl b/include/ejabberd_http.hrl
index 8b8fab2b..25209f76 100644
--- a/include/ejabberd_http.hrl
+++ b/include/ejabberd_http.hrl
@@ -23,8 +23,7 @@
path = [] :: [binary()],
q = [] :: [{binary() | nokey, binary()}],
us = {<<>>, <<>>} :: {binary(), binary()},
- auth :: {binary(), binary()} |
- {auth_jid, {binary(), binary()}, jlib:jid()},
+ auth :: {binary(), binary()} | {oauth, binary(), []} | undefined,
lang = <<"">> :: binary(),
data = <<"">> :: binary(),
ip :: {inet:ip_address(), inet:port_number()},
@@ -35,8 +34,8 @@
headers = [] :: [{atom() | binary(), binary()}]}).
-record(ws,
- {socket :: inet:socket() | p1_tls:tls_socket(),
- sockmod = gen_tcp :: gen_tcp | p1_tls,
+ {socket :: inet:socket() | fast_tls:tls_socket(),
+ sockmod = gen_tcp :: gen_tcp | fast_tls,
ip :: {inet:ip_address(), inet:port_number()},
host = <<"">> :: binary(),
port = 5280 :: inet:port_number(),
diff --git a/include/ejabberd_sql_pt.hrl b/include/ejabberd_sql_pt.hrl
new file mode 100644
index 00000000..f1a3dba3
--- /dev/null
+++ b/include/ejabberd_sql_pt.hrl
@@ -0,0 +1,33 @@
+%%%----------------------------------------------------------------------
+%%%
+%%% ejabberd, Copyright (C) 2002-2016 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.
+%%%
+%%%----------------------------------------------------------------------
+
+-define(SQL_MARK, sql__mark_).
+-define(SQL(SQL), ?SQL_MARK(SQL)).
+
+-define(SQL_UPSERT_MARK, sql_upsert__mark_).
+-define(SQL_UPSERT(Host, Table, Fields),
+ ejabberd_sql:sql_query(Host, ?SQL_UPSERT_MARK(Table, Fields))).
+-define(SQL_UPSERT_T(Table, Fields),
+ ejabberd_sql:sql_query_t(?SQL_UPSERT_MARK(Table, Fields))).
+
+-record(sql_query, {hash, format_query, format_res, args, loc}).
+
+-record(sql_escape, {string, integer, boolean}).
+
diff --git a/include/jlib.hrl b/include/jlib.hrl
index f09e392f..50a03133 100644
--- a/include/jlib.hrl
+++ b/include/jlib.hrl
@@ -20,9 +20,9 @@
-include("ns.hrl").
-ifdef(NO_EXT_LIB).
--include("xml.hrl").
+-include("fxml.hrl").
-else.
--include_lib("p1_xml/include/xml.hrl").
+-include_lib("fast_xml/include/fxml.hrl").
-endif.
-define(STANZA_ERROR(Code, Type, Condition),
diff --git a/include/logger.hrl b/include/logger.hrl
index 18dc7041..df077686 100644
--- a/include/logger.hrl
+++ b/include/logger.hrl
@@ -34,3 +34,10 @@
-define(CRITICAL_MSG(Format, Args),
lager:critical(Format, Args)).
+
+%% Use only when trying to troubleshoot test problem with ExUnit
+-define(EXUNIT_LOG(Format, Args),
+ case lists:keyfind(logger, 1, application:loaded_applications()) of
+ false -> ok;
+ _ -> 'Elixir.Logger':bare_log(error, io_lib:format(Format, Args), [?MODULE])
+ end).
diff --git a/include/mod_announce.hrl b/include/mod_announce.hrl
new file mode 100644
index 00000000..83d72aaf
--- /dev/null
+++ b/include/mod_announce.hrl
@@ -0,0 +1,5 @@
+-record(motd, {server = <<"">> :: binary(),
+ packet = #xmlel{} :: xmlel()}).
+
+-record(motd_users, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
+ dummy = [] :: [] | '_'}).
diff --git a/include/mod_caps.hrl b/include/mod_caps.hrl
new file mode 100644
index 00000000..067df949
--- /dev/null
+++ b/include/mod_caps.hrl
@@ -0,0 +1,4 @@
+-record(caps_features,
+ {node_pair = {<<"">>, <<"">>} :: {binary(), binary()},
+ features = [] :: [binary()] | pos_integer()
+ }).
diff --git a/include/mod_carboncopy.hrl b/include/mod_carboncopy.hrl
new file mode 100644
index 00000000..df69938e
--- /dev/null
+++ b/include/mod_carboncopy.hrl
@@ -0,0 +1,4 @@
+-type matchspec_atom() :: '_' | '$1' | '$2' | '$3'.
+-record(carboncopy, {us :: {binary(), binary()} | matchspec_atom(),
+ resource :: binary() | matchspec_atom(),
+ version :: binary() | matchspec_atom()}).
diff --git a/include/mod_irc.hrl b/include/mod_irc.hrl
new file mode 100644
index 00000000..b9696a88
--- /dev/null
+++ b/include/mod_irc.hrl
@@ -0,0 +1,15 @@
+-type conn_param() :: {binary(), binary(), inet:port_number(), binary()} |
+ {binary(), binary(), inet:port_number()} |
+ {binary(), binary()} |
+ {binary()}.
+
+-type irc_data() :: [{username, binary()} | {connections_params, [conn_param()]}].
+
+-record(irc_connection,
+ {jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()},
+ pid = self() :: pid()}).
+
+-record(irc_custom,
+ {us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()},
+ binary()},
+ data = [] :: irc_data()}).
diff --git a/include/mod_last.hrl b/include/mod_last.hrl
new file mode 100644
index 00000000..494bf7b0
--- /dev/null
+++ b/include/mod_last.hrl
@@ -0,0 +1,3 @@
+-record(last_activity, {us = {<<"">>, <<"">>} :: {binary(), binary()},
+ timestamp = 0 :: non_neg_integer(),
+ status = <<"">> :: binary()}).
diff --git a/include/mod_mam.hrl b/include/mod_mam.hrl
new file mode 100644
index 00000000..463db4cf
--- /dev/null
+++ b/include/mod_mam.hrl
@@ -0,0 +1,15 @@
+-record(archive_msg,
+ {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$2',
+ id = <<>> :: binary() | '_',
+ timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_' | '$1',
+ peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3' | undefined,
+ bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3',
+ packet = #xmlel{} :: xmlel() | '_',
+ nick = <<"">> :: binary(),
+ type = chat :: chat | groupchat}).
+
+-record(archive_prefs,
+ {us = {<<"">>, <<"">>} :: {binary(), binary()},
+ default = never :: never | always | roster,
+ always = [] :: [ljid()],
+ never = [] :: [ljid()]}).
diff --git a/include/mod_private.hrl b/include/mod_private.hrl
new file mode 100644
index 00000000..d833af35
--- /dev/null
+++ b/include/mod_private.hrl
@@ -0,0 +1,4 @@
+-record(private_storage,
+ {usns = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() |
+ '$1' | '_'},
+ xml = #xmlel{} :: xmlel() | '_' | '$1'}).
diff --git a/include/mod_shared_roster.hrl b/include/mod_shared_roster.hrl
new file mode 100644
index 00000000..1f96b303
--- /dev/null
+++ b/include/mod_shared_roster.hrl
@@ -0,0 +1,5 @@
+-record(sr_group, {group_host = {<<"">>, <<"">>} :: {'$1' | binary(), '$2' | binary()},
+ opts = [] :: list() | '_' | '$2'}).
+
+-record(sr_user, {us = {<<"">>, <<"">>} :: {binary(), binary()},
+ group_host = {<<"">>, <<"">>} :: {binary(), binary()}}).
diff --git a/include/mod_vcard.hrl b/include/mod_vcard.hrl
new file mode 100644
index 00000000..3bd62b2e
--- /dev/null
+++ b/include/mod_vcard.hrl
@@ -0,0 +1,8 @@
+-record(vcard_search,
+ {us, user, luser, fn, lfn, family, lfamily, given,
+ lgiven, middle, lmiddle, nickname, lnickname, bday,
+ lbday, ctry, lctry, locality, llocality, email, lemail,
+ orgname, lorgname, orgunit, lorgunit}).
+
+-record(vcard, {us = {<<"">>, <<"">>} :: {binary(), binary()} | binary(),
+ vcard = #xmlel{} :: xmlel()}).
diff --git a/include/mod_vcard_xupdate.hrl b/include/mod_vcard_xupdate.hrl
new file mode 100644
index 00000000..8634597a
--- /dev/null
+++ b/include/mod_vcard_xupdate.hrl
@@ -0,0 +1,2 @@
+-record(vcard_xupdate, {us = {<<>>, <<>>} :: {binary(), binary()},
+ hash = <<>> :: binary()}).
diff --git a/include/ns.hrl b/include/ns.hrl
index 372b68b7..c7f55637 100644
--- a/include/ns.hrl
+++ b/include/ns.hrl
@@ -131,6 +131,7 @@
-define(NS_FEATURE_COMPRESS,
<<"http://jabber.org/features/compress">>).
-define(NS_FEATURE_MSGOFFLINE, <<"msgoffline">>).
+-define(NS_FLEX_OFFLINE, <<"http://jabber.org/protocol/offline">>).
-define(NS_COMPRESS,
<<"http://jabber.org/protocol/compress">>).
-define(NS_CAPS, <<"http://jabber.org/protocol/caps">>).
@@ -156,3 +157,10 @@
-define(NS_HTTP_UPLOAD_OLD, <<"eu:siacs:conversations:http:upload">>).
-define(NS_THUMBS_1, <<"urn:xmpp:thumbs:1">>).
-define(NS_NICK, <<"http://jabber.org/protocol/nick">>).
+-define(NS_MIX_0, <<"urn:xmpp:mix:0">>).
+-define(NS_MIX_SERVICEINFO_0, <<"urn:xmpp:mix:0#serviceinfo">>).
+-define(NS_MIX_NODES_MESSAGES, <<"urn:xmpp:mix:nodes:messages">>).
+-define(NS_MIX_NODES_PRESENCE, <<"urn:xmpp:mix:nodes:presence">>).
+-define(NS_MIX_NODES_PARTICIPANTS, <<"urn:xmpp:mix:nodes:participants">>).
+-define(NS_MIX_NODES_SUBJECT, <<"urn:xmpp:mix:nodes:subject">>).
+-define(NS_MIX_NODES_CONFIG, <<"urn:xmpp:mix:nodes:config">>).
diff --git a/include/pubsub.hrl b/include/pubsub.hrl
index 209f802d..3aa090d3 100644
--- a/include/pubsub.hrl
+++ b/include/pubsub.hrl
@@ -65,7 +65,7 @@
%% note: pos_integer() should always be used, but we allow anything else coded
%% as binary, so one can have a custom implementation of nodetree with custom
%% indexing (see nodetree_virtual). this also allows to use any kind of key for
-%% indexing nodes, as this can be usefull with external backends such as odbc.
+%% indexing nodes, as this can be usefull with external backends such as sql.
-type(itemId() :: binary()).
%% @type itemId() = string().
diff --git a/lib/ct_formatter.ex b/lib/ct_formatter.ex
new file mode 100644
index 00000000..47c487ac
--- /dev/null
+++ b/lib/ct_formatter.ex
@@ -0,0 +1,130 @@
+defmodule ExUnit.CTFormatter do
+ @moduledoc false
+
+ use GenEvent
+
+ import ExUnit.Formatter, only: [format_time: 2, format_filters: 2, format_test_failure: 5,
+ format_test_case_failure: 5]
+
+ def init(opts) do
+ file = File.open! "exunit.log", [:append]
+ # We do not print filter in log file as exclusion of test with tag
+ # pending: true is always done
+ config = %{
+ file: file,
+ seed: opts[:seed],
+ trace: opts[:trace],
+ colors: Keyword.put_new(opts[:colors], :enabled, false),
+ width: 80,
+ tests_counter: 0,
+ failures_counter: 0,
+ skipped_counter: 0,
+ invalids_counter: 0
+ }
+ {:ok, config}
+ end
+
+ def handle_event({:suite_started, _opts}, config) do
+ {:ok, config}
+ end
+
+ def handle_event({:suite_finished, run_us, load_us}, config) do
+ print_suite(config, run_us, load_us)
+ File.close config[:file]
+ :remove_handler
+ end
+
+ def handle_event({:test_started, %ExUnit.Test{} = test}, config) do
+ if config.tests_counter == 0, do: IO.binwrite config[:file], "== Running #{test.case} ==\n\n"
+ {:ok, config}
+ end
+
+ def handle_event({:test_finished, %ExUnit.Test{state: nil} = _test}, config) do
+ IO.binwrite config[:file], "."
+ {:ok, %{config | tests_counter: config.tests_counter + 1}}
+ end
+
+ def handle_event({:test_finished, %ExUnit.Test{state: {:skip, _}} = _test}, config) do
+ {:ok, %{config | tests_counter: config.tests_counter + 1,
+ skipped_counter: config.skipped_counter + 1}}
+ end
+
+ def handle_event({:test_finished, %ExUnit.Test{state: {:invalid, _}} = _test}, config) do
+ IO.binwrite config[:file], "?"
+ {:ok, %{config | tests_counter: config.tests_counter + 1,
+ invalids_counter: config.invalids_counter + 1}}
+ end
+
+ def handle_event({:test_finished, %ExUnit.Test{state: {:failed, failures}} = test}, config) do
+ formatted = format_test_failure(test, failures, config.failures_counter + 1,
+ config.width, &formatter(&1, &2, config))
+ print_failure(formatted, config)
+ print_logs(test.logs)
+
+ {:ok, %{config | tests_counter: config.tests_counter + 1,
+ failures_counter: config.failures_counter + 1}}
+ end
+
+ def handle_event({:case_started, %ExUnit.TestCase{}}, config) do
+ {:ok, config}
+ end
+
+ def handle_event({:case_finished, %ExUnit.TestCase{state: nil}}, config) do
+ {:ok, config}
+ end
+
+ def handle_event({:case_finished, %ExUnit.TestCase{state: {:failed, failures}} = test_case}, config) do
+ formatted = format_test_case_failure(test_case, failures, config.failures_counter + 1,
+ config.width, &formatter(&1, &2, config))
+ print_failure(formatted, config)
+ {:ok, %{config | failures_counter: config.failures_counter + 1}}
+ end
+
+ ## Printing
+
+ defp print_suite(config, run_us, load_us) do
+ IO.binwrite config[:file], "\n\n"
+ IO.binwrite config[:file], format_time(run_us, load_us)
+ IO.binwrite config[:file], "\n\n"
+
+ # singular/plural
+ test_pl = pluralize(config.tests_counter, "test", "tests")
+ failure_pl = pluralize(config.failures_counter, "failure", "failures")
+
+ message =
+ "#{config.tests_counter} #{test_pl}, #{config.failures_counter} #{failure_pl}"
+ |> if_true(config.skipped_counter > 0, & &1 <> ", #{config.skipped_counter} skipped")
+ |> if_true(config.invalids_counter > 0, & &1 <> ", #{config.invalids_counter} invalid")
+
+ cond do
+ config.failures_counter > 0 -> IO.binwrite config[:file], message
+ config.invalids_counter > 0 -> IO.binwrite config[:file], message
+ true -> IO.binwrite config[:file], message
+ end
+
+ IO.binwrite config[:file], "\nRandomized with seed #{config.seed}\n\n\n\n"
+ end
+
+ defp if_true(value, false, _fun), do: value
+ defp if_true(value, true, fun), do: fun.(value)
+
+ defp print_failure(formatted, config) do
+ IO.binwrite config[:file], "\n"
+ IO.binwrite config[:file], formatted
+ IO.binwrite config[:file], "\n"
+ end
+
+ defp formatter(_, msg, _config),
+ do: msg
+
+ defp pluralize(1, singular, _plural), do: singular
+ defp pluralize(_, _singular, plural), do: plural
+
+ defp print_logs(""), do: nil
+
+ defp print_logs(output) do
+ indent = "\n "
+ output = String.replace(output, "\n", indent)
+ IO.puts([" The following output was logged:", indent | output])
+ end
+end
diff --git a/lib/ejabberd.ex b/lib/ejabberd.ex
deleted file mode 100644
index a843abc9..00000000
--- a/lib/ejabberd.ex
+++ /dev/null
@@ -1,2 +0,0 @@
-defmodule Ejabberd do
-end
diff --git a/mix.exs b/mix.exs
index 9a1f7072..8f58e677 100644
--- a/mix.exs
+++ b/mix.exs
@@ -3,9 +3,9 @@ defmodule Ejabberd.Mixfile do
def project do
[app: :ejabberd,
- version: "16.01.0",
+ version: "16.03.0",
description: description,
- elixir: "~> 1.1",
+ elixir: "~> 1.2",
elixirc_paths: ["lib"],
compile_path: ".",
compilers: [:asn1] ++ Mix.compilers,
@@ -25,9 +25,9 @@ defmodule Ejabberd.Mixfile do
[mod: {:ejabberd_app, []},
applications: [:ssl],
included_applications: [:lager, :mnesia, :p1_utils, :cache_tab,
- :p1_tls, :p1_stringprep, :p1_xml,
- :p1_stun, :p1_yaml, :p1_zlib, :p1_iconv,
- :esip, :jiffy, :oauth2, :xmlrpc, :eredis,
+ :fast_tls, :stringprep, :fast_xml,
+ :stun, :fast_yaml, :ezlib, :iconv,
+ :esip, :jiffy, :p1_oauth2, :p1_xmlrpc, :eredis,
:p1_mysql, :p1_pgsql, :sqlite3]]
end
@@ -38,25 +38,25 @@ defmodule Ejabberd.Mixfile do
end
defp deps do
- [{:lager, git: "https://github.com/basho/lager", tag: "3.0.2"},
- {:p1_utils, git: "https://github.com/processone/p1_utils", tag: "1.0.3", override: true},
- {:cache_tab, git: "https://github.com/processone/cache_tab", tag: "1.0.2"},
- {:p1_tls, git: "https://github.com/processone/tls", tag: "1.0.0"},
- {:p1_stringprep, git: "https://github.com/processone/stringprep", tag: "1.0.1"},
- {:p1_xml, git: "https://github.com/processone/xml", tag: "1.1.2"},
- {:p1_stun, git: "https://github.com/processone/stun", tag: "0.9.1"},
- {:esip, git: "https://github.com/processone/p1_sip", tag: "1.0.1"},
- {:p1_yaml, git: "https://github.com/processone/p1_yaml", tag: "1.0.1"},
- {:jiffy, git: "https://github.com/davisp/jiffy", tag: "0.14.5"},
- {:oauth2, git: "https://github.com/kivra/oauth2", ref: "8d129fbf8866930b4ffa6dd84e65bd2b32b9acb8"},
- {:xmlrpc, git: "https://github.com/rds13/xmlrpc.git", tag: "1.15"},
- {:p1_mysql, git: "https://github.com/processone/mysql", tag: "1.0.0"},
- {:p1_pgsql, git: "https://github.com/processone/pgsql", tag: "1.0.0"},
- {:sqlite3, git: "https://github.com/alexeyr/erlang-sqlite3", ref: "cbc3505f7a131254265d3ef56191b2581b8cc172"},
- {:p1_zlib, git: "https://github.com/processone/zlib", tag: "1.0.0"},
- {:p1_iconv, git: "https://github.com/processone/eiconv", tag: "0.9.0"},
- {:eredis, git: "https://github.com/wooga/eredis", tag: "v1.0.8"},
- {:exrm, "0.19.9"}]
+ [{:lager, "~> 3.0"},
+ {:p1_utils, "~> 1.0"},
+ {:cache_tab, "~> 1.0"},
+ {:stringprep, "~> 1.0"},
+ {:fast_yaml, "~> 1.0"},
+ {:fast_tls, "~> 1.0"},
+ {:fast_xml, "~> 1.1"},
+ {:stun, "~> 1.0"},
+ {:esip, "~> 1.0"},
+ {:jiffy, "~> 0.14.7"},
+ {:p1_oauth2, "~> 0.6.1"},
+ {:p1_xmlrpc, "~> 1.15"},
+ {:p1_mysql, "~> 1.0"},
+ {:p1_pgsql, "~> 1.0"},
+ {:sqlite3, "~> 1.1"},
+ {:ezlib, "~> 1.0"},
+ {:iconv, "~> 1.0"},
+ {:eredis, "~> 1.0"},
+ {:exrm, "~> 1.0.0-rc7", only: :dev}]
end
defp package do
@@ -66,7 +66,8 @@ defmodule Ejabberd.Mixfile do
licenses: ["GPLv2"],
links: %{"Site" => "https://www.ejabberd.im",
"Documentation" => "http://docs.ejabberd.im",
- "Source" => "https://github.com/processone/ejabberd"}]
+ "Source" => "https://github.com/processone/ejabberd",
+ "ProcessOne" => "http://www.process-one.net/"}]
end
end
diff --git a/mix.lock b/mix.lock
index 5f495511..6cc41bba 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,8 +1,26 @@
%{"bbmustache": {:hex, :bbmustache, "1.0.4"},
- "erlware_commons": {:hex, :erlware_commons, "0.18.0"},
+ "cache_tab": {:hex, :cache_tab, "1.0.2"},
+ "cf": {:hex, :cf, "0.2.1"},
+ "eredis": {:hex, :eredis, "1.0.8"},
+ "erlware_commons": {:hex, :erlware_commons, "0.19.0"},
+ "esip": {:hex, :esip, "1.0.2"},
+ "exrm": {:hex, :exrm, "1.0.3"},
+ "ezlib": {:hex, :ezlib, "1.0.1"},
+ "fast_tls": {:hex, :fast_tls, "1.0.1"},
+ "fast_xml": {:hex, :fast_xml, "1.1.11"},
+ "fast_yaml": {:hex, :fast_yaml, "1.0.3"},
"getopt": {:hex, :getopt, "0.8.2"},
+ "goldrush": {:hex, :goldrush, "0.1.7"},
+ "iconv": {:hex, :iconv, "1.0.0"},
+ "jiffy": {:hex, :jiffy, "0.14.7"},
+ "lager": {:hex, :lager, "3.0.2"},
+ "p1_mysql": {:hex, :p1_mysql, "1.0.1"},
+ "p1_oauth2": {:hex, :p1_oauth2, "0.6.1"},
+ "p1_pgsql": {:hex, :p1_pgsql, "1.1.0"},
+ "p1_utils": {:hex, :p1_utils, "1.0.3"},
+ "p1_xmlrpc": {:hex, :p1_xmlrpc, "1.15.1"},
"providers": {:hex, :providers, "1.6.0"},
- "cf": {:hex, :cf, "0.2.1"},
- "relx": {:hex, :relx, "3.11.0"},
- "conform": {:hex, :conform, "0.17.0"},
- "neotoma": {:hex, :neotoma, "1.7.3"}}
+ "relx": {:hex, :relx, "3.18.0"},
+ "sqlite3": {:hex, :sqlite3, "1.1.5"},
+ "stringprep": {:hex, :stringprep, "1.0.3"},
+ "stun": {:hex, :stun, "1.0.1"}}
diff --git a/priv/msgs/cs.msg b/priv/msgs/cs.msg
index a370d02a..f0c74988 100644
--- a/priv/msgs/cs.msg
+++ b/priv/msgs/cs.msg
@@ -1,4 +1,5 @@
%% -*- coding: latin-1 -*-
+{"Accept","Přijmout"}.
{"Access Configuration","Konfigurace přístupů"}.
{"Access Control List Configuration","Konfigurace seznamu přístupových práv (ACL)"}.
{"Access control lists","Seznamy přístupových práv (ACL)"}.
@@ -31,6 +32,7 @@
{"April",". dubna"}.
{"August",". srpna"}.
{"Backup Management","Správa zálohování"}.
+{"Backup of ~p","Záloha ~p"}.
{"Backup to File at ","Záloha do souboru na "}.
{"Backup","Zálohovat"}.
{"Bad format","Nesprávný formát"}.
@@ -59,6 +61,7 @@
{"Country","Země"}.
{"CPU Time:","Čas procesoru"}.
{"Database","Databáze"}.
+{"Database Tables at ~p","Databázové tabulky na ~p"}.
{"Database Tables Configuration at ","Konfigurace databázových tabulek "}.
{"December",". prosince"}.
{"Default users as participants","Uživatelé jsou implicitně členy"}.
@@ -78,13 +81,16 @@
{"Either approve or decline the voice request.","Povolit nebo odmítnout voice žádost."}.
{"ejabberd IRC module","ejabberd IRC modul"}.
{"ejabberd MUC module","ejabberd MUC modul"}.
+{"ejabberd Multicast service","Služba ejabberd Multicast"}.
{"ejabberd Publish-Subscribe module","ejabberd Publish-Subscribe modul"}.
{"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams modul"}.
{"ejabberd vCard module","ejabberd vCard modul"}.
{"ejabberd Web Admin","Webová administrace ejabberd"}.
{"Elements","Položek"}.
{"Email","E-mail"}.
+{"Empty Rooms","Prázdné konference"}.
{"Enable logging","Zaznamenávat konverzace"}.
+{"Enable message archiving","Povolit ukládání historie zpráv"}.
{"Encoding for server ~b","Kódování pro server ~b"}.
{"End User Session","Ukončit sezení uživatele"}.
{"Enter list of {Module, [Options]}","Vložte seznam modulů {Modul, [Parametry]}"}.
@@ -100,6 +106,7 @@
{"Error","Chyba"}.
{"Example: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}].","Příklad: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}].2\"}]."}.
{"Exclude Jabber IDs from CAPTCHA challenge","Vyloučit Jabber ID z procesu CAPTCHA ověřování"}.
+{"Export all tables as SQL queries to a file:","Zálohovat všechny tabulky jako SQL dotazy do souboru:"}.
{"Export data of all users in the server to PIEFXIS files (XEP-0227):","Exportovat všechny uživatele do souboru ve formátu PIEFXIS (XEP-0227):"}.
{"Export data of users in a host to PIEFXIS files (XEP-0227):","Exportovat uživatele na hostiteli do souboru ve formátu PIEFXIS (XEP-0227):"}.
{"Failed to extract JID from your voice request approval","Došlo k chybě při získávání Jabber ID z vaší žádosti o voice práva"}.
@@ -137,6 +144,7 @@
{"Import Users from Dir at ","Importovat uživatele z adresáře na "}.
{"Import Users From jabberd14 Spool Files","Importovat uživatele z jabberd14 spool souborů"}.
{"Improper message type","Nesprávný typ zprávy"}.
+{"Incoming s2s Connections:",""}.
{"Incorrect password","Nesprávné heslo"}.
{"Invalid affiliation: ~s","Neplatné přiřazení: ~s"}.
{"Invalid role: ~s","Neplatná role: ~s"}.
@@ -149,6 +157,7 @@
{"IRC username","IRC přezdívka"}.
{"IRC Username","IRC přezdívka"}.
{"is now known as","se přejmenoval(a) na"}.
+{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Není povoleno posílat chybové zprávy do konference. Účastník (~s) odeslal chybovou zprávu (~s) a byl vyhozen z konference."}.
{"It is not allowed to send private messages","Je zakázáno posílat soukromé zprávy"}.
{"It is not allowed to send private messages of type \"groupchat\"","Není dovoleno odeslání soukromé zprávy typu \"skupinová zpráva\" "}.
{"It is not allowed to send private messages to the conference","Není povoleno odesílat soukromé zprávy do konference"}.
@@ -170,6 +179,7 @@
{"Listened Ports at ","Otevřené porty na "}.
{"Listened Ports","Otevřené porty"}.
{"List of modules to start","Seznam modulů, které mají být spuštěné"}.
+{"List of rooms","Seznam konferencí"}.
{"Low level update script","Nízkoúrovňový aktualizační skript"}.
{"Make participants list public","Nastavit seznam účastníků jako veřejný"}.
{"Make room CAPTCHA protected","Chránit místnost pomocí CAPTCHA"}.
@@ -190,12 +200,16 @@
{"Message body","Tělo zprávy"}.
{"Middle Name","Druhé jméno"}.
{"Minimum interval between voice requests (in seconds)","Minimální interval mezi žádostmi o voice práva (v sekundách)"}.
+{"Moderator","Moderátor"}.
{"Moderator privileges required","Potřebujete práva moderátora"}.
{"moderators only","moderátorům"}.
{"Modified modules","Aktualizované moduly"}.
{"Module","Modul"}.
+{"Modules at ~p","Moduly v ~p"}.
{"Modules","Moduly"}.
{"Monday","Pondělí"}.
+{"Multicast","Multicast"}.
+{"Multi-User Chat","Víceuživatelský chat"}.
{"Name:","Jméno:"}.
{"Name","Jméno"}.
{"Never","Nikdy"}.
@@ -208,6 +222,7 @@
{"No Data","Žádná data"}.
{"Node ID","ID uzlu"}.
{"Node not found","Uzel nenalezen"}.
+{"Node ~p","Uzel ~p"}.
{"Nodes","Uzly"}.
{"No limit","Bez limitu"}.
{"None","Nic"}.
@@ -229,6 +244,7 @@
{"Online Users:","Online uživatelé:"}.
{"Online Users","Online uživatelé"}.
{"Only deliver notifications to available users","Doručovat upozornění jen právě přihlášeným uživatelům"}.
+{"Only members may query archives of this room","Pouze moderátoři mají povoleno měnit téma místnosti"}.
{"Only moderators and participants are allowed to change the subject in this room","Jen moderátoři a účastníci mají povoleno měnit téma této místnosti"}.
{"Only moderators are allowed to change the subject in this room","Jen moderátoři mají povoleno měnit téma místnosti"}.
{"Only moderators can approve voice requests","Pouze moderátoři mohou schválit žádosti o voice práva"}.
@@ -242,6 +258,7 @@
{"Outgoing s2s Connections","Odchozí s2s spojení"}.
{"Owner privileges required","Jsou vyžadována práva vlastníka"}.
{"Packet","Paket"}.
+{"Participant","Účastník"}.
{"Password ~b","Heslo ~b"}.
{"Password:","Heslo:"}.
{"Password","Heslo"}.
@@ -251,9 +268,12 @@
{"Path to File","Cesta k souboru"}.
{"Pending","Čekající"}.
{"Period: ","Čas: "}.
+{"Permanent rooms","Stálých konferencí"}.
{"Persist items to storage","Uložit položky natrvalo do úložiště"}.
{"Ping","Ping"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Podotýkáme, že tato nastavení budou zálohována do zabudované databáze Mnesia. Pokud používáte ODBC modul, musíte zálohovat svoji SQL databázi samostatně."}.
+{"Please specify file name.","Zvolit jméno souboru."}.
+{"Please specify file size.","Zvolit velikost souboru."}.
{"Please, wait for a while before sending new voice request","Prosím, počkejte chvíli před posláním nové žádosti o voice práva"}.
{"Pong","Pong"}.
{"Port ~b","Port ~b"}.
@@ -271,6 +291,7 @@
{"Really delete message of the day?","Skutečně smazat zprávu dne?"}.
{"Recipient is not in the conference room","Příjemce se nenachází v konferenční místnosti"}.
{"Register a Jabber account","Zaregistrujte si účet Jabberu"}.
+{"Registered nicknames","Registrované přezdívky"}.
{"Registered Users","Registrovaní uživatelé"}.
{"Registered Users:","Registrovaní živatelé:"}.
{"Register","Zaregistrovat se"}.
@@ -288,6 +309,7 @@
{"Restore binary backup immediately:","Okamžitě obnovit binární zálohu:"}.
{"Restore","Obnovit"}.
{"Restore plain text backup immediately:","Okamžitě obnovit zálohu z textového souboru:"}.
+{"Roles for which Presence is Broadcasted","Role, pro které je zpráva o stavu šířena"}.
{"Room Configuration","Nastavení místnosti"}.
{"Room creation is denied by service policy","Pravidla služby nepovolují vytvořit místnost"}.
{"Room description","Popis místnosti"}.
@@ -311,6 +333,7 @@
{"September",". září"}.
{"Server ~b","Server ~b"}.
{"Server:","Server:"}.
+{"Server","Server"}.
{"Set message of the day and send to online users","Nastavit zprávu dne a odeslat ji online uživatelům"}.
{"Set message of the day on all hosts and send to online users","Nastavit zprávu dne a odeslat ji online uživatelům"}.
{"Shared Roster Groups","Skupiny pro sdílený seznam kontaktů"}.
@@ -352,6 +375,7 @@
{"There was an error changing the password: ","Při změně hesla došlo k chybě: "}.
{"There was an error creating the account: ","Při vytváření účtu došlo k chybě."}.
{"There was an error deleting the account: ","Při mazání účtu došlo k chybě: "}.
+{"This IP address is blacklisted in ~s","IP adresa je blokována na ~s"}.
{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","Zde nezáleží na velikosti písmen: macbeth je stejný jako MacBeth a Macbeth."}.
{"This page allows to create a Jabber account in this Jabber server. Your JID (Jabber IDentifier) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","Na této stránce si můžete vytvořit účet na tomto serveru Jabberu. Vaše JID (Jabber IDentifikátor) bude mít tvar: uživatelskéjméno@server. Přečtěte si prosím pozorně instrukce pro vyplnění údajů."}.
{"This page allows to unregister a Jabber account in this Jabber server.","Zde můžete zrušit registraci účtu na tomto serveru Jabberu."}.
@@ -359,8 +383,11 @@
{"Time","Čas"}.
{"Time delay","Časový posun"}.
{"Too many CAPTCHA requests","Přiliš mnoho CAPTCHA žádostí"}.
+{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","Příliš mnoho (~p) chybných pokusů o přihlášení z této IP adresy (~s). Adresa bude zablokována do ~s UTC"}.
+{"Too many unacked stanzas","Příliš mnoho nepotvrzených stanz"}.
{"To","Pro"}.
{"To ~s","Pro ~s"}.
+{"Total rooms","Celkem konferencí"}.
{"Traffic rate limit is exceeded","Byl překročen limit"}.
{"Transactions Aborted:","Transakce zrušena"}.
{"Transactions Committed:","Transakce potvrzena"}.
@@ -374,6 +401,7 @@
{"Update","Aktualizovat"}.
{"Update message of the day (don't send)","Aktualizovat zprávu dne (neodesílat)"}.
{"Update message of the day on all hosts (don't send)","Aktualizovat zprávu dne pro všechny hostitele (neodesílat)"}.
+{"Update ~p","Aktualizovat ~p"}.
{"Update plan","Aktualizovat plán"}.
{"Update script","Aktualizované skripty"}.
{"Uptime:","Čas běhu:"}.
@@ -381,6 +409,7 @@
{"User JID","Jabber ID uživatele"}.
{"User Management","Správa uživatelů"}.
{"Username:","Uživatelské jméno:"}.
+{"User ~s",""}.
{"Users are not allowed to register accounts so quickly","Je zakázáno registrovat účty v tak rychlém sledu"}.
{"Users Last Activity","Poslední aktivita uživatele"}.
{"Users","Uživatelé"}.
@@ -388,6 +417,7 @@
{"Validate","Ověřit"}.
{"vCard User Search","Hledání uživatelů podle vizitek"}.
{"Virtual Hosts","Virtuální hostitelé"}.
+{"Visitor","Návštěvník"}.
{"Visitors are not allowed to change their nicknames in this room","Návštěvníkům této místnosti je zakázáno měnit přezdívku"}.
{"Visitors are not allowed to send messages to all occupants","Návštevníci nemají povoleno zasílat zprávy všem účastníkům konference"}.
{"Voice requests are disabled in this conference","Voice žádosti jsou v této konferenci zakázány"}.
diff --git a/priv/msgs/de.msg b/priv/msgs/de.msg
index 1d313b2a..dd22e684 100644
--- a/priv/msgs/de.msg
+++ b/priv/msgs/de.msg
@@ -1,4 +1,5 @@
%% -*- coding: latin-1 -*-
+{"Accept","Akzeptieren"}.
{"Access Configuration","Zugangskonfiguration"}.
{"Access Control List Configuration","Konfiguration der Zugangskontrolllisten"}.
{"Access control lists","Zugangskontroll-Listen (ACL)"}.
@@ -87,6 +88,7 @@
{"ejabberd Web Admin","ejabberd Web-Admin"}.
{"Elements","Elemente"}.
{"Email","E-Mail"}.
+{"Empty Rooms","Leere Räume"}.
{"Enable logging","Protokollierung aktivieren"}.
{"Enable message archiving","Nachrichtenarchivierung aktivieren"}.
{"Encoding for server ~b","Kodierung für Server ~b"}.
@@ -142,6 +144,7 @@
{"Import Users from Dir at ","Benutzer importieren aus dem Verzeichnis "}.
{"Import Users From jabberd14 Spool Files","Importiere Benutzer aus jabberd14-Spool-Dateien"}.
{"Improper message type","Unzulässiger Nachrichtentyp"}.
+{"Incoming s2s Connections:","Eingehende s2s-Verbindungen:"}.
{"Incorrect password","Falsches Passwort"}.
{"Invalid affiliation: ~s","Ungültige Mitgliedschaft: ~s"}.
{"Invalid role: ~s","Ungültige Rolle: ~s"}.
@@ -154,6 +157,7 @@
{"IRC username","IRC Benutzername"}.
{"IRC Username","IRC-Benutzername"}.
{"is now known as","ist nun bekannt als"}.
+{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Es ist nicht erlaubt Fehlermeldungen an den Raum zu senden. Der Teilnehmer (~s) hat eine Fehlermeldung (~s) gesendet und wurde aus dem Raum entfernt"}.
{"It is not allowed to send private messages","Es ist nicht erlaubt private Nachrichten zu senden"}.
{"It is not allowed to send private messages of type \"groupchat\"","Es ist nicht erlaubt private Nachrichten des Typs \"Gruppenchat\" zu senden"}.
{"It is not allowed to send private messages to the conference","Es ist nicht erlaubt private Nachrichten an den Raum zu schicken"}.
@@ -196,6 +200,7 @@
{"Message body","Nachrichtentext"}.
{"Middle Name","Zweiter Vorname"}.
{"Minimum interval between voice requests (in seconds)","Mindestdauer zwischen Anfragen für Sprachrechte (in Sekunden)"}.
+{"Moderator","Moderator"}.
{"Moderator privileges required","Moderatorrechte benötigt"}.
{"moderators only","ausschliesslich Moderatoren"}.
{"Modified modules","Geänderte Module"}.
@@ -210,7 +215,7 @@
{"Never","Nie"}.
{"New Password:","Neues Passwort:"}.
{"Nickname","Benutzername"}.
-{"Nickname Registration at ","Registrieren des Benutzernames auf"}.
+{"Nickname Registration at ","Registrieren des Benutzernames auf "}.
{"Nickname ~s does not exist in the room","Der Benutzername ~s existiert im Raum nicht"}.
{"nobody","niemanden"}.
{"No body provided for announce message","Kein Text für die Ankündigungsnachricht angegeben"}.
@@ -239,6 +244,7 @@
{"Online Users:","Angemeldete Benutzer:"}.
{"Online Users","Angemeldete Benutzer"}.
{"Only deliver notifications to available users","Benachrichtigungen nur an verfügbare Benutzer schicken"}.
+{"Only members may query archives of this room","Nur Mitglieder dürfen den Verlauf dieses Raumes abrufen"}.
{"Only moderators and participants are allowed to change the subject in this room","Nur Moderatoren und Mitglieder dürfen das Thema in diesem Raum ändern"}.
{"Only moderators are allowed to change the subject in this room","Nur Moderatoren dürfen das Thema in diesem Raum ändern"}.
{"Only moderators can approve voice requests","Nur Moderatoren können Anfragen für Sprachrechte bestätigen"}.
@@ -252,6 +258,7 @@
{"Outgoing s2s Connections","Ausgehende s2s-Verbindungen"}.
{"Owner privileges required","Besitzerrechte benötigt"}.
{"Packet","Paket"}.
+{"Participant","Teilnehmer"}.
{"Password ~b","Passwort ~b"}.
{"Password:","Passwort:"}.
{"Password","Passwort"}.
@@ -261,10 +268,12 @@
{"Path to File","Pfad zur Datei"}.
{"Pending","Schwebend"}.
{"Period: ","Zeitraum: "}.
-{"Permanent rooms","permanente Chaträume"}.
+{"Permanent rooms","Permanente Chaträume"}.
{"Persist items to storage","Einträge dauerhaft speichern"}.
{"Ping","Ping"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Beachten sie, das diese Optionen nur die eingebaute Mnesia-Datenbank sichern. Wenn sie das ODBC-Modul verwenden, müssen sie die SQL-Datenbank manuell sichern."}.
+{"Please specify file name.","Bitte geben Sie den Dateinamen an."}.
+{"Please specify file size.","Bitte geben Sie die Dateigröße an."}.
{"Please, wait for a while before sending new voice request","Bitte warten sie ein wenig, bevor sie eine weitere Anfrage für Sprachrechte senden"}.
{"Pong","Pong"}.
{"Port ~b","Port ~b"}.
@@ -300,6 +309,7 @@
{"Restore binary backup immediately:","Stelle binäre Sicherung sofort wieder her:"}.
{"Restore plain text backup immediately:","Stelle Klartext-Sicherung sofort wieder her:"}.
{"Restore","Wiederherstellung"}.
+{"Roles for which Presence is Broadcasted","Rollen, für die der Status übertragen wird"}.
{"Room Configuration","Raum-Konfiguration"}.
{"Room creation is denied by service policy","Anlegen des Raumes aufgrund der Dienstrichtlinien verweigert"}.
{"Room description","Raum Beschreibung"}.
@@ -323,6 +333,7 @@
{"September","September"}.
{"Server ~b","Server ~b"}.
{"Server:","Server:"}.
+{"Server","Server"}.
{"Set message of the day and send to online users","Setze Nachricht des Tages und sende sie an alle angemeldeten Benutzer"}.
{"Set message of the day on all hosts and send to online users","Setze Nachricht des Tages auf allen Hosts und sende sie an alle angemeldeten Benutzer"}.
{"Shared Roster Groups","Gruppen der gemeinsamen Kontaktliste"}.
@@ -406,6 +417,7 @@
{"Validate","Validieren"}.
{"vCard User Search","vCard-Benutzer-Suche"}.
{"Virtual Hosts","Virtuelle Hosts"}.
+{"Visitor","Besucher"}.
{"Visitors are not allowed to change their nicknames in this room","Besucher dürfen in diesem Raum ihren Benutzernamen nicht ändern"}.
{"Visitors are not allowed to send messages to all occupants","Besucher dürfen nicht an alle Teilnehmer Nachrichten verschicken"}.
{"Voice request","Anfrage für Sprachrechte"}.
diff --git a/priv/msgs/de.po b/priv/msgs/de.po
index 4f3d2a37..09df3e9a 100644
--- a/priv/msgs/de.po
+++ b/priv/msgs/de.po
@@ -16,7 +16,7 @@ msgstr ""
"X-Additional-Translator: Patrick Dreker\n"
"X-Additional-Translator: Torsten Werner\n"
"X-Additional-Translator: Marina Hahn\n"
-"X-Generator: Poedit 1.8.4\n"
+"X-Generator: Poedit 1.8.6\n"
#: ejabberd_c2s.erl:505 ejabberd_c2s.erl:853
msgid "Use of STARTTLS required"
@@ -72,9 +72,8 @@ msgid "User"
msgstr "Benutzer"
#: ejabberd_oauth.erl:256
-#, fuzzy
msgid "Server"
-msgstr "Server:"
+msgstr "Server"
#: ejabberd_oauth.erl:259 ejabberd_web_admin.erl:1408 mod_configure.erl:1398
#: mod_configure.erl:1485 mod_configure.erl:1889 mod_configure.erl:2123
@@ -84,7 +83,7 @@ msgstr "Passwort"
#: ejabberd_oauth.erl:267
msgid "Accept"
-msgstr ""
+msgstr "Akzeptieren"
#: ejabberd_web_admin.erl:202 ejabberd_web_admin.erl:214
#: ejabberd_web_admin.erl:234 ejabberd_web_admin.erl:246
@@ -156,8 +155,7 @@ msgstr "Virtuelle Hosts"
msgid "Users"
msgstr "Benutzer"
-#: ejabberd_web_admin.erl:955 ejabberd_web_admin.erl:1340
-#: mod_configure.erl:524
+#: ejabberd_web_admin.erl:955 ejabberd_web_admin.erl:1340 mod_configure.erl:524
msgid "Online Users"
msgstr "Angemeldete Benutzer"
@@ -251,9 +249,8 @@ msgid "Outgoing s2s Connections:"
msgstr "Ausgehende s2s-Verbindungen:"
#: ejabberd_web_admin.erl:1559
-#, fuzzy
msgid "Incoming s2s Connections:"
-msgstr "Ausgehende s2s-Verbindungen:"
+msgstr "Eingehende s2s-Verbindungen:"
#: ejabberd_web_admin.erl:1595 ejabberd_web_admin.erl:1794
#: ejabberd_web_admin.erl:1804 ejabberd_web_admin.erl:2214 mod_roster.erl:1429
@@ -855,11 +852,11 @@ msgstr ""
#: mod_http_upload.erl:586
msgid "Please specify file size."
-msgstr ""
+msgstr "Bitte geben Sie die Dateigröße an."
#: mod_http_upload.erl:590
msgid "Please specify file name."
-msgstr ""
+msgstr "Bitte geben Sie den Dateinamen an."
#: mod_ip_blacklist.erl:121
msgid "This IP address is blacklisted in ~s"
@@ -978,9 +975,8 @@ msgid "Server ~b"
msgstr "Server ~b"
#: mod_mam.erl:541
-#, fuzzy
msgid "Only members may query archives of this room"
-msgstr "Nur Moderatoren dürfen das Thema in diesem Raum ändern"
+msgstr "Nur Mitglieder dürfen den Verlauf dieses Raumes abrufen"
#: mod_muc.erl:585
msgid "Only service administrators are allowed to send service messages"
@@ -1001,7 +997,7 @@ msgstr "Chaträume"
#: mod_muc.erl:781
msgid "Empty Rooms"
-msgstr ""
+msgstr "Leere Räume"
#: mod_muc.erl:933
msgid "You need a client that supports x:data to register the nickname"
@@ -1011,7 +1007,7 @@ msgstr ""
#: mod_muc.erl:943
msgid "Nickname Registration at "
-msgstr "Registrieren des Benutzernames auf"
+msgstr "Registrieren des Benutzernames auf "
#: mod_muc.erl:949
msgid "Enter nickname you want to register"
@@ -1045,7 +1041,7 @@ msgstr "Alle Chaträume"
#: mod_muc_admin.erl:250
msgid "Permanent rooms"
-msgstr "permanente Chaträume"
+msgstr "Permanente Chaträume"
#: mod_muc_admin.erl:251
msgid "Registered nicknames"
@@ -1200,6 +1196,8 @@ msgid ""
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
+"Es ist nicht erlaubt Fehlermeldungen an den Raum zu senden. Der Teilnehmer "
+"(~s) hat eine Fehlermeldung (~s) gesendet und wurde aus dem Raum entfernt"
#: mod_muc_room.erl:241
msgid "It is not allowed to send private messages to the conference"
@@ -1377,20 +1375,19 @@ msgstr "jeden"
#: mod_muc_room.erl:3471
msgid "Roles for which Presence is Broadcasted"
-msgstr ""
+msgstr "Rollen, für die der Status übertragen wird"
#: mod_muc_room.erl:3486
-#, fuzzy
msgid "Moderator"
-msgstr "ausschliesslich Moderatoren"
+msgstr "Moderator"
#: mod_muc_room.erl:3496
msgid "Participant"
-msgstr ""
+msgstr "Teilnehmer"
#: mod_muc_room.erl:3506
msgid "Visitor"
-msgstr ""
+msgstr "Besucher"
#: mod_muc_room.erl:3513
msgid "Make room members-only"
diff --git a/priv/msgs/gl.po b/priv/msgs/gl.po
index 7a8b27dc..4d75d21d 100644
--- a/priv/msgs/gl.po
+++ b/priv/msgs/gl.po
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
-"Project-Id-Version: 2.1.0-alpha\n"
-"Last-Translator: Carlos E. Lopez - suso AT jabber-hispano.org\n"
+"Project-Id-Version: 16.02\n"
+"Last-Translator: Carlos E. Lopez - carlos AT suchat.org\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -9,7 +9,7 @@ msgstr ""
#: ejabberd_c2s.erl:505 ejabberd_c2s.erl:853
msgid "Use of STARTTLS required"
-msgstr "É obrigatorio usar STARTTLS"
+msgstr "Requírese o uso de STARTTLS"
#: ejabberd_c2s.erl:604
msgid "No resource provided"
@@ -26,11 +26,11 @@ msgstr "foi expulsado"
#: ejabberd_c2s.erl:2114
msgid "Your active privacy list has denied the routing of this stanza."
-msgstr ""
+msgstr "A súa lista de privacidade activa negou o encaminamiento desta estrofa."
#: ejabberd_c2s.erl:2429
msgid "Too many unacked stanzas"
-msgstr ""
+msgstr "Demasiadas mensaxes sen recoñecer recibilos"
#: ejabberd_captcha.erl:122 ejabberd_captcha.erl:245 ejabberd_captcha.erl:284
msgid "Enter the text you see"
@@ -43,11 +43,11 @@ msgstr ""
#: ejabberd_captcha.erl:192
msgid "If you don't see the CAPTCHA image here, visit the web page."
-msgstr ""
+msgstr "Si non ves a imaxe CAPTCHA aquí, visita a páxina web."
#: ejabberd_captcha.erl:227
msgid "CAPTCHA web page"
-msgstr ""
+msgstr "CAPTCHA páxina Web"
#: ejabberd_captcha.erl:381
msgid "The CAPTCHA is valid."
@@ -59,9 +59,8 @@ msgid "User"
msgstr "Usuario"
#: ejabberd_oauth.erl:256
-#, fuzzy
msgid "Server"
-msgstr "Servidor ~b"
+msgstr "Servidor"
#: ejabberd_oauth.erl:259 ejabberd_web_admin.erl:1408 mod_configure.erl:1398
#: mod_configure.erl:1485 mod_configure.erl:1889 mod_configure.erl:2123
@@ -71,7 +70,7 @@ msgstr "Contrasinal"
#: ejabberd_oauth.erl:267
msgid "Accept"
-msgstr ""
+msgstr "Aceptar"
#: ejabberd_web_admin.erl:202 ejabberd_web_admin.erl:214
#: ejabberd_web_admin.erl:234 ejabberd_web_admin.erl:246
@@ -238,7 +237,6 @@ msgid "Outgoing s2s Connections:"
msgstr "Conexións S2S saíntes:"
#: ejabberd_web_admin.erl:1559
-#, fuzzy
msgid "Incoming s2s Connections:"
msgstr "Conexións S2S saíntes:"
@@ -253,9 +251,8 @@ msgid "Change Password"
msgstr "Cambiar contrasinal"
#: ejabberd_web_admin.erl:1673
-#, fuzzy
msgid "User ~s"
-msgstr "Usuario "
+msgstr "Usuario ~s"
#: ejabberd_web_admin.erl:1684
msgid "Connected Resources:"
@@ -287,9 +284,8 @@ msgid "Stopped Nodes"
msgstr "Nodos detidos"
#: ejabberd_web_admin.erl:1833 ejabberd_web_admin.erl:1858
-#, fuzzy
msgid "Node ~p"
-msgstr "Nodo "
+msgstr "Nodo ~p"
#: ejabberd_web_admin.erl:1842 mod_configure.erl:150 mod_configure.erl:611
msgid "Database"
@@ -297,7 +293,7 @@ msgstr "Base de datos"
#: ejabberd_web_admin.erl:1843 mod_configure.erl:159 mod_configure.erl:648
msgid "Backup"
-msgstr "Gardar copia de seguridade"
+msgstr "Copia de seguridade"
#: ejabberd_web_admin.erl:1845
msgid "Listened Ports"
@@ -326,9 +322,8 @@ msgid "RPC Call Error"
msgstr "Erro na chamada RPC"
#: ejabberd_web_admin.erl:1917
-#, fuzzy
msgid "Database Tables at ~p"
-msgstr "Táboas da base de datos en "
+msgstr "Táboas da base de datos en ~p"
#: ejabberd_web_admin.erl:1927 mod_vcard.erl:490 mod_vcard.erl:616
msgid "Name"
@@ -336,7 +331,7 @@ msgstr "Nome"
#: ejabberd_web_admin.erl:1928
msgid "Storage Type"
-msgstr "Tipo de almacenamiento"
+msgstr "Tipo de almacenamento"
#: ejabberd_web_admin.erl:1929
msgid "Elements"
@@ -351,9 +346,8 @@ msgid "Error"
msgstr "Erro"
#: ejabberd_web_admin.erl:1955
-#, fuzzy
msgid "Backup of ~p"
-msgstr "Copia de seguridade de "
+msgstr "Copia de seguridade de ~p"
#: ejabberd_web_admin.erl:1959
msgid ""
@@ -387,7 +381,7 @@ msgid ""
"Restore binary backup after next ejabberd restart (requires less memory):"
msgstr ""
"Restaurar copia de seguridade binaria no seguinte reinicio de ejabberd "
-"(require menos memoria que se instantánea):"
+"(require menos memoria):"
#: ejabberd_web_admin.erl:1999
msgid "Store plain text backup:"
@@ -399,7 +393,7 @@ msgstr "Restaurar copias de seguridade de texto plano inmediatamente:"
#: ejabberd_web_admin.erl:2019
msgid "Import users data from a PIEFXIS file (XEP-0227):"
-msgstr "Importar usuarios desde un fichero PIEFXIS"
+msgstr "Importar usuarios en un fichero PIEFXIS (XEP-0227):"
#: ejabberd_web_admin.erl:2032
msgid "Export data of all users in the server to PIEFXIS files (XEP-0227):"
@@ -409,17 +403,15 @@ msgstr ""
#: ejabberd_web_admin.erl:2044
msgid "Export data of users in a host to PIEFXIS files (XEP-0227):"
-msgstr ""
-"Exportar datos de todos os usuarios do servidor a ficheros PIEFXIS "
-"(XEP-0227):"
+msgstr "Exportar datos dos usuarios dun dominio a ficheiros PIEFXIS (XEP-0227):"
#: ejabberd_web_admin.erl:2060
msgid "Export all tables as SQL queries to a file:"
-msgstr ""
+msgstr "Exportar todas as táboas a un ficheiro SQL:"
#: ejabberd_web_admin.erl:2076
msgid "Import user data from jabberd14 spool file:"
-msgstr "Importar usuario de fichero spool de jabberd14:"
+msgstr "Importar usuario de ficheiro spool de jabberd14:"
#: ejabberd_web_admin.erl:2087
msgid "Import users data from jabberd14 spool directory:"
@@ -430,9 +422,8 @@ msgid "Listened Ports at "
msgstr "Portos de escoita en "
#: ejabberd_web_admin.erl:2144
-#, fuzzy
msgid "Modules at ~p"
-msgstr "Módulos en "
+msgstr "Módulos en ~p"
#: ejabberd_web_admin.erl:2175
msgid "Statistics of ~p"
@@ -463,9 +454,8 @@ msgid "Transactions Logged:"
msgstr "Transaccións rexistradas:"
#: ejabberd_web_admin.erl:2243
-#, fuzzy
msgid "Update ~p"
-msgstr "Actualizar"
+msgstr "Actualizar ~p"
#: ejabberd_web_admin.erl:2254
msgid "Update plan"
@@ -525,7 +515,7 @@ msgstr "Pong"
#: mod_announce.erl:523
msgid "Really delete message of the day?"
-msgstr "Está seguro de quere borrar a mensaxe do dia?"
+msgstr "¿Está seguro que quere borrar a mensaxe do dia?"
#: mod_announce.erl:536 mod_configure.erl:1238 mod_configure.erl:1298
msgid "Subject"
@@ -553,7 +543,7 @@ msgstr "Enviar anuncio a todos os usuarios en todos os dominios"
#: mod_announce.erl:668
msgid "Send announcement to all online users"
-msgstr "Enviar anuncio a todos los usuarios conectados"
+msgstr "Enviar anuncio a todos os usuarios conectados"
#: mod_announce.erl:670 mod_configure.erl:1231 mod_configure.erl:1291
msgid "Send announcement to all online users on all hosts"
@@ -595,7 +585,7 @@ msgstr "Iniciar módulos"
#: mod_configure.erl:156 mod_configure.erl:638
msgid "Stop Modules"
-msgstr "Detener módulos"
+msgstr "Deter módulos"
#: mod_configure.erl:162 mod_configure.erl:650
msgid "Restore"
@@ -844,18 +834,20 @@ msgid ""
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr ""
+"Demasiados (~p) fallou autenticaciones desde esta dirección IP (~s). A dirección "
+"será desbloqueada as ~s UTC"
#: mod_http_upload.erl:586
msgid "Please specify file size."
-msgstr ""
+msgstr "Por favor, especifica o tamaño do arquivo"
#: mod_http_upload.erl:590
msgid "Please specify file name."
-msgstr ""
+msgstr "Por favor, indique o nome do arquivo."
#: mod_ip_blacklist.erl:121
msgid "This IP address is blacklisted in ~s"
-msgstr ""
+msgstr "Esta dirección IP está na lista negra en ~s"
#: mod_irc.erl:220 mod_muc.erl:467
msgid "Access denied by service policy"
@@ -884,8 +876,8 @@ msgid ""
"Enter username, encodings, ports and passwords you wish to use for "
"connecting to IRC servers"
msgstr ""
-"Introduza o nome de usuario, codificaciones de carácter, portos e "
-"contrasinal que pretende utilizar a conectar a servidores de IRC"
+"Introduce o nome de usuario, codificaciones de carácteres, portos e "
+"contrasinai que queiras usar ao conectar nos servidores de IRC"
#: mod_irc.erl:667
msgid "IRC Username"
@@ -970,9 +962,8 @@ msgid "Server ~b"
msgstr "Servidor ~b"
#: mod_mam.erl:541
-#, fuzzy
msgid "Only members may query archives of this room"
-msgstr "Só os moderadores están autorizados a cambiar o tema nesta sala"
+msgstr "Só membros poden consultar o arquivo de mensaxes da sala"
#: mod_muc.erl:585
msgid "Only service administrators are allowed to send service messages"
@@ -994,10 +985,9 @@ msgstr "Salas de charla"
#: mod_muc.erl:781
msgid "Empty Rooms"
-msgstr ""
+msgstr "Salas baleiras"
#: mod_muc.erl:933
-#, fuzzy
msgid "You need a client that supports x:data to register the nickname"
msgstr ""
"Necesitas un cliente con soporte de x:data para poder rexistrar o alcume"
@@ -1030,34 +1020,31 @@ msgstr "Módulo de MUC para ejabberd"
#: mod_muc_admin.erl:231 mod_muc_admin.erl:234 mod_muc_admin.erl:246
#: mod_muc_admin.erl:320
msgid "Multi-User Chat"
-msgstr ""
+msgstr "Salas de Charla"
#: mod_muc_admin.erl:249
-#, fuzzy
msgid "Total rooms"
-msgstr "Salas de charla"
+msgstr "Salas totais"
#: mod_muc_admin.erl:250
-#, fuzzy
msgid "Permanent rooms"
-msgstr "sae da sala"
+msgstr "Salas permanentes"
#: mod_muc_admin.erl:251
-#, fuzzy
msgid "Registered nicknames"
-msgstr "Usuarios rexistrados"
+msgstr "Alcumes rexistrados"
#: mod_muc_admin.erl:254
msgid "List of rooms"
-msgstr ""
+msgstr "Lista de salas"
#: mod_muc_log.erl:398 mod_muc_log.erl:407
msgid "Chatroom configuration modified"
-msgstr "Configuración de la sala modificada"
+msgstr "Configuración da sala modificada"
#: mod_muc_log.erl:410
msgid "joins the room"
-msgstr "entra en la sala"
+msgstr "entra na sala"
#: mod_muc_log.erl:413 mod_muc_log.erl:416
msgid "leaves the room"
@@ -1077,7 +1064,7 @@ msgstr "foi expulsado, porque a sala cambiouse a só-membros"
#: mod_muc_log.erl:445
msgid "has been kicked because of a system shutdown"
-msgstr "foi expulsado por mor dun sistema de peche"
+msgstr "foi expulsado porque o sistema vaise a deter"
#: mod_muc_log.erl:450
msgid "is now known as"
@@ -1088,24 +1075,20 @@ msgid " has set the subject to: "
msgstr " puxo o asunto: "
#: mod_muc_log.erl:493
-#, fuzzy
msgid "Chatroom is created"
-msgstr "Salas de charla"
+msgstr "Creouse a sala"
#: mod_muc_log.erl:495
-#, fuzzy
msgid "Chatroom is destroyed"
-msgstr "Salas de charla"
+msgstr "Destruíuse a sala"
#: mod_muc_log.erl:497
-#, fuzzy
msgid "Chatroom is started"
-msgstr "Salas de charla"
+msgstr "Iniciouse a sala"
#: mod_muc_log.erl:499
-#, fuzzy
msgid "Chatroom is stopped"
-msgstr "Salas de charla"
+msgstr "Detívose a sala"
#: mod_muc_log.erl:503
msgid "Monday"
@@ -1200,6 +1183,8 @@ msgid ""
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
+"Non está permitido enviar mensaxes de erro á sala. Este participante (~s) "
+"enviou unha mensaxe de erro (~s) e foi expulsado da sala"
#: mod_muc_room.erl:241
msgid "It is not allowed to send private messages to the conference"
@@ -1207,20 +1192,19 @@ msgstr "Impedir o envio de mensaxes privadas á sala"
#: mod_muc_room.erl:316
msgid "Please, wait for a while before sending new voice request"
-msgstr ""
+msgstr "Por favor, espera un pouco antes de enviar outra petición de voz"
#: mod_muc_room.erl:329
msgid "Voice requests are disabled in this conference"
-msgstr ""
+msgstr "As peticións de voz están desactivadas nesta sala"
#: mod_muc_room.erl:347
msgid "Failed to extract JID from your voice request approval"
-msgstr ""
+msgstr "Fallo ao extraer o Jabber ID da túa aprobación de petición de voz"
#: mod_muc_room.erl:377
-#, fuzzy
msgid "Only moderators can approve voice requests"
-msgstr "Permitir aos usuarios enviar invitacións"
+msgstr "Só os moderadores poden aprobar peticións de voz"
#: mod_muc_room.erl:389
msgid "Improper message type"
@@ -1269,11 +1253,11 @@ msgstr "Os visitantes non poden enviar mensaxes a todos os ocupantes"
#: mod_muc_room.erl:1080
msgid "Visitors are not allowed to change their nicknames in this room"
msgstr ""
-"Os visitantes non están autorizados a cambiar os seus That alcumes nesta sala"
+"Os visitantes non teñen permitido cambiar os seus alcumes nesta sala"
#: mod_muc_room.erl:1093 mod_muc_room.erl:1835
msgid "That nickname is already in use by another occupant"
-msgstr "Ese alcume que xa está en uso por outro ocupante"
+msgstr "Ese alcume xa está a ser usado por outro ocupante"
#: mod_muc_room.erl:1822
msgid "You have been banned from this room"
@@ -1289,12 +1273,11 @@ msgstr "Necesítase contrasinal para entrar nesta sala"
#: mod_muc_room.erl:1898 mod_register.erl:295
msgid "Too many CAPTCHA requests"
-msgstr ""
+msgstr "Demasiadas peticións de CAPTCHA"
#: mod_muc_room.erl:1908 mod_register.erl:301
-#, fuzzy
msgid "Unable to generate a CAPTCHA"
-msgstr "Non se pode xerar un CAPTCHA"
+msgstr "No se pudo generar un CAPTCHA"
#: mod_muc_room.erl:1919
msgid "Incorrect password"
@@ -1378,20 +1361,19 @@ msgstr "calquera"
#: mod_muc_room.erl:3471
msgid "Roles for which Presence is Broadcasted"
-msgstr ""
+msgstr "Roles para os que si se difunde a súa Presenza"
#: mod_muc_room.erl:3486
-#, fuzzy
msgid "Moderator"
-msgstr "só moderadores"
+msgstr "Moderator"
#: mod_muc_room.erl:3496
msgid "Participant"
-msgstr ""
+msgstr "Participante"
#: mod_muc_room.erl:3506
msgid "Visitor"
-msgstr ""
+msgstr "Visitante"
#: mod_muc_room.erl:3513
msgid "Make room members-only"
@@ -1414,13 +1396,12 @@ msgid "Allow users to send private messages"
msgstr "Permitir aos usuarios enviar mensaxes privadas"
#: mod_muc_room.erl:3533
-#, fuzzy
msgid "Allow visitors to send private messages to"
-msgstr "Permitir aos usuarios enviar mensaxes privadas"
+msgstr "Permitir aos visitantes enviar mensaxes privadas a"
#: mod_muc_room.erl:3551
msgid "nobody"
-msgstr ""
+msgstr "ninguén"
#: mod_muc_room.erl:3576
msgid "Allow users to query other users"
@@ -1440,13 +1421,12 @@ msgid "Allow visitors to change nickname"
msgstr "Permitir aos visitantes cambiarse o alcume"
#: mod_muc_room.erl:3589
-#, fuzzy
msgid "Allow visitors to send voice requests"
-msgstr "Permitir aos usuarios enviar invitacións"
+msgstr "Permitir aos visitantes enviar peticións de voz"
#: mod_muc_room.erl:3592
msgid "Minimum interval between voice requests (in seconds)"
-msgstr ""
+msgstr "Intervalo mínimo entre peticións de voz (en segundos)"
#: mod_muc_room.erl:3599
msgid "Make room CAPTCHA protected"
@@ -1454,11 +1434,11 @@ msgstr "Protexer a sala con CAPTCHA"
#: mod_muc_room.erl:3606
msgid "Enable message archiving"
-msgstr ""
+msgstr "Activar o almacenamento de mensaxes"
#: mod_muc_room.erl:3612
msgid "Exclude Jabber IDs from CAPTCHA challenge"
-msgstr ""
+msgstr "Excluír Jabber IDs das probas de CAPTCHA"
#: mod_muc_room.erl:3621
msgid "Enable logging"
@@ -1478,20 +1458,19 @@ msgstr "privado"
#: mod_muc_room.erl:4326
msgid "Voice request"
-msgstr ""
+msgstr "Petición de voz"
#: mod_muc_room.erl:4331
msgid "Either approve or decline the voice request."
-msgstr ""
+msgstr "Aproba ou rexeita a petición de voz."
#: mod_muc_room.erl:4351
-#, fuzzy
msgid "User JID"
-msgstr "Usuario "
+msgstr "Jabber ID do usuario"
#: mod_muc_room.erl:4355
msgid "Grant voice to this person?"
-msgstr ""
+msgstr "¿Conceder voz a esta persoa?"
#: mod_muc_room.erl:4498
msgid "~s invites you to the room ~s"
@@ -1503,11 +1482,11 @@ msgstr "a contrasinal é"
#: mod_multicast.erl:291
msgid "Multicast"
-msgstr ""
+msgstr "Multicast"
#: mod_multicast.erl:306
msgid "ejabberd Multicast service"
-msgstr ""
+msgstr "Servizo Multicast de ejabberd"
#: mod_offline.erl:647
msgid ""
@@ -1546,7 +1525,7 @@ msgstr "Borrar Todas as Mensaxes Sen conexión"
#: mod_proxy65_service.erl:248
msgid "ejabberd SOCKS5 Bytestreams module"
-msgstr "ejabberd SOCKS5 Bytestreams module"
+msgstr "Módulo SOCKS5 Bytestreams para ejabberd"
#: mod_pubsub.erl:1102
msgid "Publish-Subscribe"
@@ -1566,7 +1545,7 @@ msgstr "Decidir se aprobar a subscripción desta entidade."
#: mod_pubsub.erl:1559
msgid "Node ID"
-msgstr "Nodo IDE"
+msgstr "Nodo ID"
#: mod_pubsub.erl:1571
msgid "Subscriber Address"
@@ -1602,7 +1581,7 @@ msgstr "Persistir elementos ao almacenar"
#: mod_pubsub.erl:3757
msgid "A friendly name for the node"
-msgstr "Un nome para o nodo"
+msgstr "Un nome sinxelo para o nodo"
#: mod_pubsub.erl:3759
msgid "Max # of items to persist"
@@ -1626,12 +1605,11 @@ msgstr "Especificar o modelo do publicante"
#: mod_pubsub.erl:3769
msgid "Purge all items when the relevant publisher goes offline"
-msgstr ""
+msgstr "Purgar todos os elementos cando o editor correspondente desconéctase"
#: mod_pubsub.erl:3771
-#, fuzzy
msgid "Specify the event message type"
-msgstr "Especifica o modelo de acceso"
+msgstr "Especifica o tipo da mensaxe de evento"
#: mod_pubsub.erl:3773
msgid "Max payload size in bytes"
@@ -1650,15 +1628,13 @@ msgid "The collections with which a node is affiliated"
msgstr "As coleccións coas que un nodo está afiliado"
#: mod_register.erl:209
-#, fuzzy
msgid "The CAPTCHA verification has failed"
-msgstr "O CAPTCHA é válido."
+msgstr "A verificación de CAPTCHA fallou"
#: mod_register.erl:253
-#, fuzzy
msgid "You need a client that supports x:data and CAPTCHA to register"
msgstr ""
-"Necesitas un cliente con soporte de x:data para poder rexistrar o alcume"
+"Necesitas un cliente con soporte de x:data e CAPTCHA para rexistrarche"
#: mod_register.erl:259 mod_register.erl:320
msgid "Choose a username and password to register with this server"
@@ -1666,9 +1642,8 @@ msgstr ""
"Escolle un nome de usuario e contrasinal para rexistrarche neste servidor"
#: mod_register.erl:373 mod_register.erl:421
-#, fuzzy
msgid "The password is too weak"
-msgstr "a contrasinal é"
+msgstr "O contrasinal é demasiado débil"
#: mod_register.erl:426
msgid "Users are not allowed to register accounts so quickly"
@@ -1676,39 +1651,39 @@ msgstr "Os usuarios non están autorizados a rexistrar contas con tanta rapidez"
#: mod_register_web.erl:105
msgid "Your Jabber account was successfully created."
-msgstr ""
+msgstr "A súa conta Jabber creouse correctamente."
#: mod_register_web.erl:110
msgid "There was an error creating the account: "
-msgstr ""
+msgstr "Produciuse un erro ao crear a conta: "
#: mod_register_web.erl:119
msgid "Your Jabber account was successfully deleted."
-msgstr ""
+msgstr "A súa conta Jabber eliminouse correctamente."
#: mod_register_web.erl:124
msgid "There was an error deleting the account: "
-msgstr ""
+msgstr "Produciuse un erro ao eliminar a conta: "
#: mod_register_web.erl:135
msgid "The password of your Jabber account was successfully changed."
-msgstr ""
+msgstr "O contrasinal da súa conta Jabber cambiouse correctamente."
#: mod_register_web.erl:140
msgid "There was an error changing the password: "
-msgstr ""
+msgstr "Produciuse un erro ao cambiar o contrasinal: "
#: mod_register_web.erl:175 mod_register_web.erl:183
msgid "Jabber Account Registration"
-msgstr ""
+msgstr "Rexistro de conta Jabber"
#: mod_register_web.erl:186 mod_register_web.erl:204 mod_register_web.erl:212
msgid "Register a Jabber account"
-msgstr ""
+msgstr "Rexistrar unha conta Jabber"
#: mod_register_web.erl:191 mod_register_web.erl:453 mod_register_web.erl:461
msgid "Unregister a Jabber account"
-msgstr ""
+msgstr "Eliminar o rexistro dunha conta Jabber"
#: mod_register_web.erl:214
msgid ""
@@ -1716,40 +1691,45 @@ msgid ""
"(Jabber IDentifier) will be of the form: username@server. Please read "
"carefully the instructions to fill correctly the fields."
msgstr ""
+"Esta páxina permite crear unha conta Jabber neste servidor Jabber. o seu JID "
+"(Jabber IDentificador) será da forma: nomeusuario@servidor. Por favor le "
+"coidadosamente as instrucións para encher correctamente os campos."
#: mod_register_web.erl:224 mod_register_web.erl:360 mod_register_web.erl:469
-#, fuzzy
msgid "Username:"
-msgstr "Nome de usuario en IRC"
+msgstr "Nome de usuario:"
#: mod_register_web.erl:230
msgid "This is case insensitive: macbeth is the same that MacBeth and Macbeth."
-msgstr ""
+msgstr "Esta é insensible: Macbeth é o mesmo que MacBeth e Macbeth."
#: mod_register_web.erl:233
msgid "Characters not allowed:"
-msgstr ""
+msgstr "Caracteres non permitidos:"
#: mod_register_web.erl:236 mod_register_web.erl:364 mod_register_web.erl:473
-#, fuzzy
msgid "Server:"
-msgstr "Servidor ~b"
+msgstr "Servidor:"
#: mod_register_web.erl:245
msgid ""
"Don't tell your password to anybody, not even the administrators of the "
-"Jabber server."
+"Jabber Server."
msgstr ""
+"Non lle diga o seu contrasinal a ninguén, nin sequera os administradores do "
+"Servidor Jabber."
#: mod_register_web.erl:249
msgid "You can later change your password using a Jabber client."
-msgstr ""
+msgstr "Máis tarde, pode cambiar o seu contrasinal utilizando un cliente Jabber."
#: mod_register_web.erl:252
msgid ""
"Some Jabber clients can store your password in the computer, but you should "
"do this only in your personal computer for safety reasons."
msgstr ""
+"Algúns clientes Jabber pode almacenar o contrasinal no computador, pero debe "
+"facer isto só no seu computador persoal por razóns de seguridade."
#: mod_register_web.erl:256
msgid ""
@@ -1757,34 +1737,33 @@ msgid ""
"Jabber there isn't an automated way to recover your password if you forget "
"it."
msgstr ""
+"Memorice o seu contrasinal ou escribilo nun papel colocado nun lugar seguro. En "
+"Jabber non hai unha forma automatizada para recuperar o seu contrasinal si "
+"a esquece"
#: mod_register_web.erl:262 mod_register_web.erl:374
-#, fuzzy
msgid "Password Verification:"
msgstr "Verificación da contrasinal"
#: mod_register_web.erl:269
-#, fuzzy
msgid "Register"
-msgstr "Lista de contactos"
+msgstr "Rexistrar"
#: mod_register_web.erl:366
-#, fuzzy
msgid "Old Password:"
-msgstr "Contrasinal:"
+msgstr "Contrasinal anterior:"
#: mod_register_web.erl:370
-#, fuzzy
msgid "New Password:"
-msgstr "Contrasinal:"
+msgstr "Novo contrasinal:"
#: mod_register_web.erl:463
msgid "This page allows to unregister a Jabber account in this Jabber server."
-msgstr ""
+msgstr "Esta páxina permite anular o rexistro dunha conta Jabber neste servidor Jabber."
#: mod_register_web.erl:480
msgid "Unregister"
-msgstr ""
+msgstr "Eliminar rexistro"
#: mod_roster.erl:1436
msgid "Subscription"
@@ -1872,8 +1851,8 @@ msgid ""
"Fill in the form to search for any matching Jabber User (Add * to the end of "
"field to match substring)"
msgstr ""
-"Enche o formulario para buscar usuarios Jabber. Engade * ao final dun campo "
-"para buscar subcadenas."
+"Enche o formulario para buscar usuarios Jabber (Engade * ao final dun campo "
+"para buscar subcadenas)"
#: mod_vcard.erl:490 mod_vcard.erl:615
msgid "Full Name"
@@ -1901,7 +1880,7 @@ msgstr "Necesitas un cliente con soporte de x:data para poder buscar"
#: mod_vcard.erl:519 mod_vcard_ldap.erl:531
msgid "vCard User Search"
-msgstr "Procura de usuario en vCard"
+msgstr "vCard busqueda de usuario"
#: mod_vcard.erl:580 mod_vcard_ldap.erl:586
msgid "ejabberd vCard module"
@@ -1942,7 +1921,6 @@ msgstr "Rechea campos para buscar usuarios Jabber que concuerden"
#~ "Este participante é expulsado da sala, porque el enviou un erro de "
#~ "presenza"
-#, fuzzy
#~ msgid "CAPTCHA test failed"
#~ msgstr "O CAPTCHA é válido."
diff --git a/priv/msgs/he.msg b/priv/msgs/he.msg
index 2fa51b74..65092de4 100644
--- a/priv/msgs/he.msg
+++ b/priv/msgs/he.msg
@@ -244,6 +244,7 @@
{"Online Users","משתמשים מקוונים"}.
{"Online","מקוון"}.
{"Only deliver notifications to available users","מסור התראות למשתמשים זמינים בלבד"}.
+{"Only members may query archives of this room","רק חברים רשאים לתשאל ארכיונים של חדר זה"}.
{"Only moderators and participants are allowed to change the subject in this room","רק אחראים ומשתתפים רשאים לשנות את הנושא בחדר זה"}.
{"Only moderators are allowed to change the subject in this room","רק אחראים רשאים לשנות את הנושא בחדר זה"}.
{"Only moderators can approve voice requests","רק אחראים יכולים לאשר בקשות ביטוי"}.
diff --git a/priv/msgs/ja.msg b/priv/msgs/ja.msg
index d6a9a0a4..404a1620 100644
--- a/priv/msgs/ja.msg
+++ b/priv/msgs/ja.msg
@@ -1,4 +1,5 @@
%% -*- coding: latin-1 -*-
+{"Accept","許可"}.
{"Access Configuration","アクセス設定"}.
{"Access Control List Configuration","アクセスコントロールリスト設定"}.
{"Access control lists","アクセスコントロールリスト"}.
@@ -72,7 +73,7 @@
{"Deliver payloads with event notifications","イベント通知と同時にペイロードを配送する"}.
{"Description:","説明:"}.
{"Disc only copy","ディスクだけのコピー"}.
-{"Displayed Groups:","表示グループ"}.
+{"Displayed Groups:","表示グループ:"}.
{"Don't tell your password to anybody, not even the administrators of the Jabber server.","パスワードは誰にも教えないようにしてください。Jabber サーバーの管理者があなたにパスワードを尋ねることはありません。"}.
{"Dump Backup to Text File at ","テキストファイルにバックアップ: "}.
{"Dump to Text File","テキストファイルに出力"}.
@@ -87,6 +88,7 @@
{"ejabberd Web Admin","ejabberd ウェブ管理"}.
{"Elements","要素"}.
{"Email","メールアドレス"}.
+{"Empty Rooms","空のルーム"}.
{"Enable logging","ロギングを有効"}.
{"Enable message archiving","メッセージアーカイブを有効化"}.
{"Encoding for server ~b","サーバーのエンコーディング ~b"}.
@@ -103,7 +105,7 @@
{"Erlang Jabber Server","Erlang Jabber Server"}.
{"Error","エラー"}.
{"Example: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}].","例: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}]."}.
-{"Exclude Jabber IDs from CAPTCHA challenge","CAPTCHA 試験を免除する Jabber ID"}.
+{"Exclude Jabber IDs from CAPTCHA challenge","CAPTCHA 入力を免除する Jabber ID"}.
{"Export all tables as SQL queries to a file:","すべてのテーブルをSQL形式でファイルにエクスポート: "}.
{"Export data of all users in the server to PIEFXIS files (XEP-0227):","サーバーにあるすべてのユーザーデータを PIEFXIS ファイルにエクスポート (XEP-0227):"}.
{"Export data of users in a host to PIEFXIS files (XEP-0227):","ホストのユーザーデータを PIEFXIS ファイルにエクスポート (XEP-0227):"}.
@@ -113,7 +115,7 @@
{"Fill in fields to search for any matching Jabber User","項目を入力してユーザーを検索してください"}.
{"Fill in the form to search for any matching Jabber User (Add * to the end of field to match substring)","項目を入力してユーザーを検索を行えます (* を使用すると部分文字列にマッチします)"}.
{"Friday","金曜日"}.
-{"From ~s","差出人 ~s"}.
+{"From ~s","From ~s"}.
{"From","差出人"}.
{"Full Name","氏名"}.
{"Get Number of Online Users","オンラインユーザー数を取得"}.
@@ -142,6 +144,7 @@
{"Import Users from Dir at ","ディレクトリからユーザーをインポート: "}.
{"Import Users From jabberd14 Spool Files","jabberd14 Spool ファイルからユーザーをインポート"}.
{"Improper message type","誤ったメッセージタイプです"}.
+{"Incoming s2s Connections:","内向き s2s コネクション:"}.
{"Incorrect password","パスワードが違います"}.
{"Invalid affiliation: ~s","無効な分掌です: ~s"}.
{"Invalid role: ~s","無効な役です: ~s"}.
@@ -154,6 +157,7 @@
{"IRC username","IRC ユーザー名"}.
{"IRC Username","IRC ユーザー名"}.
{"is now known as","は名前を変更しました: "}.
+{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","このルームにエラーメッセージを送ることは許可されていません。参加者(~s)はエラーメッセージを(~s)を送信してルームからキックされました。"}.
{"It is not allowed to send private messages of type \"groupchat\"","種別が\"groupchat\" であるプライベートメッセージを送信することはできません"}.
{"It is not allowed to send private messages to the conference","この会議にプライベートメッセージを送信することはできません"}.
{"It is not allowed to send private messages","プライベートメッセージを送信することはできません"}.
@@ -196,6 +200,7 @@
{"Message body","本文"}.
{"Middle Name","ミドルネーム"}.
{"Minimum interval between voice requests (in seconds)","発言権の要求の最小時間間隔 (秒)"}.
+{"Moderator","モデレーター"}.
{"Moderator privileges required","モデレーター権限が必要です"}.
{"moderators only","モデレーターにのみ"}.
{"Modified modules","更新されたモジュール"}.
@@ -221,7 +226,7 @@
{"Nodes","ノード"}.
{"No limit","制限なし"}.
{"None","なし"}.
-{"No resource provided","リソースが提供されませんでした"}.
+{"No resource provided","リソースが指定されていません"}.
{"Not Found","見つかりません"}.
{"Notify subscribers when items are removed from the node","アイテムがノードから消された時に購読者へ通知する"}.
{"Notify subscribers when the node configuration changes","ノード設定に変更があった時に購読者へ通知する"}.
@@ -239,6 +244,7 @@
{"Online Users:","オンラインユーザー:"}.
{"Online Users","オンラインユーザー"}.
{"Only deliver notifications to available users","有効なユーザーにのみ告知を送信する"}.
+{"Only members may query archives of this room","メンバーのみがこのルームのアーカイブを取得できます"}.
{"Only moderators and participants are allowed to change the subject in this room","モデレーターと参加者のみがチャットルームの件名を変更できます"}.
{"Only moderators are allowed to change the subject in this room","モデレーターのみがチャットルームの件名を変更できます"}.
{"Only moderators can approve voice requests","モデレーターだけが発言権の要求を承認できます"}.
@@ -252,7 +258,8 @@
{"Outgoing s2s Connections","外向き s2s コネクション"}.
{"Owner privileges required","主宰者の権限が必要です"}.
{"Packet","パケット"}.
-{"Password:","パスワード"}.
+{"Participant","参加者"}.
+{"Password:","パスワード:"}.
{"Password","パスワード"}.
{"Password ~b","パスワード ~b"}.
{"Password Verification:","パスワード (確認):"}.
@@ -265,6 +272,8 @@
{"Persist items to storage","アイテムをストレージに保存する"}.
{"Ping","Ping"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","これらのオプションは組み込みの Mnesia データーベースのバックアップのみを行うことに注意してください。もし ODBC モジュールを使用している場合は、SQL データーベースのバックアップを別に行う必要があります。"}.
+{"Please specify file name.","ファイル名を指定してください。"}.
+{"Please specify file size.","ファイルサイズを指定してください。"}.
{"Please, wait for a while before sending new voice request","新しい発言権の要求を送るまで少し間をおいてください"}.
{"Pong","Pong"}.
{"Port","ポート"}.
@@ -300,6 +309,7 @@
{"Restore binary backup after next ejabberd restart (requires less memory):","ejabberd の再起動時にバイナリバックアップからリストア (メモリ少):"}.
{"Restore binary backup immediately:","直ちにバイナリバックアップからリストア:"}.
{"Restore plain text backup immediately:","直ちにプレーンテキストバックアップからリストア:"}.
+{"Roles for which Presence is Broadcasted","プレゼンスをブロードキャストするロール"}.
{"Room Configuration","チャットルームの設定"}.
{"Room creation is denied by service policy","サービスポリシーによってチャットルームの作成が禁止されています"}.
{"Room description","チャットルームの説明"}.
@@ -322,6 +332,7 @@
{"Send announcement to all users","すべてのユーザーにアナウンスを送信"}.
{"September","9月"}.
{"Server:","サーバー:"}.
+{"Server","サーバー"}.
{"Server ~b","サーバー ~b"}.
{"Set message of the day and send to online users","お知らせメッセージを設定し、オンラインユーザーに送信"}.
{"Set message of the day on all hosts and send to online users","全ホストのお知らせメッセージを設定し、オンラインユーザーに送信"}.
@@ -367,7 +378,7 @@
{"This IP address is blacklisted in ~s","このIPアドレスはアクセスを禁止されています ~s"}.
{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","大文字と小文字は区別しません: macbeth は MacBeth や Macbeth と同じです。"}.
{"This page allows to create a Jabber account in this Jabber server. Your JID (Jabber IDentifier) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","ここはこの Jabber サーバーにアカウントを作成するページです。あなたの JID (JabberID) は username@server のような形式になります。注意事項どおり、正しく項目を記入してください。"}.
-{"This page allows to unregister a Jabber account in this Jabber server.","ここはこの Jabber サーバーのアカウントを削除するページです。"}.
+{"This page allows to unregister a Jabber account in this Jabber server.","このページはサーバー上のJabberアカウントを削除するページです。"}.
{"Thursday","木曜日"}.
{"Time delay","遅延時間"}.
{"Time","時間"}.
@@ -376,7 +387,7 @@
{"Too many unacked stanzas","多くのスタンザが応答していません"}.
{"To ~s","宛先 ~s"}.
{"Total rooms","チャットルーム数"}.
-{"To","宛先"}.
+{"To","To"}.
{"Traffic rate limit is exceeded","トラフィックレートの制限を超えました"}.
{"Transactions Aborted:","トランザクションの失敗:"}.
{"Transactions Committed:","トランザクションのコミット:"}.
@@ -394,7 +405,7 @@
{"Update script","スクリプトの更新"}.
{"Update","更新"}.
{"Uptime:","起動時間:"}.
-{"Use of STARTTLS required","STARTTLS の使用が必要です"}.
+{"Use of STARTTLS required","STARTTLS の使用が必須です"}.
{"User","ユーザー"}.
{"User JID","ユーザー JID"}.
{"User Management","ユーザー管理"}.
@@ -408,6 +419,7 @@
{"Virtual Hosts","バーチャルホスト"}.
{"Visitors are not allowed to change their nicknames in this room","傍聴者はこのチャットルームでニックネームを変更することはできません"}.
{"Visitors are not allowed to send messages to all occupants","傍聴者はすべての在室者にメッセージを送信することはできません"}.
+{"Visitor","傍聴者"}.
{"Voice requests are disabled in this conference","この会議では、発言権の要求はできません"}.
{"Voice request","発言権を要求"}.
{"Wednesday","水曜日"}.
diff --git a/priv/msgs/pl.msg b/priv/msgs/pl.msg
index 4bab696c..03fbd3d0 100644
--- a/priv/msgs/pl.msg
+++ b/priv/msgs/pl.msg
@@ -1,4 +1,5 @@
%% -*- coding: latin-1 -*-
+{"Accept","Zaakceptuj"}.
{"Access Configuration","Konfiguracja dostępu"}.
{"Access Control List Configuration","Konfiguracja listy dostępowej"}.
{"Access Control Lists","Lista dostępowa"}.
@@ -87,6 +88,7 @@
{"ejabberd Web Admin","ejabberd: Panel Administracyjny"}.
{"Elements","Elementy"}.
{"Email","Email"}.
+{"Empty Rooms","Puste pokoje"}.
{"Enable logging","Włącz logowanie"}.
{"Enable message archiving","Włącz archiwizowanie rozmów"}.
{"Encoding for server ~b","Kodowanie znaków dla serwera ~b"}.
@@ -142,6 +144,7 @@
{"Import Users from Dir at ","Importuj użytkowników z katalogu na "}.
{"Import Users From jabberd14 Spool Files","Importuj użytkowników z plików roboczych serwera jabberd14"}.
{"Improper message type","Nieprawidłowy typ wiadomości"}.
+{"Incoming s2s Connections:","Przychodzące połączenia s2s:"}.
{"Incorrect password","Nieprawidłowe hasło"}.
{"Invalid affiliation: ~s","Nieprawidłowa przynależność: ~s"}.
{"Invalid role: ~s","Nieprawidłowa rola: ~s"}.
@@ -154,6 +157,7 @@
{"IRC username","Nazwa użytkownika IRC"}.
{"IRC Username","Nazwa użytkownika IRC"}.
{"is now known as","jest teraz znany jako"}.
+{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Użytkownik nie może wysyłać wiadomości o błędach do pokoju. Użytkownik (~s) wysłał błąd (~s) i został wyrzucony z pokoju"}.
{"It is not allowed to send private messages of type \"groupchat\"","Nie można wysyłać prywatnych wiadomości typu \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Nie wolno wysyłac prywatnych wiadomości na konferencję"}.
{"It is not allowed to send private messages","Wysyłanie prywatnych wiadomości jest zabronione"}.
@@ -196,6 +200,7 @@
{"Message body","Treść wiadomości"}.
{"Middle Name","Drugie imię"}.
{"Minimum interval between voice requests (in seconds)","Minimalny odstęp między żądaniami głosowymi (w sekundach)"}.
+{"Moderator","Moderatorzy"}.
{"Moderator privileges required","Wymagane uprawnienia moderatora"}.
{"moderators only","tylko moderatorzy"}.
{"Modified modules","Zmodyfikowane moduły"}.
@@ -239,6 +244,7 @@
{"Online Users:","Użytkownicy zalogowani:"}.
{"Online Users","Użytkownicy zalogowani"}.
{"Only deliver notifications to available users","Dostarczaj powiadomienia tylko dostępnym użytkownikom"}.
+{"Only members may query archives of this room","Tylko moderatorzy mogą przeglądać archiwa tego pokoju"}.
{"Only moderators and participants are allowed to change the subject in this room","Tylko moderatorzy i uczestnicy mogą zmienić temat tego pokoju"}.
{"Only moderators are allowed to change the subject in this room","Tylko moderatorzy mogą zmienić temat tego pokoju"}.
{"Only moderators can approve voice requests","Tylko moderatorzy mogą zatwierdzać żądania głosowe"}.
@@ -252,6 +258,7 @@
{"Outgoing s2s Connections","Wychodzące połączenia s2s"}.
{"Owner privileges required","Wymagane uprawnienia właściciela"}.
{"Packet","Pakiet"}.
+{"Participant","Uczestnicy"}.
{"Password ~b","Hasło ~b"}.
{"Password:","Hasło:"}.
{"Password","Hasło"}.
@@ -265,6 +272,8 @@
{"Persist items to storage","Przechowuj na stałe dane PubSub"}.
{"Ping","Ping"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Te opcje kopii zapasowych dotyczą tylko wbudowanej bazy danych typu Mnesia. Jeśli korzystasz z modułu ODBC, musisz wykonać kopie bazy we własnym zakresie."}.
+{"Please specify file name.","Proszę podać nazwę pliku."}.
+{"Please specify file size.","Proszę podać rozmiar pliku."}.
{"Please, wait for a while before sending new voice request","Proszę poczekać chwile, zanim wyślesz nowe żądanie głosowe"}.
{"Pong","Pong"}.
{"Port ~b","Port ~b"}.
@@ -300,6 +309,7 @@
{"Restore binary backup immediately:","Natychmiast odtwórz kopię binarną:"}.
{"Restore plain text backup immediately:","Natychmiast odtwórz kopię z postaci tekstowej:"}.
{"Restore","Przywróć z kopii"}.
+{"Roles for which Presence is Broadcasted","Role dla których wysyłane są statusy"}.
{"Room Configuration","Konfiguracja pokoju"}.
{"Room creation is denied by service policy","Zasady serwera zabraniają tworzyć nowe pokoje"}.
{"Room description","Opis pokoju"}.
@@ -323,6 +333,7 @@
{"September","Wrzesień"}.
{"Server ~b","Serwer ~b"}.
{"Server:","Serwer:"}.
+{"Server","Serwer"}.
{"Set message of the day and send to online users","Wyślij wiadomość dnia do wszystkich zalogowanych użytkowników"}.
{"Set message of the day on all hosts and send to online users","Ustaw wiadomość dnia dla wszystkich hostów i wyślij do zalogowanych uzytkowników"}.
{"Shared Roster Groups","Wspólne grupy kontaktów"}.
@@ -406,6 +417,7 @@
{"Validate","Potwierdź"}.
{"vCard User Search","Wyszukiwanie vCard użytkowników"}.
{"Virtual Hosts","Wirtualne Hosty"}.
+{"Visitor","Odwiedzający"}.
{"Visitors are not allowed to change their nicknames in this room","Uczestnicy tego pokoju nie mogą zmieniać swoich nicków"}.
{"Visitors are not allowed to send messages to all occupants","Odwiedzający nie mogą wysyłać wiadomości do wszystkich obecnych"}.
{"Voice requests are disabled in this conference","Głosowe żądania są wyłączone w tym pokoju"}.
diff --git a/priv/msgs/pt-br.msg b/priv/msgs/pt-br.msg
index a18227f6..3e7b28ee 100644
--- a/priv/msgs/pt-br.msg
+++ b/priv/msgs/pt-br.msg
@@ -1,9 +1,10 @@
%% -*- coding: latin-1 -*-
+{"Accept","Aceito"}.
{"Access Configuration","Configuração de Acesso"}.
{"Access Control List Configuration","Configuração da Lista de Controle de Acesso"}.
{"Access control lists","Listas de Controle de Acesso"}.
{"Access Control Lists","Listas de Controle de Acesso"}.
-{"Access denied by service policy","Aceso denegado por la política do serviço"}.
+{"Access denied by service policy","Acesso negado pela política do serviço"}.
{"Access rules","Regras de acesso"}.
{"Access Rules","Regras de Acesso"}.
{"Action on user","Ação no usuário"}.
@@ -23,11 +24,11 @@
{"Allow visitors to change nickname","Permitir mudança de apelido aos visitantes"}.
{"Allow visitors to send private messages to","Permitir visitantes enviar mensagem privada para"}.
{"Allow visitors to send status text in presence updates","Permitir atualizações de status aos visitantes"}.
-{"Allow visitors to send voice requests","Permitir aos visitantes o envio de convites"}.
+{"Allow visitors to send voice requests","Permitir aos visitantes o envio de requisições de voz"}.
{"All Users","Todos os usuários"}.
{"Announcements","Anúncios"}.
{"anyone","qualquer um"}.
-{"A password is required to enter this room","Se necessita senha para entrar em esta sala"}.
+{"A password is required to enter this room","Se necessita senha para entrar nesta sala"}.
{"April","Abril"}.
{"August","Agosto"}.
{"Backup Management","Gestão de Backup"}.
@@ -43,7 +44,7 @@
{"Chatroom configuration modified","Configuração da sala de bate-papo modificada"}.
{"Chatroom is created","A sala de chat está criada"}.
{"Chatroom is destroyed","A sala de chat está destruída"}.
-{"Chatroom is started","A sala de chat está inciada"}.
+{"Chatroom is started","A sala de chat está iniciada"}.
{"Chatroom is stopped","A sala de chat está parada"}.
{"Chatrooms","Salas de Chat"}.
{"Choose a username and password to register with this server","Escolha um nome de usuário e senha para registrar-se neste servidor"}.
@@ -52,7 +53,7 @@
{"Choose whether to approve this entity's subscription.","Aprovar esta assinatura."}.
{"City","Cidade"}.
{"Commands","Comandos"}.
-{"Conference room does not exist","La sala de conferencias não existe"}.
+{"Conference room does not exist","A sala de conferência não existe"}.
{"Configuration","Configuração"}.
{"Configuration of room ~s","Configuração para ~s"}.
{"Connected Resources:","Recursos conectados:"}.
@@ -60,7 +61,7 @@
{"Country","País"}.
{"CPU Time:","Tempo de CPU"}.
{"Database","Base de dados"}.
-{"Database Tables at ~p","Tabelas do bancod de dados em ~p"}.
+{"Database Tables at ~p","Tabelas da Base de dados em ~p"}.
{"Database Tables Configuration at ","Configuração de Tabelas de Base de dados em "}.
{"December","Dezembro"}.
{"Default users as participants","Usuários padrões como participantes"}.
@@ -71,9 +72,9 @@
{"Deliver event notifications","Entregar as notificações de evento"}.
{"Deliver payloads with event notifications","Enviar payloads junto com as notificações de eventos"}.
{"Description:","Descrição:"}.
-{"Disc only copy","Somente copia em disco"}.
+{"Disc only copy","Somente cópia em disco"}.
{"Displayed Groups:","Grupos Exibidos:"}.
-{"Don't tell your password to anybody, not even the administrators of the Jabber server.","Não revele o seu computador a ninguém, mesmo para o administrador deste servidor Jabber."}.
+{"Don't tell your password to anybody, not even the administrators of the Jabber server.","Não revele a sua senha a ninguém, nem mesmo para o administrador deste servidor Jabber."}.
{"Dump Backup to Text File at ","Exportar backup para texto em "}.
{"Dump to Text File","Exportar para arquivo texto"}.
{"Edit Properties","Editar propriedades"}.
@@ -87,6 +88,7 @@
{"ejabberd Web Admin","ejabberd Web Admin"}.
{"Elements","Elementos"}.
{"Email","Email"}.
+{"Empty Rooms","Salas vazias"}.
{"Enable logging","Permitir criação de logs"}.
{"Enable message archiving","Habilitar arquivamento de mensagens"}.
{"Encoding for server ~b","Codificação para o servidor ~b"}.
@@ -99,7 +101,7 @@
{"Enter path to text file","Introduza caminho para o arquivo texto"}.
{"Enter the text you see","Insira o texto que você vê"}.
{"Enter username and encodings you wish to use for connecting to IRC servers. Press 'Next' to get more fields to fill in. Press 'Complete' to save settings.","Insira o nome de usuário e codificações que você deseja usar para conectar-se aos servidores de IRC. Depois, presione 'Next' ('Próximo') para exibir mais campos que devem ser preenchidos. Ao final, pressione 'Complete' ('Completar') para salvar a configuração."}.
-{"Enter username, encodings, ports and passwords you wish to use for connecting to IRC servers","Insira o nome de usuário, codificações, portas e senhas que vocêdeseja para usar nos servidores IRC"}.
+{"Enter username, encodings, ports and passwords you wish to use for connecting to IRC servers","Insira o nome de usuário, codificações, portas e senhas que você deseja para usar nos servidores IRC"}.
{"Erlang Jabber Server","Servidor Jabber em Erlang"}.
{"Error","Erro"}.
{"Example: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}].","Exemplo: [{\"irc.teste.net\", \"koi8-r\"}, 6667, \"senha\"}, {\"dominio.foo.net\", \"iso8859-1\", 7000}, {\"irc.servidordeteste.net\", \"utf-8\"}]."}.
@@ -121,7 +123,7 @@
{"Get User Last Login Time","Obter a Data do Último Login"}.
{"Get User Password","Obter Senha do Usuário"}.
{"Get User Statistics","Obter Estatísticas do Usuário"}.
-{"Grant voice to this person?","Dar 'voice' a esta usuário?"}.
+{"Grant voice to this person?","Dar voz a esta pessoa?"}.
{"Group ","Grupo "}.
{"Groups","Grupos"}.
{"has been banned","foi banido"}.
@@ -129,7 +131,7 @@
{"has been kicked because of a system shutdown","foi desconectado porque o sistema foi desligado"}.
{"has been kicked because the room has been changed to members-only","foi desconectado porque a política da sala mudou, só membros são permitidos"}.
{"has been kicked","foi removido"}.
-{" has set the subject to: "," a posto o assunto: "}.
+{" has set the subject to: "," mudou o assunto para: "}.
{"Host","Máquina"}.
{"If you don't see the CAPTCHA image here, visit the web page.","Se você não conseguir ver o CAPTCHA aqui, visite a web page."}.
{"If you want to specify different ports, passwords, encodings for IRC servers, fill this list with values in format '{\"irc server\", \"encoding\", port, \"password\"}'. By default this service use \"~s\" encoding, port ~p, empty password.","Se você deseja especificar portas diferentes, senhas ou codifações para servidores de IRC, complete esta lista com os valores no formato: '{\"servidor IRC\", \"codificação\", porta, \"senha\"}'. Por padrão, este serviço usa a codificação \"~s\", porta \"~p\", e senha em branco (vazia)"}.
@@ -142,6 +144,7 @@
{"Import Users from Dir at ","Importar usuários a partir do diretório em "}.
{"Import Users From jabberd14 Spool Files","Importar usuários de arquivos jabberd14 (spool files)"}.
{"Improper message type","Tipo de mensagem incorreto"}.
+{"Incoming s2s Connections:","Conexões que entram de s2s"}.
{"Incorrect password","Senha incorreta"}.
{"Invalid affiliation: ~s","Afiliação não válida: ~s"}.
{"Invalid role: ~s","Cargo (role) é não válido: ~s"}.
@@ -154,8 +157,9 @@
{"IRC username","Usuário IRC"}.
{"IRC Username","Usuário IRC"}.
{"is now known as","é agora conhecido como"}.
+{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Não é permitido o envio de mensagens de erro a esta sala. O membro (~s) enviou uma mensagem de erro (~s) e foi desconectado (\"kicked\")."}.
{"It is not allowed to send private messages","Não é permitido enviar mensagens privadas"}.
-{"It is not allowed to send private messages of type \"groupchat\"","No está permitido enviar mensagens privados do tipo \"groupchat\""}.
+{"It is not allowed to send private messages of type \"groupchat\"","Não é permitido enviar mensagens privadas do tipo \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Impedir o envio de mensagens privadas para a sala"}.
{"Jabber Account Registration","Registros de Contas Jabber"}.
{"Jabber ID","ID Jabber"}.
@@ -181,7 +185,7 @@
{"Make room CAPTCHA protected","Tornar protegida a senha da sala"}.
{"Make room members-only","Tornar sala apenas para membros"}.
{"Make room moderated","Tornar a sala moderada"}.
-{"Make room password protected","Tornar protegida a senha da sala"}.
+{"Make room password protected","Tornar sala protegida à senha"}.
{"Make room persistent","Tornar sala persistente"}.
{"Make room public searchable","Tornar sala pública possível de ser encontrada"}.
{"March","Março"}.
@@ -189,13 +193,14 @@
{"Max # of items to persist","Máximo # de elementos que persistem"}.
{"Max payload size in bytes","Máximo tamanho do payload em bytes"}.
{"May","Maio"}.
-{"Membership is required to enter this room","Necessitas ser membro de esta sala para poder entrar"}.
+{"Membership is required to enter this room","Necessitas ser membro desta sala para poder entrar"}.
{"Members:","Membros:"}.
{"Memorize your password, or write it in a paper placed in a safe place. In Jabber there isn't an automated way to recover your password if you forget it.","Memorize a sua senha, ou escreva-a em um papel e guarde-o em um lugar seguro. Jabber não é uma maneira automatizada para recuperar a sua senha, se você a esquecer eventualmente."}.
{"Memory","Memória"}.
{"Message body","Corpo da mensagem"}.
{"Middle Name","Nome do meio"}.
{"Minimum interval between voice requests (in seconds)","O intervalo mínimo entre requisições de voz (em segundos)"}.
+{"Moderator","Moderador"}.
{"Moderator privileges required","Se necessita privilégios de moderador"}.
{"moderators only","apenas moderadores"}.
{"Modified modules","Módulos atualizados"}.
@@ -211,7 +216,7 @@
{"New Password:","Nova Senha:"}.
{"Nickname","Apelido"}.
{"Nickname Registration at ","Registro do apelido em "}.
-{"Nickname ~s does not exist in the room","O nick ~s não existe em la sala"}.
+{"Nickname ~s does not exist in the room","O nick ~s não existe na sala"}.
{"nobody","ninguém"}.
{"No body provided for announce message","Nenhum corpo de texto fornecido para anunciar mensagem"}.
{"No Data","Nenhum dado"}.
@@ -238,20 +243,22 @@
{"Online","Conectado"}.
{"Online Users","Usuários conectados"}.
{"Online Users:","Usuários online"}.
-{"Only deliver notifications to available users","Solo enviar notificações aos usuários disponíveis"}.
+{"Only deliver notifications to available users","Somente enviar notificações aos usuários disponíveis"}.
+{"Only members may query archives of this room","Somente os membros podem procurar nos arquivos desta sala"}.
{"Only moderators and participants are allowed to change the subject in this room","Somente os moderadores e os participamentes podem alterar o assunto desta sala"}.
{"Only moderators are allowed to change the subject in this room","Somente os moderadores podem alterar o assunto desta sala"}.
{"Only moderators can approve voice requests","Somente moderadores podem aprovar requisições de voz"}.
-{"Only occupants are allowed to send messages to the conference","Solo os ocupantes podem enviar mensagens a la sala"}.
-{"Only occupants are allowed to send queries to the conference","Solo os ocupantes podem enviar consultas a la sala"}.
+{"Only occupants are allowed to send messages to the conference","Somente os ocupantes podem enviar mensagens à sala"}.
+{"Only occupants are allowed to send queries to the conference","Somente os ocupantes podem enviar consultas à sala"}.
{"Only service administrators are allowed to send service messages","Apenas administradores possuem permissão para enviar mensagens de serviço"}.
{"Options","Opções"}.
{"Organization Name","Nome da organização"}.
{"Organization Unit","Departamento/Unidade"}.
{"Outgoing s2s Connections","Conexões que partam de s2s"}.
{"Outgoing s2s Connections:","Conexões que partem de s2s"}.
-{"Owner privileges required","Se requere privilégios de proprietário da sala"}.
+{"Owner privileges required","Se requer privilégios de proprietário da sala"}.
{"Packet","Pacote"}.
+{"Participant","Participante"}.
{"Password ~b","Senha ~b"}.
{"Password:","Senha:"}.
{"Password","Senha"}.
@@ -265,6 +272,8 @@
{"Persist items to storage","Persistir elementos ao armazenar"}.
{"Ping","Ping"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Observe que tais opções farão backup apenas da base de dados Mnesia. Caso você esteja utilizando o modulo ODBC, você precisará fazer backup de sua base de dados SQL separadamente."}.
+{"Please specify file name.","Por favor informe o nome do arquivo."}.
+{"Please specify file size.","Por favor informe o tamanho do arquivo."}.
{"Please, wait for a while before sending new voice request","Por favor, espere antes de enviar uma nova requisição de voz"}.
{"Pong","Pong"}.
{"Port ~b","Porta ~b"}.
@@ -276,18 +285,18 @@
{"PubSub subscriber request","PubSub requisição de assinante"}.
{"Purge all items when the relevant publisher goes offline","Descartar todos os itens quando o publicante principal estiver offline"}.
{"Queries to the conference members are not allowed in this room","Nesta sala não se permite consultas aos membros da sala"}.
-{"RAM and disc copy","Copias na RAM e disco rígido"}.
-{"RAM copy","Copia em RAM"}.
+{"RAM and disc copy","Cópias na RAM e disco rígido"}.
+{"RAM copy","Cópia em RAM"}.
{"Raw","Intocado"}.
{"Really delete message of the day?","Deletar realmente a mensagem do dia?"}.
-{"Recipient is not in the conference room","O receptor não está em la sala de conferencia"}.
+{"Recipient is not in the conference room","O receptor não está na sala de conferência"}.
{"Register a Jabber account","Registrar uma conta Jabber"}.
{"Registered nicknames","Usuários registrados"}.
{"Registered Users:","Usuários registrados"}.
{"Registered Users","Usuários Registrados"}.
{"Register","Registrar"}.
{"Registration in mod_irc for ","Registro em mod_irc para "}.
-{"Remote copy","Copia remota"}.
+{"Remote copy","Cópia remota"}.
{"Remove All Offline Messages","Remover Todas as Mensagens Offline"}.
{"Remove","Remover"}.
{"Remove User","Remover usuário"}.
@@ -300,8 +309,9 @@
{"Restore binary backup immediately:","Restaurar backup binário imediatamente"}.
{"Restore plain text backup immediately:","Restaurar backup formato texto imediatamente:"}.
{"Restore","Restaurar"}.
+{"Roles for which Presence is Broadcasted","Para quem a presença será notificada"}.
{"Room Configuration","Configuração de salas"}.
-{"Room creation is denied by service policy","Se te a denegado criar la sala por política do serviço"}.
+{"Room creation is denied by service policy","Sala não pode ser criada devido à política do serviço"}.
{"Room description","Descrição da Sala"}.
{"Room Occupants","Número de participantes"}.
{"Room title","Título da sala"}.
@@ -310,7 +320,7 @@
{"Roster of ","Lista de contatos de "}.
{"Roster size","Tamanho da Lista"}.
{"RPC Call Error","Erro de chamada RPC"}.
-{"Running Nodes","Nos em execução"}.
+{"Running Nodes","Nós em execução"}.
{"~s access rule configuration","Configuração da Regra de Acesso ~s"}.
{"Saturday","Sábado"}.
{"Script check","Verificação de Script"}.
@@ -323,6 +333,7 @@
{"September","Setembro"}.
{"Server ~b","Servidor ~b"}.
{"Server:","Servidor:"}.
+{"Server","Servidor"}.
{"Set message of the day and send to online users","Definir mensagem do dia e enviar a todos usuários online"}.
{"Set message of the day on all hosts and send to online users","Definir mensagem do dia em todos os hosts e enviar para os usuários online"}.
{"Shared Roster Groups","Grupos Shared Roster"}.
@@ -361,9 +372,9 @@
{"the password is","a senha é"}.
{"The password is too weak","Senha considerada fraca'"}.
{"The password of your Jabber account was successfully changed.","A senha da sua conta Jabber foi mudada com sucesso."}.
-{"There was an error changing the password: ","Houveram erros ao mudar a senha: "}.
-{"There was an error creating the account: ","Houveram erras ao criar esta conta: "}.
-{"There was an error deleting the account: ","Erro ao deletar esta conta: "}.
+{"There was an error changing the password: ","Houve um erro ao mudar a senha: "}.
+{"There was an error creating the account: ","Houve um erro ao criar esta conta: "}.
+{"There was an error deleting the account: ","Houve um erro ao deletar esta conta: "}.
{"This IP address is blacklisted in ~s","Este endereço IP está bloqueado em ~s"}.
{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","Não é 'case insensitive': macbeth é o mesmo que MacBeth e ainda Macbeth. "}.
{"This page allows to create a Jabber account in this Jabber server. Your JID (Jabber IDentifier) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","Esta pagina aceita criações de novas contas Jabber neste servidor. A sua JID (Identificador Jabber) será da seguinte forma: 'usuário@servidor'. Por favor, leia cuidadosamente as instruções para preencher corretamente os campos."}.
@@ -372,7 +383,7 @@
{"Time delay","Intervalo (Tempo)"}.
{"Time","Tempo"}.
{"Too many CAPTCHA requests","Número excessivo de requisições para o CAPTCHA"}.
-{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","Número excessivo (~p) de tentativas falhas de autenticação (~s). O endereço serádesbloqueado as ~s UTC"}.
+{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","Número excessivo (~p) de tentativas falhas de autenticação (~s). O endereço será desbloqueado às ~s UTC"}.
{"Too many unacked stanzas","número excessivo de instâncias sem confirmação"}.
{"To","Para"}.
{"To ~s","Para ~s"}.
@@ -399,30 +410,31 @@
{"User Management","Gerenciamento de Usuários"}.
{"Username:","Usuário:"}.
{"Users are not allowed to register accounts so quickly","Usuários não estão autorizados a registrar contas imediatamente"}.
-{"Users Last Activity","Ultimas atividades dos usuários"}.
+{"Users Last Activity","Últimas atividades dos usuários"}.
{"User ~s","Usuário ~s"}.
{"Users","Usuários"}.
{"User","Usuário"}.
{"Validate","Validar"}.
{"vCard User Search","Busca de Usuário vCard"}.
{"Virtual Hosts","Hosts virtuais"}.
-{"Visitors are not allowed to change their nicknames in this room","Nesta sala, os visitantes não pode mudar seus apelidos"}.
+{"Visitors are not allowed to change their nicknames in this room","Nesta sala, os visitantes não podem mudar seus apelidos"}.
{"Visitors are not allowed to send messages to all occupants","Os visitantes não podem enviar mensagens a todos os ocupantes"}.
+{"Visitor","Visitante"}.
{"Voice request","Requisição de voz"}.
-{"Voice requests are disabled in this conference","Requisições de voz estào desabilitadas nesta conferência"}.
+{"Voice requests are disabled in this conference","Requisições de voz estão desabilitadas nesta conferência"}.
{"Wednesday","Quarta"}.
{"When to send the last published item","Quando enviar o último tópico publicado"}.
{"Whether to allow subscriptions","Permitir subscrições"}.
{"You can later change your password using a Jabber client.","Mais tarde você pode alterar a sua senha usando um cliente Jabber."}.
-{"You have been banned from this room","As sido bloqueado em esta sala"}.
+{"You have been banned from this room","Você foi banido desta sala"}.
{"You must fill in field \"Nickname\" in the form","Você deve completar o campo \"Apelido\" no formulário"}.
{"You need a client that supports x:data and CAPTCHA to register","Você precisa de um cliente com suporte de x:data para poder registrar o nick"}.
{"You need a client that supports x:data to register the nickname","Você precisa de um cliente com suporte a x:data para registrar o seu apelido"}.
-{"You need an x:data capable client to configure mod_irc settings","Necessitas um cliente com suporte de x:data para configurar las opções de mod_irc"}.
-{"You need an x:data capable client to configure room","Necessitas um cliente com suporte de x:data para configurar la sala"}.
+{"You need an x:data capable client to configure mod_irc settings","Necessitas um cliente com suporte de x:data para configurar as opções de mod_irc"}.
+{"You need an x:data capable client to configure room","Necessitas um cliente com suporte de x:data para configurar a sala"}.
{"You need an x:data capable client to search","Necessitas um cliente com suporte de x:data para poder buscar"}.
{"Your active privacy list has denied the routing of this stanza.","Sua lista de privacidade ativa negou o roteamento deste."}.
{"Your contact offline message queue is full. The message has been discarded.","Sua fila de mensagens offline esta cheia. Sua mensagem foi descartada"}.
-{"Your Jabber account was successfully created.","Sua conta jabber foi criada corretamente."}.
+{"Your Jabber account was successfully created.","Sua conta jabber foi criada com sucesso."}.
{"Your Jabber account was successfully deleted.","Sua conta Jabber foi deletada com sucesso."}.
{"Your messages to ~s are being blocked. To unblock them, visit ~s","Suas mensagens para ~s estão bloqueadas. Para desbloqueá-las, visite: ~s"}.
diff --git a/priv/msgs/pt-br.po b/priv/msgs/pt-br.po
index e60153a8..6d2ae1c5 100644
--- a/priv/msgs/pt-br.po
+++ b/priv/msgs/pt-br.po
@@ -1,11 +1,12 @@
msgid ""
msgstr ""
"Project-Id-Version: 2.1.0-alpha\n"
-"Last-Translator: Otávio Fernandes\n"
+"Last-Translator: Victor Rodrigues\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Language: Portuguese (Brazil)\n"
+"X-Additional-Translator: Otávio Fernandes\n"
"X-Additional-Translator: Renato Botelho\n"
"X-Additional-Translator: Lucius Curado\n"
"X-Additional-Translator: Felipe Brito Vasconcellos\n"
@@ -153,7 +154,7 @@ msgstr "Usuários conectados"
#: ejabberd_web_admin.erl:971
msgid "Users Last Activity"
-msgstr "Ultimas atividades dos usuários"
+msgstr "Últimas atividades dos usuários"
#: ejabberd_web_admin.erl:975
msgid "Period: "
@@ -281,7 +282,7 @@ msgstr "Nós"
#: ejabberd_web_admin.erl:1814 mod_configure.erl:525
msgid "Running Nodes"
-msgstr "Nos em execução"
+msgstr "Nós em execução"
#: ejabberd_web_admin.erl:1815 mod_configure.erl:526
msgid "Stopped Nodes"
@@ -327,7 +328,7 @@ msgstr "Erro de chamada RPC"
#: ejabberd_web_admin.erl:1917
msgid "Database Tables at ~p"
-msgstr "Tabelas do bancod de dados em ~p"
+msgstr "Tabelas da Base de dados em ~p"
#: ejabberd_web_admin.erl:1927 mod_vcard.erl:490 mod_vcard.erl:616
msgid "Name"
@@ -687,19 +688,19 @@ msgstr "Selecione o tipo de armazenamento das tabelas"
#: mod_configure.erl:1014 mod_configure.erl:1016
msgid "Disc only copy"
-msgstr "Somente copia em disco"
+msgstr "Somente cópia em disco"
#: mod_configure.erl:1014 mod_configure.erl:1016
msgid "RAM and disc copy"
-msgstr "Copias na RAM e disco rígido"
+msgstr "Cópias na RAM e disco rígido"
#: mod_configure.erl:1014 mod_configure.erl:1016
msgid "RAM copy"
-msgstr "Copia em RAM"
+msgstr "Cópia em RAM"
#: mod_configure.erl:1014 mod_configure.erl:1016
msgid "Remote copy"
-msgstr "Copia remota"
+msgstr "Cópia remota"
#: mod_configure.erl:1042
msgid "Stop Modules at "
@@ -840,7 +841,7 @@ msgid ""
"will be unblocked at ~s UTC"
msgstr ""
"Número excessivo (~p) de tentativas falhas de autenticação (~s). O endereço "
-"serádesbloqueado as ~s UTC"
+"será desbloqueado às ~s UTC"
#: mod_http_upload.erl:586
msgid "Please specify file size."
@@ -856,7 +857,7 @@ msgstr "Este endereço IP está bloqueado em ~s"
#: mod_irc.erl:220 mod_muc.erl:455
msgid "Access denied by service policy"
-msgstr "Aceso denegado por la política do serviço"
+msgstr "Acesso negado pela política do serviço"
#: mod_irc.erl:439
msgid "IRC Transport"
@@ -869,7 +870,7 @@ msgstr "Módulo de IRC para ejabberd"
#: mod_irc.erl:644
msgid "You need an x:data capable client to configure mod_irc settings"
msgstr ""
-"Necessitas um cliente com suporte de x:data para configurar las opções de "
+"Necessitas um cliente com suporte de x:data para configurar as opções de "
"mod_irc"
#: mod_irc.erl:653
@@ -881,7 +882,7 @@ msgid ""
"Enter username, encodings, ports and passwords you wish to use for "
"connecting to IRC servers"
msgstr ""
-"Insira o nome de usuário, codificações, portas e senhas que vocêdeseja para "
+"Insira o nome de usuário, codificações, portas e senhas que você deseja para "
"usar nos servidores IRC"
#: mod_irc.erl:667
@@ -978,7 +979,7 @@ msgstr ""
#: mod_muc.erl:610
msgid "Room creation is denied by service policy"
-msgstr "Sala não pode ser criada devido a política de serviço"
+msgstr "Sala não pode ser criada devido à política do serviço"
#: mod_muc.erl:617
msgid "Conference room does not exist"
@@ -1078,7 +1079,7 @@ msgstr "é agora conhecido como"
#: mod_muc_log.erl:449 mod_muc_log.erl:788
msgid " has set the subject to: "
-msgstr " a posto o assunto: "
+msgstr " mudou o assunto para: "
#: mod_muc_log.erl:489
msgid "Chatroom is created"
@@ -1090,7 +1091,7 @@ msgstr "A sala de chat está destruída"
#: mod_muc_log.erl:493
msgid "Chatroom is started"
-msgstr "A sala de chat está inciada"
+msgstr "A sala de chat está iniciada"
#: mod_muc_log.erl:495
msgid "Chatroom is stopped"
@@ -1190,7 +1191,7 @@ msgid ""
"has sent an error message (~s) and got kicked from the room"
msgstr ""
"Não é permitido o envio de mensagens de erro a esta sala. O membro "
-"(~s)enviou uma mensagem de erro (~s) e foi desconectado (\"kicked\")."
+"(~s) enviou uma mensagem de erro (~s) e foi desconectado (\"kicked\")."
#: mod_muc_room.erl:241
msgid "It is not allowed to send private messages to the conference"
@@ -1202,7 +1203,7 @@ msgstr "Por favor, espere antes de enviar uma nova requisição de voz"
#: mod_muc_room.erl:329
msgid "Voice requests are disabled in this conference"
-msgstr "Requisições de voz estào desabilitadas nesta conferência"
+msgstr "Requisições de voz estão desabilitadas nesta conferência"
#: mod_muc_room.erl:347
msgid "Failed to extract JID from your voice request approval"
@@ -1218,11 +1219,11 @@ msgstr "Tipo de mensagem incorreto"
#: mod_muc_room.erl:534
msgid "It is not allowed to send private messages of type \"groupchat\""
-msgstr "No está permitido enviar mensagens privados do tipo \"groupchat\""
+msgstr "Não é permitido enviar mensagens privadas do tipo \"groupchat\""
#: mod_muc_room.erl:546 mod_muc_room.erl:621
msgid "Recipient is not in the conference room"
-msgstr "O receptor não está em la sala de conferencia"
+msgstr "O receptor não está na sala de conferência"
#: mod_muc_room.erl:576 mod_muc_room.erl:598
msgid "It is not allowed to send private messages"
@@ -1230,11 +1231,11 @@ msgstr "Não é permitido enviar mensagens privadas"
#: mod_muc_room.erl:588 mod_muc_room.erl:983 mod_muc_room.erl:4594
msgid "Only occupants are allowed to send messages to the conference"
-msgstr "Solo os ocupantes podem enviar mensagens a la sala"
+msgstr "Somente os ocupantes podem enviar mensagens à sala"
#: mod_muc_room.erl:644
msgid "Only occupants are allowed to send queries to the conference"
-msgstr "Solo os ocupantes podem enviar consultas a la sala"
+msgstr "Somente os ocupantes podem enviar consultas à sala"
#: mod_muc_room.erl:657
msgid "Queries to the conference members are not allowed in this room"
@@ -1258,7 +1259,7 @@ msgstr "Os visitantes não podem enviar mensagens a todos os ocupantes"
#: mod_muc_room.erl:1080
msgid "Visitors are not allowed to change their nicknames in this room"
-msgstr "Nesta sala, os visitantes não pode mudar seus apelidos"
+msgstr "Nesta sala, os visitantes não podem mudar seus apelidos"
#: mod_muc_room.erl:1093 mod_muc_room.erl:1835
msgid "That nickname is already in use by another occupant"
@@ -1266,15 +1267,15 @@ msgstr "O apelido (nick) já está sendo utilizado"
#: mod_muc_room.erl:1822
msgid "You have been banned from this room"
-msgstr "As sido bloqueado em esta sala"
+msgstr "Você foi banido desta sala"
#: mod_muc_room.erl:1826
msgid "Membership is required to enter this room"
-msgstr "Necessitas ser membro de esta sala para poder entrar"
+msgstr "Necessitas ser membro desta sala para poder entrar"
#: mod_muc_room.erl:1872
msgid "A password is required to enter this room"
-msgstr "Se necessita senha para entrar em esta sala"
+msgstr "Se necessita senha para entrar nesta sala"
#: mod_muc_room.erl:1898 mod_register.erl:295
msgid "Too many CAPTCHA requests"
@@ -1302,7 +1303,7 @@ msgstr "O Jabber ID ~s não es válido"
#: mod_muc_room.erl:2772
msgid "Nickname ~s does not exist in the room"
-msgstr "O nick ~s não existe em la sala"
+msgstr "O nick ~s não existe na sala"
#: mod_muc_room.erl:2795 mod_muc_room.erl:3175
msgid "Invalid affiliation: ~s"
@@ -1314,7 +1315,7 @@ msgstr "Cargo (role) é não válido: ~s"
#: mod_muc_room.erl:3155 mod_muc_room.erl:3187 mod_muc_room.erl:4236
msgid "Owner privileges required"
-msgstr "Se requere privilégios de proprietário da sala"
+msgstr "Se requer privilégios de proprietário da sala"
#: mod_muc_room.erl:3348
msgid "Configuration of room ~s"
@@ -1342,7 +1343,7 @@ msgstr "Tornar pública a lista de participantes"
#: mod_muc_room.erl:3380
msgid "Make room password protected"
-msgstr "Tornar protegida a senha da sala"
+msgstr "Tornar sala protegida à senha"
#: mod_muc_room.erl:3394
msgid "Maximum Number of Occupants"
@@ -1426,7 +1427,7 @@ msgstr "Permitir mudança de apelido aos visitantes"
#: mod_muc_room.erl:3589
msgid "Allow visitors to send voice requests"
-msgstr "Permitir aos visitantes o envio de convites"
+msgstr "Permitir aos visitantes o envio de requisições de voz"
#: mod_muc_room.erl:3592
msgid "Minimum interval between voice requests (in seconds)"
@@ -1450,7 +1451,7 @@ msgstr "Permitir criação de logs"
#: mod_muc_room.erl:3631
msgid "You need an x:data capable client to configure room"
-msgstr "Necessitas um cliente com suporte de x:data para configurar la sala"
+msgstr "Necessitas um cliente com suporte de x:data para configurar a sala"
#: mod_muc_room.erl:4192
msgid "Number of occupants"
@@ -1474,7 +1475,7 @@ msgstr "Usuário JID"
#: mod_muc_room.erl:4355
msgid "Grant voice to this person?"
-msgstr "Dar 'voice' a esta usuário?"
+msgstr "Dar voz a esta pessoa?"
#: mod_muc_room.erl:4498
msgid "~s invites you to the room ~s"
@@ -1623,7 +1624,7 @@ msgstr "Quando enviar o último tópico publicado"
#: mod_pubsub.erl:3777
msgid "Only deliver notifications to available users"
-msgstr "Solo enviar notificações aos usuários disponíveis"
+msgstr "Somente enviar notificações aos usuários disponíveis"
#: mod_pubsub.erl:3779
msgid "The collections with which a node is affiliated"
@@ -1652,11 +1653,11 @@ msgstr "Usuários não estão autorizados a registrar contas imediatamente"
#: mod_register_web.erl:105
msgid "Your Jabber account was successfully created."
-msgstr "Sua conta jabber foi criada corretamente."
+msgstr "Sua conta jabber foi criada com sucesso."
#: mod_register_web.erl:110
msgid "There was an error creating the account: "
-msgstr "Houveram erras ao criar esta conta: "
+msgstr "Houve um erro ao criar esta conta: "
#: mod_register_web.erl:119
msgid "Your Jabber account was successfully deleted."
@@ -1664,7 +1665,7 @@ msgstr "Sua conta Jabber foi deletada com sucesso."
#: mod_register_web.erl:124
msgid "There was an error deleting the account: "
-msgstr "Erro ao deletar esta conta: "
+msgstr "Houve um erro ao deletar esta conta: "
#: mod_register_web.erl:135
msgid "The password of your Jabber account was successfully changed."
@@ -1672,7 +1673,7 @@ msgstr "A senha da sua conta Jabber foi mudada com sucesso."
#: mod_register_web.erl:140
msgid "There was an error changing the password: "
-msgstr "Houveram erros ao mudar a senha: "
+msgstr "Houve um erro ao mudar a senha: "
#: mod_register_web.erl:175 mod_register_web.erl:183
msgid "Jabber Account Registration"
@@ -1719,7 +1720,7 @@ msgid ""
"Don't tell your password to anybody, not even the administrators of the "
"Jabber server."
msgstr ""
-"Não revele o seu computador a ninguém, mesmo para o administrador deste "
+"Não revele a sua senha a ninguém, nem mesmo para o administrador deste "
"servidor Jabber."
#: mod_register_web.erl:249
diff --git a/priv/msgs/uk.msg b/priv/msgs/uk.msg
index 985796ca..568ac092 100644
--- a/priv/msgs/uk.msg
+++ b/priv/msgs/uk.msg
@@ -1,4 +1,5 @@
%% -*- coding: latin-1 -*-
+{"Accept","Прийняти"}.
{"Access Configuration","Конфігурація доступу"}.
{"Access Control List Configuration","Конфігурація списків керування доступом"}.
{"Access control lists","Списки керування доступом"}.
@@ -21,7 +22,9 @@
{"Allow users to send invites","Дозволити користувачам надсилати запрошення"}.
{"Allow users to send private messages","Дозволити приватні повідомлення"}.
{"Allow visitors to change nickname","Дозволити відвідувачам змінювати псевдонім"}.
+{"Allow visitors to send private messages to","Дозволити відвідувачам відсилати приватні повідомлення"}.
{"Allow visitors to send status text in presence updates","Дозволити відвідувачам відсилати текст статусу в оновленнях присутності"}.
+{"Allow visitors to send voice requests","Дозволити відвідувачам надсилати голосові запрошення"}.
{"All Users","Всі користувачі"}.
{"Announcements","Сповіщення"}.
{"anyone","всім учасникам"}.
@@ -29,6 +32,7 @@
{"April","квітня"}.
{"August","серпня"}.
{"Backup Management","Керування резервним копіюванням"}.
+{"Backup of ~p","Резервне копіювання ~p"}.
{"Backup to File at ","Резервне копіювання в файл на "}.
{"Backup","Резервне копіювання"}.
{"Bad format","Неправильний формат"}.
@@ -56,6 +60,7 @@
{"Connections parameters","Параметри з'єднання"}.
{"Country","Країна"}.
{"CPU Time:","Процесорний час:"}.
+{"Database Tables at ~p","Таблиці бази даних на ~p"}.
{"Database Tables Configuration at ","Конфігурація таблиць бази даних на "}.
{"Database","База даних"}.
{"December","грудня"}.
@@ -73,15 +78,19 @@
{"Dump Backup to Text File at ","Копіювання в текстовий файл на "}.
{"Dump to Text File","Копіювання в текстовий файл"}.
{"Edit Properties","Змінити параметри"}.
+{"Either approve or decline the voice request.","Підтвердить або відхилите голосовий запит"}.
{"ejabberd IRC module","ejabberd IRC модуль"}.
{"ejabberd MUC module","ejabberd MUC модуль"}.
+{"ejabberd Multicast service","Мультікаст ejabberd сервіс"}.
{"ejabberd Publish-Subscribe module","Модуль ejabberd Публікації-Підписки"}.
{"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams модуль"}.
{"ejabberd vCard module","ejabberd vCard модуль"}.
{"ejabberd Web Admin","Веб-інтерфейс Адміністрування ejabberd"}.
{"Elements","Елементи"}.
{"Email","Електронна пошта"}.
+{"Empty Rooms","Порожні кімнати"}.
{"Enable logging","Включити журнал роботи"}.
+{"Enable message archiving","Ввімкнути архівацію повідомлень"}.
{"Encoding for server ~b","Кодування для сервера ~b"}.
{"End User Session","Закінчити Сеанс Користувача"}.
{"Enter list of {Module, [Options]}","Введіть перелік такого виду {Module, [Options]}"}.
@@ -97,8 +106,10 @@
{"Error","Помилка"}.
{"Example: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}].","Приклад: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}]."}.
{"Exclude Jabber IDs from CAPTCHA challenge","Пропускати ці Jabber ID без CAPTCHA-запиту"}.
+{"Export all tables as SQL queries to a file:","Експорт усіх таблиць, як SQL запити, у файл"}.
{"Export data of all users in the server to PIEFXIS files (XEP-0227):","Експорт даних всіх користувачів сервера до файлу PIEFXIS (XEP-0227):"}.
{"Export data of users in a host to PIEFXIS files (XEP-0227):","Експорт даних користувачів домена до файлу PIEFXIS (XEP-0227):"}.
+{"Failed to extract JID from your voice request approval","Помилка витягнення JID з вашого схвалення голосового запиту"}.
{"Family Name","Прізвище"}.
{"February","лютого"}.
{"Fill in fields to search for any matching Jabber User","Заповніть поля для пошуку користувача Jabber"}.
@@ -112,6 +123,7 @@
{"Get User Last Login Time","Отримати Час Останнього Підключення Користувача"}.
{"Get User Password","Отримати Пароль Користувача"}.
{"Get User Statistics","Отримати Статистику по Користувачу"}.
+{"Grant voice to this person?","Надати голос персоні?"}.
{"Groups","Групи"}.
{"Group ","Група "}.
{"has been banned","заборонили вхід в кімнату"}.
@@ -130,8 +142,9 @@
{"Import users data from a PIEFXIS file (XEP-0227):","Імпорт даних користовучів з файлу PIEFXIS (XEP-0227):"}.
{"Import users data from jabberd14 spool directory:","Імпорт користувачів з діректорії спула jabberd14:"}.
{"Import Users from Dir at ","Імпортування користувача з директорії на "}.
-{"Import Users From jabberd14 Spool Files","Імпорт користувачів зі спулу jabberd14"}.
+{"Import Users From jabberd14 Spool Files","Імпорт користувачів з jabberd14 файлів \"Spool\""}.
{"Improper message type","Неправильний тип повідомлення"}.
+{"Incoming s2s Connections:","Вхідні s2s-з'єднання:"}.
{"Incorrect password","Неправильний пароль"}.
{"Invalid affiliation: ~s","Недопустимий ранг: ~s"}.
{"Invalid role: ~s","Недопустима роль: ~s"}.
@@ -144,6 +157,7 @@
{"IRC username","Ім'я користувача IRC"}.
{"IRC Username","Ім'я користувача IRC"}.
{"is now known as","змінив(ла) псевдонім на"}.
+{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Не дозволяється відправляти помилкові повідомлення в кімнату. Учасник (~s) відправив помилкове повідомлення (~s), та був виганий з кімнати"}.
{"It is not allowed to send private messages of type \"groupchat\"","Не дозволяється надсилати приватні повідомлення типу \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Не дозволяється надсилати приватні повідомлення в конференцію"}.
{"It is not allowed to send private messages","Приватні повідомлення не дозволені"}.
@@ -165,6 +179,7 @@
{"Listened Ports at ","Відкриті порти на "}.
{"Listened Ports","Відкриті порти"}.
{"List of modules to start","Список завантажуваних модулів"}.
+{"List of rooms","Перелік кімнат"}.
{"Low level update script","Низькорівневий сценарій поновлення"}.
{"Make participants list public","Зробити список учасників видимим всім"}.
{"Make room CAPTCHA protected","Зробити кімнату захищеною капчею"}.
@@ -184,12 +199,17 @@
{"Memory","Пам'ять"}.
{"Message body","Тіло повідомлення"}.
{"Middle Name","По-батькові"}.
+{"Minimum interval between voice requests (in seconds)","Мінімальний інтервал між голосовими запитами (в секундах)"}.
{"Moderator privileges required","Необхідні права модератора"}.
{"moderators only","тільки модераторам"}.
+{"Moderator","Модератор"}.
{"Modified modules","Змінені модулі"}.
+{"Modules at ~p","Модулі на ~p"}.
{"Modules","Модулі"}.
{"Module","Модуль"}.
{"Monday","Понеділок"}.
+{"Multicast","Мультікаст"}.
+{"Multi-User Chat","Багато-користувальницький чат"}.
{"Name:","Назва:"}.
{"Name","Назва"}.
{"Never","Ніколи"}.
@@ -198,9 +218,11 @@
{"Nickname ~s does not exist in the room","Псевдонім ~s в кімнаті відсутній"}.
{"Nickname","Псевдонім"}.
{"No body provided for announce message","Тіло оголошення має бути непустим"}.
+{"nobody","ніхто"}.
{"No Data","Немає даних"}.
{"Node ID","ID вузла"}.
{"Node not found","Вузол не знайдено"}.
+{"Node ~p","Вузол ~p"}.
{"Nodes","Вузли"}.
{"No limit","Без обмежень"}.
{"None","Немає"}.
@@ -222,8 +244,10 @@
{"Online Users","Підключені користувачі"}.
{"Online","Підключений"}.
{"Only deliver notifications to available users","Доставляти повідомленнями тільки доступним користувачам"}.
+{"Only members may query archives of this room","Тільки модератори можуть запитувати архіви цієї кімнати"}.
{"Only moderators and participants are allowed to change the subject in this room","Тільки модератори та учасники можуть змінювати тему в цій кімнаті"}.
{"Only moderators are allowed to change the subject in this room","Тільки модератори можуть змінювати тему в цій кімнаті"}.
+{"Only moderators can approve voice requests","Тільки модератори можуть схвалювати голосові запити"}.
{"Only occupants are allowed to send messages to the conference","Тільки присутнім дозволяється надсилати повідомленняя в конференцію"}.
{"Only occupants are allowed to send queries to the conference","Тільки присутнім дозволяється відправляти запити в конференцію"}.
{"Only service administrators are allowed to send service messages","Тільки адміністратор сервісу може надсилати службові повідомлення"}.
@@ -234,6 +258,7 @@
{"Outgoing s2s Connections","Вихідні s2s-з'єднання"}.
{"Owner privileges required","Необхідні права власника"}.
{"Packet","Пакет"}.
+{"Participant","Учасник"}.
{"Password ~b","Пароль ~b"}.
{"Password Verification:","Перевірка Пароля:"}.
{"Password Verification","Перевірка Пароля"}.
@@ -243,9 +268,13 @@
{"Path to File","Шлях до файла"}.
{"Pending","Очікування"}.
{"Period: ","Період"}.
+{"Permanent rooms","Постійні кімнати"}.
{"Persist items to storage","Зберегати публікації до сховища"}.
{"Ping","Пінг"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Зауважте, що ця опція відповідає за резервне копіювання тільки вбудованної бази даних Mnesia. Якщо Ви також використовуєте інше сховище для даних (наприклад за допомогою модуля ODBC), то його резервне копіювання потрібно робити окремо."}.
+{"Please specify file name.","Будь ласка вкажіть ім'я файлу."}.
+{"Please specify file size.","Будь ласка вкажіть розмір файлу."}.
+{"Please, wait for a while before sending new voice request","Будь ласка, почекайте деякий час перед тим, як знову відправляти голосовий запит"}.
{"Pong","Понг"}.
{"Port ~b","Порт ~b"}.
{"Port","Порт"}.
@@ -259,9 +288,10 @@
{"RAM and disc copy","ОЗП та диск"}.
{"RAM copy","ОЗП"}.
{"Raw","необроблений формат"}.
-{"Really delete message of the day?","Насправді видалити повідомлення дня?"}.
+{"Really delete message of the day?","Насправді, видалити повідомлення дня?"}.
{"Recipient is not in the conference room","Адресата немає в конференції"}.
{"Register a Jabber account","Зареєструвати Jabber-акаунт"}.
+{"Registered nicknames","Зареєстровані імена"}.
{"Registered Users:","Зареєстровані користувачі:"}.
{"Registered Users","Зареєстровані користувачі"}.
{"Register","Реєстрація"}.
@@ -279,6 +309,7 @@
{"Restore binary backup immediately:","Відновити з бінарної резервної копії негайно:"}.
{"Restore plain text backup immediately:","Відновити з текстової резервної копії негайно:"}.
{"Restore","Відновлення з резервної копії"}.
+{"Roles for which Presence is Broadcasted","Ролі для яких поширюється наявність"}.
{"Room Configuration","Конфігурація кімнати"}.
{"Room creation is denied by service policy","Створювати конференцію заборонено політикою служби"}.
{"Room description","Опис кімнати"}.
@@ -302,6 +333,7 @@
{"September","вересня"}.
{"Server ~b","Сервер ~b"}.
{"Server:","Сервер:"}.
+{"Server","Сервер:"}.
{"Set message of the day and send to online users","Встановити повідомлення дня та надіслати його підключеним користувачам"}.
{"Set message of the day on all hosts and send to online users","Встановити повідомлення дня на всіх хостах та надійслати його підключеним користувачам"}.
{"Shared Roster Groups","Спільні групи контактів"}.
@@ -343,6 +375,7 @@
{"There was an error changing the password: ","Помилка при зміні пароля: "}.
{"There was an error creating the account: ","Помилка при створенні акаунту:"}.
{"There was an error deleting the account: ","Помилка при видаленні акаунту: "}.
+{"This IP address is blacklisted in ~s","Ця IP адреса у чорному списку ~s"}.
{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","Регістр не має значення: \"МАША\" та \"маша\" буде сприйматися як одне й те саме ім'я."}.
{"This page allows to create a Jabber account in this Jabber server. Your JID (Jabber IDentifier) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","Тут ви можете зареєструвати обліковий запис Jabber на цьому сервері. Ваш JID (ідентифікатор Jabber) матиме вигляд \"користувач@сервер\". Щоб вірно заповнити поля нижче, будь ласка, уважно читайте інструкції до них."}.
{"This page allows to unregister a Jabber account in this Jabber server.","Ця сторінка дозволяє видалити свій акаунт з Jabber-сервера."}.
@@ -350,7 +383,10 @@
{"Time delay","Час затримки"}.
{"Time","Час"}.
{"Too many CAPTCHA requests","Надто багато CAPTCHA-запитів"}.
+{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","Забагато (~p) помилок авторизації з цієї IP адреси (~s). Адресу буде розблоковано о ~s UTC"}.
+{"Too many unacked stanzas","Занадто багато пакетів без відповідей"}.
{"To ~s","До ~s"}.
+{"Total rooms","Всього кімнат"}.
{"To","Кому"}.
{"Traffic rate limit is exceeded","Швидкість передачі інформації було перевищено"}.
{"Transactions Aborted:","Транзакції відмінені:"}.
@@ -364,15 +400,18 @@
{"Unregister","Видалити"}.
{"Update message of the day (don't send)","Оновити повідомлення дня (не надсилати)"}.
{"Update message of the day on all hosts (don't send)","Оновити повідомлення дня на всіх хостах (не надсилати)"}.
-{"Update plan","План поновлення"}.
+{"Update plan","План оновлення"}.
+{"Update ~p","Оновлення ~p"}.
{"Update script","Сценарій поновлення"}.
{"Update","Обновити"}.
{"Uptime:","Час роботи:"}.
{"Use of STARTTLS required","Ви мусите використовувати STARTTLS"}.
+{"User JID","JID Користувача"}.
{"User Management","Управління Користувачами"}.
{"Username:","Ім'я користувача:"}.
{"Users are not allowed to register accounts so quickly","Користувачам не дозволено так часто реєструвати облікові записи"}.
{"Users Last Activity","Статистика останнього підключення користувачів"}.
+{"User ~s","Користувач ~s"}.
{"Users","Користувачі"}.
{"User","Користувач"}.
{"Validate","Затвердити"}.
@@ -380,6 +419,9 @@
{"Virtual Hosts","віртуальні хости"}.
{"Visitors are not allowed to change their nicknames in this room","Відвідувачам не дозволяється змінювати псевдонім в цій кімнаті"}.
{"Visitors are not allowed to send messages to all occupants","Відвідувачам не дозволяється надсилати повідомлення всім присутнім"}.
+{"Visitor","Відвідувач"}.
+{"Voice requests are disabled in this conference","Голосові запити відключені в цій конференції"}.
+{"Voice request","Голосовий запит"}.
{"Wednesday","Середа"}.
{"When to send the last published item","Коли надсилати останній опублікований елемент"}.
{"Whether to allow subscriptions","Дозволяти підписку"}.
diff --git a/priv/msgs/wa.msg b/priv/msgs/wa.msg
index 9c79a6f7..8946b71d 100644
--- a/priv/msgs/wa.msg
+++ b/priv/msgs/wa.msg
@@ -1,4 +1,5 @@
%% -*- coding: latin-1 -*-
+{"Accept","Accepter"}.
{"Access Configuration","Apontiaedje des accès"}.
{"Access Control List Configuration","Apontiaedje des droets (ACL)"}.
{"Access control lists","Droets (ACL)"}.
@@ -16,24 +17,35 @@
{"A friendly name for the node","On no uzeu-ahessåve pol nuk"}.
{"All activity","Dispoy todi"}.
{"Allow this Jabber ID to subscribe to this pubsub node?","Permete ki ci Jabber ID ci si poye abouner a ç' nuk eplaidaedje-abounmint ci?"}.
+{"Allow users to change the subject","Les uzeus polèt candjî l' tite"}.
{"Allow users to query other users","Les uzeus polèt cweri ls ôtes uzeus"}.
{"Allow users to send invites","Les uzeus polèt evoyî priyaedjes"}.
{"Allow users to send private messages","Les uzeus polèt evoyî des messaedjes privés"}.
{"Allow visitors to change nickname","Permete ki les viziteus candjexhe leus metous nos"}.
+{"Allow visitors to send private messages to","Les uzeus polèt evoyî des messaedjes privés"}.
{"Allow visitors to send status text in presence updates","Permete ki les viziteus evoyexhe des tecse d' estat dins leus messaedjes di prezince"}.
+{"Allow visitors to send voice requests","Les uzeus polèt evoyî des dmandes di vwès"}.
{"All Users","Tos les uzeus"}.
{"Announcements","Anonces"}.
{"anyone","tot l' minme kî"}.
+{"A password is required to enter this room","I fåt dner on scret po poleur intrer dins cisse såle ci"}.
{"April","avri"}.
{"August","awousse"}.
{"Backup","Copeye di såvrité"}.
{"Backup Management","Manaedjaedje des copeyes di såvrité"}.
+{"Backup of ~p","Copeye di såvrité po ~p"}.
{"Backup to File at ","Fé ene copeye di såvrité dins on fitchî so "}.
{"Bad format","Mwais fôrmat"}.
{"Birthday","Date d' askepiaedje"}.
+{"CAPTCHA web page","Pådje web CAPTCHA"}.
{"Change Password","Candjî l' sicret"}.
{"Change User Password","Candjî l' sicret d' l' uzeu"}.
+{"Characters not allowed:","Caracteres nén permetous:"}.
{"Chatroom configuration modified","L' apontiaedje del såle di berdelaedje a candjî"}.
+{"Chatroom is created","Li såle di berdelaedje est ahivêye"}.
+{"Chatroom is destroyed","Li såle di berdelaedje est distrûte"}.
+{"Chatroom is started","Li såle di berdelaedje est enondêye"}.
+{"Chatroom is stopped","Li såle di berdelaedje est ahotêye"}.
{"Chatrooms","Såles di berdelaedje"}.
{"Choose a username and password to register with this server","Tchoezixhoz on no d' uzeu eyet on scret po vs edjîstrer so ç' sierveu ci"}.
{"Choose modules to stop","Tchoezixhoz les modules a-z arester"}.
@@ -43,10 +55,13 @@
{"Commands","Comandes"}.
{"Conference room does not exist","Li såle di conferince n' egzistêye nén"}.
{"Configuration","Apontiaedjes"}.
+{"Configuration of room ~s","Apontiaedje del såle ~s"}.
{"Connected Resources:","Raloyî avou les rsoûces:"}.
+{"Connections parameters","Parametes des raloyaedjes"}.
{"Country","Payis"}.
{"CPU Time:","Tins CPU:"}.
{"Database","Båze di dnêyes"}.
+{"Database Tables at ~p","Tåves del båze di dnêyes so ~p"}.
{"Database Tables Configuration at ","Apontiaedje des tåves del båze di dnêyes so "}.
{"December","decimbe"}.
{"Default users as participants","Les uzeus sont des pårticipants come prémetowe dujhance"}.
@@ -59,17 +74,24 @@
{"Description:","Discrijhaedje:"}.
{"Disc only copy","Copeye seulmint sol deure plake"}.
{"Displayed Groups:","Groupes håynés:"}.
+{"Don't tell your password to anybody, not even the administrators of the Jabber server.","Ni dnez vosse sicret a nolu, nén ddja ås manaedjeus do sierveu Jabber."}.
{"Dump Backup to Text File at ","Copeye di såvritè viè on fitchî tecse so "}.
{"Dump to Text File","Schaper en on fitchî tecse"}.
{"Edit Properties","Candjî les prôpietés"}.
+{"Either approve or decline the voice request.","Aprover oudonbén rifuzer li dmande di vwès."}.
{"ejabberd IRC module","Module IRC po ejabberd"}.
{"ejabberd MUC module","Module MUC (såles di berdelaedje) po ejabberd"}.
+{"ejabberd Multicast service","siervice multicast d' ejabberd"}.
{"ejabberd Publish-Subscribe module","Module d' eplaidaedje-abounmint po ejabberd"}.
{"ejabberd SOCKS5 Bytestreams module","Module SOCKS5 Bytestreams po ejabberd"}.
{"ejabberd vCard module","Module vCard ejabberd"}.
{"ejabberd Web Admin","Manaedjeu waibe ejabberd"}.
+{"Elements","Elemints"}.
{"Email","Emile"}.
+{"Empty Rooms","Såles vudes"}.
{"Enable logging","Mete en alaedje li djournå"}.
+{"Enable message archiving","Mete en alaedje l' årtchivaedje des messaedjes"}.
+{"Encoding for server ~b","Ecôdaedje pol sierveu ~b"}.
{"End User Session","Fini l' session d' l' uzeu"}.
{"Enter list of {Module, [Options]}","Dinez ene djivêye del cogne {Module, [Tchuzes]}"}.
{"Enter nickname you want to register","Dinez l' metou no ki vos vloz edjîstrer"}.
@@ -77,7 +99,17 @@
{"Enter path to jabberd14 spool dir","Dinez l' tchimin viè l' ridant di spool jabberd14"}.
{"Enter path to jabberd14 spool file","Dinez l' tchimin viè l' fitchî di spool jabberd14"}.
{"Enter path to text file","Dinez l' tchimin viè l' fitchî tecse"}.
+{"Enter the text you see","Tapez l' tecse ki vos voeyoz"}.
+{"Enter username and encodings you wish to use for connecting to IRC servers. Press 'Next' to get more fields to fill in. Press 'Complete' to save settings.","Dinez les nos d' uzeu et ls ecôdaedjes ki vos vloz eployî po vs raloyî åzès sierveus IRC Clitchîz so «Shuvant» po-z aveur di pus di tchamps a rimpli. Clitchîz so «Fini» po schaper les apontiaedjes."}.
+{"Enter username, encodings, ports and passwords you wish to use for connecting to IRC servers","Dinez l' no d' uzeu, les ecôdaedjes, les pôrts et les screts ki vos vloz eployî po vs raloyî åzès sierveus IRC"}.
{"Erlang Jabber Server","Sierveu Jabber Erlang"}.
+{"Error","Aroke"}.
+{"Example: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}].","Egzimpe: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}]."}.
+{"Exclude Jabber IDs from CAPTCHA challenge","Esclure les IDs Jabber des kesses CAPTCHA"}.
+{"Export all tables as SQL queries to a file:","Espoirter totes les tåves, come des cmandes SQL, viè on fitchî"}.
+{"Export data of all users in the server to PIEFXIS files (XEP-0227):","Espoirter les dnêyes di tos les uzeus do sierveu viè des fitchîs PIEFXIS (XEP-0227):"}.
+{"Export data of users in a host to PIEFXIS files (XEP-0227):","Espoirter les dnêyes di tos les uzeus do sierveu viè des fitchîs PIEFXIS (XEP-0227):"}.
+{"Failed to extract JID from your voice request approval","Nén moyén di rsaetchî on JID foû d' l' aprovaedje di vosse dimande di vwès"}.
{"Family Name","No d' famile"}.
{"February","fevrî"}.
{"Fill in fields to search for any matching Jabber User","Rimplixhoz les tchamps po cweri èn uzeu Jabber"}.
@@ -91,6 +123,7 @@
{"Get User Last Login Time","Riçure li date/eure do dierin elodjaedje di l' uzeu"}.
{"Get User Password","Riçure sicret d' l' uzeu"}.
{"Get User Statistics","Riçure les statistikes di l' uzeu"}.
+{"Grant voice to this person?","Permete li vwès po cisse djin ci?"}.
{"Group ","Groupe "}.
{"Groups","Groupes"}.
{"has been banned","a stî bani"}.
@@ -100,26 +133,42 @@
{"has been kicked because the room has been changed to members-only","a stî pité evoye cåze ki l' såle a stî ristrindowe åzès mimbes seulmint"}.
{" has set the subject to: "," a candjî l' tite a: "}.
{"Host","Sierveu"}.
+{"If you don't see the CAPTCHA image here, visit the web page.","Si vos n' voeyoz nole imådje CAPTCHA chal, vizitez l' pådje waibe."}.
+{"If you want to specify different ports, passwords, encodings for IRC servers, fill this list with values in format '{\"irc server\", \"encoding\", port, \"password\"}'. By default this service use \"~s\" encoding, port ~p, empty password.","Si vos vloz dner des pôrts, sicrets ou ecôdaedjes diferins po les sierveus IRC, rimplixhoz cisse djivêye ci avou des valixhances del cogne «{\"sierveu irc\", \"ecôdaedje\", pôrt, \"sicret\"}». Les prémetowès valixhances do siervice sont «~s» po l' ecôdaedje, «~p» pol pôrt, et on vude sicret."}.
{"Import Directory","Sititchî d' on ridant"}.
{"Import File","Sititchî d' on fitchî"}.
+{"Import user data from jabberd14 spool file:","Sititchî des dnêyes uzeus foû d' on fitchî spoûle jabberd14:"}.
{"Import User from File at ","Sititchî uzeu d' on fitchî so "}.
+{"Import users data from a PIEFXIS file (XEP-0227):","Sititchî des dnêyes uzeus foû d' on fitchî PIEFXIS (XEP-0227):"}.
+{"Import users data from jabberd14 spool directory:","Sititchî des dnêyes uzeus foû d' on ridant spoûle jabberd14:"}.
{"Import Users from Dir at ","Sitichî des uzeus d' on ridant so "}.
{"Import Users From jabberd14 Spool Files","Sititchî des uzeus Jabberd 1.4"}.
{"Improper message type","Sôre di messaedje nén valide"}.
+{"Incoming s2s Connections:","Raloyaedjes s2s en intrêye:"}.
{"Incorrect password","Sicret nén corek"}.
{"Invalid affiliation: ~s","Afiyaedje nén valide: ~s"}.
{"Invalid role: ~s","Role nén valide: ~s"}.
{"IP addresses","Adresses IP"}.
+{"IP","IP"}.
+{"IRC channel (don't put the first #)","Canå IRC (èn nén mete li prumî #)"}.
+{"IRC server","Sierveu IRC"}.
+{"IRC settings","Apontiaedjes IRC"}.
{"IRC Transport","Transpoirt IRC"}.
+{"IRC username","No d' uzeu IRC"}.
{"IRC Username","No d' uzeu IRC"}.
{"is now known as","est asteure kinoxhou come"}.
+{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","On n' pout nén evoyî des messaedjes d' aroke sol såle. Li pårticipan (~s) a-st evoyî on messaedje d' aroke (~s) ey a stî tapé foû."}.
{"It is not allowed to send private messages","Ci n' est nén permetou d' evoyî des messaedjes privés"}.
{"It is not allowed to send private messages of type \"groupchat\"","C' est nén possibe d' evoyî des messaedjes privés del sôre «groupchat»"}.
{"It is not allowed to send private messages to the conference","On n' pout nén evoyî des messaedjes privés dins cisse conferince ci"}.
+{"Jabber Account Registration","Edjîstraedje di conte Jabber"}.
{"Jabber ID","ID Jabber"}.
{"Jabber ID ~s is invalid","Li Jabber ID ~s n' est nén valide"}.
{"January","djanvî"}.
+{"Join IRC channel","Radjonde canå IRC"}.
{"joins the room","arive sol såle"}.
+{"Join the IRC channel here.","Radjonde li canå IRC droci."}.
+{"Join the IRC channel in this Jabber ID: ~s","Radjonde li canå IRC e cist ID Jabber: ~s"}.
{"July","djulete"}.
{"June","djun"}.
{"Last Activity","Dierinne activité"}.
@@ -130,8 +179,10 @@
{"Listened Ports at ","Pôrts drovous so "}.
{"Listened Ports","Pôrts drovous"}.
{"List of modules to start","Djivêye di modules a-z enonder"}.
+{"List of rooms","Djivêye des såles"}.
{"Low level update script","Sicripe di metaedje a djoû d' bas livea"}.
{"Make participants list public","Rinde publike li djivêye des pårticipants"}.
+{"Make room CAPTCHA protected","Rinde li såle di berdelaedje protedjeye pa CAPTCHA"}.
{"Make room members-only","Rinde li såle di berdelaedje ristrindowe ås mimbes seulmint"}.
{"Make room moderated","Rinde li såle di berdelaedje moderêye"}.
{"Make room password protected","Rinde li såle di berdelaedje protedjeye pa scret"}.
@@ -142,29 +193,41 @@
{"Max # of items to persist","Nombe macsimoms di cayets permanints"}.
{"Max payload size in bytes","Contnou macsimom en octets"}.
{"May","may"}.
+{"Membership is required to enter this room","I fåt esse mimbe po poleur intrer dins cisse såle ci"}.
{"Members:","Mimbes:"}.
+{"Memorize your password, or write it in a paper placed in a safe place. In Jabber there isn't an automated way to recover your password if you forget it.","Rimimbrez vosse sicret, ou scrijhoz l' so on papî ki vos wådroz en ene place bén a hoûte, ca avou Jabber i n' a pont moyén di rapexhî vosse sicret si vos l' rovyîz."}.
{"Memory","Memwere"}.
{"Message body","Coir do messaedje"}.
{"Middle Name","No do mitan"}.
+{"Minimum interval between voice requests (in seconds)","Tins minimom etur deus dmandes di vwès (e segondes)"}.
+{"Moderator","Moderateu"}.
{"Moderator privileges required","I fåt des priviledjes di moderateu"}.
{"moderators only","les moderateus seulmint"}.
+{"Modified modules","Modules di candjîs"}.
{"Module","Module"}.
+{"Modules at ~p","Modules so ~p"}.
{"Modules","Modules"}.
{"Monday","londi"}.
+{"Multicast","Multicast"}.
+{"Multi-User Chat","Berdelaedje a sacwants"}.
{"Name","No"}.
{"Name:","Pitit no:"}.
{"Never","Måy"}.
+{"New Password:","Novea scret:"}.
{"Nickname","Metou no"}.
{"Nickname Registration at ","Edjîstraedje di metou no amon "}.
{"Nickname ~s does not exist in the room","Li metou no ~s n' egzistêye nén dins l' såle"}.
+{"nobody","nolu"}.
{"No body provided for announce message","I n' a nou coir do messaedje po ciste anonce la"}.
{"No Data","Nole dinêye disponibe"}.
{"Node ID","ID d' nuk"}.
{"Node not found","Nuk nén trové"}.
+{"Node ~p","Nuk ~p"}.
{"Nodes","Nuks"}.
{"No limit","Pont d' limite"}.
{"None","Nole"}.
{"No resource provided","Nole rissoûce di dnêye"}.
+{"Not Found","Nén trové"}.
{"Notify subscribers when items are removed from the node","Notifyî åzès abounés cwand des cayets sont oisté foû do nuk"}.
{"Notify subscribers when the node configuration changes","Notifyî åzès abounés cwand l' apontiaedje do nuk candje"}.
{"Notify subscribers when the node is deleted","Notifyî åzès abounés cwand l' nuk est disfacé"}.
@@ -176,10 +239,15 @@
{"Offline Messages:","Messaedjes ki ratindèt:"}.
{"Offline Messages","Messaedjes ki ratindèt"}.
{"OK","'l est bon"}.
+{"Old Password:","Vî scret:"}.
{"Online","Raloyî"}.
{"Online Users:","Uzeus raloyîs:"}.
{"Online Users","Uzeus raloyîs"}.
{"Only deliver notifications to available users","Seulmint evoyî des notifiaedje åzès uzeus disponibes"}.
+{"Only members may query archives of this room","Seulmint les mimbes polèt cweri les årtchives dins cisse såle ci"}.
+{"Only moderators and participants are allowed to change the subject in this room","Seulmint les moderateus et les pårticipants polèt candjî l' sudjet dins cisse såle ci"}.
+{"Only moderators are allowed to change the subject in this room","Seulmint les moderateus polèt candjî l' sudjet dins cisse såle ci"}.
+{"Only moderators can approve voice requests","Seulmint les moderateus polèt aprover des dmandes di vwès"}.
{"Only occupants are allowed to send messages to the conference","Seulmint les prezints polèt evoyî des messaedjes al conferince"}.
{"Only occupants are allowed to send queries to the conference","Seulmint les prezints polèt evoyî des cweraedjes sol conferince"}.
{"Only service administrators are allowed to send service messages","Seulmint les manaedjeus d' siervices polèt evoyî des messaedjes di siervice"}.
@@ -190,31 +258,46 @@
{"Outgoing s2s Connections","Raloyaedjes s2s e rexhowe"}.
{"Owner privileges required","I fåt des priviledjes di prôpietaire"}.
{"Packet","Paket"}.
+{"Participant","Pårticipant"}.
+{"Password ~b","Sicret ~b"}.
{"Password:","Sicret:"}.
{"Password","Sicret"}.
+{"Password Verification:","Acertinaedje do scret:"}.
{"Password Verification","Acertinaedje do scret"}.
{"Path to Dir","Tchimin viè l' ridant"}.
{"Path to File","Tchimin viè l' fitchî"}.
{"Pending","Ratindant"}.
{"Period: ","Termene:"}.
+{"Permanent rooms","Såles tofer la"}.
{"Persist items to storage","Cayets permanints a wårder"}.
{"Ping","Ping"}.
+{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Notez ki ces tchuzes la vont seulmint fé ene copeye di såvrité del båze di dnêyes Mnesia costrûte å dvins do programe. Si vos eployîz ene difoûtrinne båze di dnêyes avou l' module ODBC, vos dvoz fé ene copeye di såvrité del båze SQL da vosse sepårumint."}.
+{"Please specify file name.","Dinez l' no do fitchî."}.
+{"Please specify file size.","Dinez l' grandeu do fitchî."}.
+{"Please, wait for a while before sending new voice request","Ratindez ene miete s' i vs plait divant d' rivoyî ene nouve dimande di vwès"}.
{"Pong","Pong"}.
+{"Port ~b","Pôrt ~b"}.
{"Port","Pôrt"}.
{"Present real Jabber IDs to","Mostrer les vraiys Jabber IDs a"}.
{"private, ","privé, "}.
+{"Protocol","Protocole"}.
{"Publish-Subscribe","Eplaidaedje-abounmint"}.
{"PubSub subscriber request","Dimande d' eplaidaedje-abounmint d' èn abouné"}.
+{"Purge all items when the relevant publisher goes offline","Purdjî tos les cayets cwand l' eplaideu aloyî va foû raloyaedje"}.
{"Queries to the conference members are not allowed in this room","Les cweraedjes des mimbes del conferince ni sont nén permetous dins cisse såle ci"}.
{"RAM and disc copy","Copeye e memwere (RAM) et sol deure plake"}.
{"RAM copy","Copeye e memwere (RAM)"}.
{"Raw","Dinêyes brutes"}.
{"Really delete message of the day?","Voloz vs vormint disfacer l' messaedje do djoû?"}.
{"Recipient is not in the conference room","Li riçuveu n' est nén dins l' såle di conferince"}.
+{"Register a Jabber account","Edjîstrer on conte Jabber"}.
+{"Register","Edjîstrer"}.
+{"Registered nicknames","Metous nos edjistrés"}.
{"Registered Users:","Uzeus edjistrés:"}.
{"Registered Users","Uzeus edjistrés"}.
{"Registration in mod_irc for ","Edjîstraedje dins mod_irc po "}.
{"Remote copy","Copeye å lon"}.
+{"Remove All Offline Messages","Oister tos les messaedjes ki ratindèt"}.
{"Remove","Oister"}.
{"Remove User","Disfacer l' uzeu"}.
{"Replaced by new connection","Replaecî pa on novea raloyaedje"}.
@@ -226,8 +309,11 @@
{"Restore binary backup immediately:","Rapexhî do côp foû d' ene copeye di såvrité binaire:"}.
{"Restore plain text backup immediately:","Rapexhî do côp foû d' ene copeye di såvrité tecse:"}.
{"Restore","Rapexhî"}.
+{"Roles for which Presence is Broadcasted","Roles ki leu prezince est difuzêye"}.
{"Room Configuration","Apontiaedje del såle"}.
{"Room creation is denied by service policy","L' ahivaedje del såle est rfuzé pal politike do siervice"}.
+{"Room description","Discrijhaedje del såle"}.
+{"Room Occupants","Prezints el såle"}.
{"Room title","Tite del såle"}.
{"Roster","Djivêye des soçons"}.
{"Roster groups allowed to subscribe","Pårtaedjîs groupes di soçons k' on s' î pout abouner"}.
@@ -245,6 +331,9 @@
{"Send announcement to all users","Evoyî l' anonce a tos les uzeus"}.
{"Send announcement to all users on all hosts","Evoyî l' anonce a tos les uzeus so tos les lodjoes"}.
{"September","setimbe"}.
+{"Server ~b","Sierveu ~b"}.
+{"Server:","Sierveu:"}.
+{"Server","Sierveu"}.
{"Set message of the day and send to online users","Defini l' messaedje do djoû et l' evoyî åzès uzeus raloyîs"}.
{"Set message of the day on all hosts and send to online users","Defini l' messaedje do djoû so tos les lodjoes et l' evoyî åzès uzeus raloyîs"}.
{"Shared Roster Groups","Pårtaedjîs groupes ezès djivêyes di soçons"}.
@@ -252,7 +341,9 @@
{"Show Ordinary Table","Mostrer crexhince"}.
{"Shut Down Service","Arester siervice"}.
{"~s invites you to the room ~s","~s vos preye sol såle ~s"}.
+{"Some Jabber clients can store your password in the computer, but you should do this only in your personal computer for safety reasons.","Des cliyints Jabber k' i gn a polèt wårder vosse sicret sol copiutrece, mins vos n' duvrîz fé çoula ki sol copiutrece da vosse, po des råjhons di såvrité."}.
{"Specify the access model","Sipecifyî l' modele d' accès"}.
+{"Specify the event message type","Sipecifyî l' sôre do messaedje d' evenmint"}.
{"Specify the publisher model","Dinez l' modele d' eplaideu"}.
{"~s's Offline Messages Queue","messaedjes ki ratindèt el cawêye po ~s"}.
{"Start","Enonder"}.
@@ -273,27 +364,54 @@
{"Subscriber Address","Adresse di l' abouné"}.
{"Subscription","Abounmimnt"}.
{"Sunday","dimegne"}.
+{"That nickname is already in use by another occupant","Li metou no est ddja eployî pa ene ôte sakî sol såle"}.
+{"That nickname is registered by another person","Li metou no est ddja edjîstré pa ene ôte sakî"}.
+{"The CAPTCHA is valid.","Li CAPTCHA est valide."}.
+{"The CAPTCHA verification has failed","Li verifiaedje CAPTCHA a fwait berwete"}.
+{"The collections with which a node is affiliated","Les ramexhnêyes k' on nuk est afiyî avou"}.
{"the password is","li scret est"}.
+{"The password is too weak","li scret est trop flåw"}.
+{"The password of your Jabber account was successfully changed.","Li scret do conte Jabber da vosse a stî candjî comifåt."}.
+{"There was an error changing the password: ","Åk n' a nén stî tot candjant l' sicret: "}.
+{"There was an error creating the account: ","Åk n' a nén stî tot ahivant l' conte: "}.
+{"There was an error deleting the account: ","Åk n' a nén stî tot disfaçant l' conte: "}.
+{"This IP address is blacklisted in ~s","Ciste adresse IP est so ene noere djivêye e ~s"}.
+{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","Pont d' diferince etur les grandes et ptitès letes: «macbeth» est l' minme ki «MacBeth» ou co «Macbeth»"}.
+{"This page allows to create a Jabber account in this Jabber server. Your JID (Jabber IDentifier) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","Cisse pådje permete d' ahiver on conte Jabber so ç' sierveu Jabber ci. Li JID (IDintifieu Jabber) da vosse serè del cogne: noduzeu@sierveu. Lijhoz atintivmint les instruccions po bén rimpli les tchamps."}.
+{"This page allows to unregister a Jabber account in this Jabber server.","Cisse pådje permete di disdjîstrer on conte Jabber so ç' sierveu ci."}.
{"Thursday","djudi"}.
{"Time","Date"}.
{"Time delay","Tårdjaedje"}.
+{"Too many CAPTCHA requests","Pår trop di dmandes CAPTCHA"}.
+{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","I gn a-st avou pår trop (~p) d' otintifiaedjes k' ont fwait berwete vinant di ciste adresse IP la (~s). L' adresse serè disblokêye a ~s UTC"}.
+{"Too many unacked stanzas","Pår trop di messaedjes sins acertinaedje di rçuvaedje"}.
{"To","Po"}.
{"To ~s","Viè ~s"}.
+{"Total rooms","Totå di såles"}.
{"Traffic rate limit is exceeded","Li limite pol volume di trafik a stî passêye"}.
{"Transactions Aborted:","Transaccions arestêyes:"}.
{"Transactions Committed:","Transaccions evoyeyes:"}.
{"Transactions Logged:","Transaccions wårdêyes e djournå:"}.
{"Transactions Restarted:","Transaccions renondêyes:"}.
{"Tuesday","mårdi"}.
+{"Unable to generate a CAPTCHA","Nén moyén di djenerer on CAPTCHA"}.
+{"Unauthorized","Nén otorijhî"}.
+{"Unregister a Jabber account","Disdjîstrer on conte Jabber"}.
+{"Unregister","Disdjîstrer"}.
{"Update message of the day (don't send)","Mete a djoû l' messaedje do djoû (nén l' evoyî)"}.
{"Update message of the day on all hosts (don't send)","Mete a djoû l' messaedje do djoû so tos les lodjoes (nén l' evoyî)"}.
{"Update","Mete a djoû"}.
{"Update plan","Plan d' metaedje a djoû"}.
+{"Update ~p","Metaedje a djoû di ~p"}.
{"Update script","Sicripe di metaedje a djoû"}.
{"Uptime:","Tins dispoy l' enondaedje:"}.
{"Use of STARTTLS required","L' eployaedje di STARTTL est oblidjî"}.
+{"User JID","JID d' l' uzeu"}.
{"User Management","Manaedjaedje des uzeus"}.
+{"Username:","No d' uzeu:"}.
+{"Users are not allowed to register accounts so quickly","Les noveas uzeus n' si polèt nén edjîstrer si raddimint"}.
{"Users Last Activity","Dierinne activité des uzeus"}.
+{"User ~s","Uzeu ~s"}.
{"Users","Uzeus"}.
{"User","Uzeu"}.
{"Validate","Valider"}.
@@ -301,12 +419,22 @@
{"Virtual Hosts","Forveyous sierveus"}.
{"Visitors are not allowed to change their nicknames in this room","Les viziteus èn polèt nén candjî leus metous no po ç' såle ci"}.
{"Visitors are not allowed to send messages to all occupants","Les viziteus n' polèt nén evoyî des messaedjes a tos les prezints"}.
+{"Visitor","Viziteu"}.
+{"Voice request","Dimande di vwès"}.
+{"Voice requests are disabled in this conference","Les dmandes di vwès sont dismetowes e cisse conferince ci"}.
{"Wednesday","mierkidi"}.
{"When to send the last published item","Cwand evoyî l' dierin cayet eplaidî"}.
{"Whether to allow subscriptions","Si on permete les abounmints"}.
+{"You can later change your password using a Jabber client.","Vos ploz candjî vosse sicret pus tård avou on cliyint Jabber."}.
{"You have been banned from this room","Vos avoz stî bani di cisse såle ci"}.
{"You must fill in field \"Nickname\" in the form","Vos dvoz rimpli l' tchamp «Metou no» dins l' formiulaire"}.
+{"You need a client that supports x:data and CAPTCHA to register","Vos avoz mezåjhe d' on cliyint ki sopoite x:data eyet CAPTCHA po vs edjîstrer"}.
+{"You need a client that supports x:data to register the nickname","Vos avoz mezåjhe d' on cliyint ki sopoite x:data po-z edjîstrer l' metou no"}.
{"You need an x:data capable client to configure mod_irc settings","Vos avoz mezåjhe d' on cliyint ki sopoite x:data po candjî ls apontiaedjes di mod_irc"}.
{"You need an x:data capable client to configure room","I vs fåt on cliyint ki sopoite x:data por vos poleur apontyî l' såle"}.
{"You need an x:data capable client to search","Vos avoz mezåjhe d' on cliyint ki sopoite x:data po fé on cweraedje"}.
+{"Your active privacy list has denied the routing of this stanza.","Vosse djivêye di privaceye active a rfuzé l' evoyaedje di ç' messaedje ci."}.
{"Your contact offline message queue is full. The message has been discarded.","Li cawêye di messaedjes e môde disraloyî di vosse soçon est plinne. Li messaedje a stî tapé å diale."}.
+{"Your Jabber account was successfully created.","Li conte Jabber da vosse a stî ahivé comifåt."}.
+{"Your Jabber account was successfully deleted.","Li conte Jabber da vosse a stî disfacé comifåt."}.
+{"Your messages to ~s are being blocked. To unblock them, visit ~s","Vos messaedjes po ~s sont blokés. Po les disbloker, alez vey ~s"}.
diff --git a/priv/msgs/zh.msg b/priv/msgs/zh.msg
index 41309b16..84ac7691 100644
--- a/priv/msgs/zh.msg
+++ b/priv/msgs/zh.msg
@@ -1,4 +1,5 @@
%% -*- coding: latin-1 -*-
+{"Accept","接受"}.
{"Access Configuration","访问配置"}.
{"Access Control List Configuration","访问控制列表(ACL)配置"}.
{"Access control lists","访问控制列表(ACL)"}.
@@ -7,7 +8,7 @@
{"Access rules","访问规则"}.
{"Access Rules","访问规则"}.
{"Action on user","对用户的动作"}.
-{"Add Jabber ID","添加 Jabber ID"}.
+{"Add Jabber ID","添加Jabber ID"}.
{"Add New","添加新用户"}.
{"Add User","添加用户"}.
{"Administration of ","管理"}.
@@ -15,7 +16,7 @@
{"Administrator privileges required","需要管理员权限"}.
{"A friendly name for the node","该节点的友好名称"}.
{"All activity","所有活动"}.
-{"Allow this Jabber ID to subscribe to this pubsub node?","允许该 Jabber ID 订阅该 pubsub 节点?"}.
+{"Allow this Jabber ID to subscribe to this pubsub node?","允许该Jabber ID订阅该pubsub节点?"}.
{"Allow users to change the subject","允许用户更改主题"}.
{"Allow users to query other users","允许用户查询其它用户"}.
{"Allow users to send invites","允许用户发送邀请"}.
@@ -31,6 +32,7 @@
{"April","四月"}.
{"August","八月"}.
{"Backup Management","备份管理"}.
+{"Backup of ~p","~p的备份"}.
{"Backup to File at ","备份文件位于"}.
{"Backup","备份"}.
{"Bad format","格式错误"}.
@@ -52,12 +54,13 @@
{"City","城市"}.
{"Commands","命令"}.
{"Conference room does not exist","会议室不存在"}.
-{"Configuration of room ~s","房间 ~s 的配置 "}.
+{"Configuration of room ~s","房间~s的配置 "}.
{"Configuration","配置"}.
{"Connected Resources:","已连接资源:"}.
{"Connections parameters","连接参数"}.
{"Country","国家"}.
-{"CPU Time:","CPU 时间:"}.
+{"CPU Time:","CPU时间:"}.
+{"Database Tables at ~p","位于~p的数据库表"}.
{"Database Tables Configuration at ","数据库表格配置位于"}.
{"Database","数据库"}.
{"December","十二月"}.
@@ -71,42 +74,46 @@
{"Description:","描述:"}.
{"Disc only copy","仅磁盘复制"}.
{"Displayed Groups:","已显示的组:"}.
-{"Don't tell your password to anybody, not even the administrators of the Jabber server.","不要将密码告诉任何人, 就算是 Jabber 服务器的管理员也不可以."}.
+{"Don't tell your password to anybody, not even the administrators of the Jabber server.","不要将密码告诉任何人, 就算是Jabber服务器的管理员也不可以."}.
{"Dump Backup to Text File at ","转储备份到文本文件于"}.
{"Dump to Text File","转储到文本文件"}.
{"Edit Properties","编辑属性"}.
{"Either approve or decline the voice request.","接受或拒绝声音请求"}.
{"ejabberd IRC module","ejabberd IRC 模块"}.
{"ejabberd MUC module","ejabberd MUC 模块"}.
+{"ejabberd Multicast service","ejabberd多重映射服务"}.
{"ejabberd Publish-Subscribe module","ejabberd 发行-订阅模块"}.
{"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 字节流模块"}.
-{"ejabberd vCard module","ejabberd vCard 模块"}.
-{"ejabberd Web Admin","ejabberd 网页管理"}.
+{"ejabberd vCard module","ejabberd vCard模块"}.
+{"ejabberd Web Admin","ejabberd网页管理"}.
{"Elements","元素"}.
{"Email","电子邮件"}.
+{"Empty Rooms","空房间"}.
{"Enable logging","启用服务器端聊天记录"}.
-{"Encoding for server ~b","服务器 ~b 的编码"}.
+{"Enable message archiving","启用消息归档"}.
+{"Encoding for server ~b","服务器~b的编码"}.
{"End User Session","结束用户会话"}.
{"Enter list of {Module, [Options]}","请输入{模块, [选项]}列表"}.
{"Enter nickname you want to register","请输入您想要注册的昵称"}.
{"Enter path to backup file","请输入备份文件的路径"}.
-{"Enter path to jabberd14 spool dir","请输入 jabberd14 spool 目录的路径"}.
+{"Enter path to jabberd14 spool dir","请输入jabberd14 spool目录的路径"}.
{"Enter path to jabberd14 spool file","请输入 jabberd14 spool 文件的路径"}.
{"Enter path to text file","请输入文本文件的路径"}.
{"Enter the text you see","请输入您所看到的文本"}.
{"Enter username and encodings you wish to use for connecting to IRC servers. Press 'Next' to get more fields to fill in. Press 'Complete' to save settings.","请输入您想使用的用来连接到 IRC 服务器的用户名和编码. 按 '下一步' 获取更多待填字段. 按 '完成' 保存设置."}.
{"Enter username, encodings, ports and passwords you wish to use for connecting to IRC servers","请输入您想使用的用来连接到IRC服务器的用户名, 编码, 端口和密码."}.
-{"Erlang Jabber Server","Erlang Jabber 服务器"}.
+{"Erlang Jabber Server","Erlang Jabber服务器"}.
{"Error","错误"}.
{"Example: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}].","例如: [{\"irc.lucky.net\", \"koi8-r\"}, 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}]."}.
-{"Exclude Jabber IDs from CAPTCHA challenge","从验证码挑战中排除 Jabber ID"}.
+{"Exclude Jabber IDs from CAPTCHA challenge","从验证码挑战中排除Jabber ID"}.
+{"Export all tables as SQL queries to a file:","将所有表以SQL查询语句导出到文件:"}.
{"Export data of all users in the server to PIEFXIS files (XEP-0227):","将服务器上所有用户的数据导出到 PIEFXIS 文件 (XEP-0227):"}.
{"Export data of users in a host to PIEFXIS files (XEP-0227):","将某主机的用户数据导出到 PIEFXIS 文件 (XEP-0227):"}.
{"Failed to extract JID from your voice request approval","无法从你的声音请求确认信息中提取JID"}.
{"Family Name","姓氏"}.
{"February","二月"}.
-{"Fill in fields to search for any matching Jabber User","填充字段以搜索任何匹配的 Jabber 用户"}.
-{"Fill in the form to search for any matching Jabber User (Add * to the end of field to match substring)","填充表单以搜索任何匹配的 Jabber 用户(在字段末添加*来匹配子串)"}.
+{"Fill in fields to search for any matching Jabber User","填充字段以搜索任何匹配的Jabber用户"}.
+{"Fill in the form to search for any matching Jabber User (Add * to the end of field to match substring)","填充表单以搜索任何匹配的Jabber用户(在字段末添加*来匹配子串)"}.
{"Friday","星期五"}.
{"From ~s","来自~s"}.
{"From","从"}.
@@ -131,35 +138,37 @@
{"Import Directory","导入目录"}.
{"Import File","导入文件"}.
{"Import user data from jabberd14 spool file:","从 jabberd14 Spool 文件导入用户数据:"}.
-{"Import User from File at ","导入用户的文件位于 "}.
+{"Import User from File at ","导入用户的文件位于"}.
{"Import users data from a PIEFXIS file (XEP-0227):","从 PIEFXIS 文件 (XEP-0227) 导入用户数据:"}.
-{"Import users data from jabberd14 spool directory:","从 jabberd14 Spool 目录导入用户数据:"}.
-{"Import Users from Dir at ","导入用户的目录位于 "}.
+{"Import users data from jabberd14 spool directory:","从jabberd14 Spool目录导入用户数据:"}.
+{"Import Users from Dir at ","导入用户的目录位于"}.
{"Import Users From jabberd14 Spool Files","从 jabberd14 Spool 文件导入用户"}.
{"Improper message type","不恰当的消息类型"}.
+{"Incoming s2s Connections:","入站 s2s 连接:"}.
{"Incorrect password","密码不正确"}.
{"Invalid affiliation: ~s","无效加入: ~s"}.
{"Invalid role: ~s","无效角色: ~s"}.
-{"IP addresses","IP 地址"}.
+{"IP addresses","IP地址"}.
{"IP","IP"}.
-{"IRC channel (don't put the first #)","IRC 频道 (不要输入第一个#号)"}.
-{"IRC server","IRC 服务器"}.
-{"IRC settings","IRC 设置"}.
-{"IRC Transport","IRC 传输"}.
-{"IRC username","IRC 用户名"}.
-{"IRC Username","IRC 用户名"}.
+{"IRC channel (don't put the first #)","IRC频道 (不要输入第一个#号)"}.
+{"IRC server","IRC服务器"}.
+{"IRC settings","IRC设置"}.
+{"IRC Transport","IRC传输"}.
+{"IRC username","IRC用户名"}.
+{"IRC Username","IRC用户名"}.
{"is now known as","现在称呼为"}.
+{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","不允许将错误消息发送到该房间. 参与者(~s)已发送过一条消息(~s)并已被踢出房间"}.
{"It is not allowed to send private messages of type \"groupchat\"","\"群组聊天\"类型不允许发送私聊消息"}.
{"It is not allowed to send private messages to the conference","不允许向会议发送私聊消息"}.
{"It is not allowed to send private messages","不可以发送私聊消息"}.
-{"Jabber Account Registration","Jabber 帐户注册"}.
+{"Jabber Account Registration","Jabber帐户注册"}.
{"Jabber ID","Jabber ID"}.
{"Jabber ID ~s is invalid","Jabber ID ~s 无效"}.
{"January","一月"}.
-{"Join IRC channel","加入 IRC 频道"}.
+{"Join IRC channel","加入IRC频道"}.
{"joins the room","加入房间"}.
-{"Join the IRC channel here.","在这里加入 IRC 频道."}.
-{"Join the IRC channel in this Jabber ID: ~s","用此 Jabber ID: ~s 加入 IRC 频道"}.
+{"Join the IRC channel here.","在这里加入IRC频道."}.
+{"Join the IRC channel in this Jabber ID: ~s","用此Jabber ID ~s加入IRC频道"}.
{"July","七月"}.
{"June","六月"}.
{"Last Activity","上次活动"}.
@@ -167,9 +176,10 @@
{"Last month","上个月"}.
{"Last year","上一年"}.
{"leaves the room","离开房间"}.
-{"Listened Ports at ","监听的端口位于 "}.
+{"Listened Ports at ","监听的端口位于"}.
{"Listened Ports","被监听的端口"}.
{"List of modules to start","要启动的模块列表"}.
+{"List of rooms","房间列表"}.
{"Low level update script","低级别更新脚本"}.
{"Make participants list public","公开参与人列表"}.
{"Make room CAPTCHA protected","保护房间验证码"}.
@@ -185,29 +195,34 @@
{"May","五月"}.
{"Membership is required to enter this room","进入此房间需要会员身份"}.
{"Members:","会员:"}.
-{"Memorize your password, or write it in a paper placed in a safe place. In Jabber there isn't an automated way to recover your password if you forget it.","记住你的密码, 或将其记到纸上并放于安全位置. 如果你忘记了密码, Jabber 也没有自动恢复密码的方式."}.
+{"Memorize your password, or write it in a paper placed in a safe place. In Jabber there isn't an automated way to recover your password if you forget it.","记住你的密码, 或将其记到纸上并放于安全位置. 如果你忘记了密码, Jabber也没有自动恢复密码的方式."}.
{"Memory","内存"}.
{"Message body","消息主体"}.
{"Middle Name","中间名"}.
{"Minimum interval between voice requests (in seconds)","声音请求的最小间隔(以秒为单位)"}.
{"Moderator privileges required","需要主持人权限"}.
{"moderators only","仅主持人"}.
+{"Moderator","主持人"}.
{"Modified modules","被修改模块"}.
+{"Modules at ~p","位于~p的模块"}.
{"Modules","模块"}.
{"Module","模块"}.
{"Monday","星期一"}.
+{"Multicast","多重映射"}.
+{"Multi-User Chat","多用户聊天"}.
{"Name:","姓名:"}.
{"Name","姓名"}.
{"Never","从未"}.
-{"New Password:","新密码: "}.
-{"Nickname Registration at ","昵称注册于 "}.
-{"Nickname ~s does not exist in the room","昵称 ~s 不在该房间"}.
+{"New Password:","新密码:"}.
+{"Nickname Registration at ","昵称注册于"}.
+{"Nickname ~s does not exist in the room","昵称~s不在该房间"}.
{"Nickname","昵称"}.
{"No body provided for announce message","通知消息无正文内容"}.
{"nobody","没有人"}.
{"No Data","没有数据"}.
-{"Node ID","节点 ID"}.
+{"Node ID","节点ID"}.
{"Node not found","没有找到节点"}.
+{"Node ~p","节点~p"}.
{"Nodes","节点"}.
{"No limit","不限"}.
{"None","无"}.
@@ -229,6 +244,7 @@
{"Online Users","在线用户"}.
{"Online","在线"}.
{"Only deliver notifications to available users","仅将通知发送给可发送的用户"}.
+{"Only members may query archives of this room","只有会员可以查询本房间的存档"}.
{"Only moderators and participants are allowed to change the subject in this room","只有主持人和参与人可以在此房间里更改主题"}.
{"Only moderators are allowed to change the subject in this room","只有主持人可以在此房间里更改主题"}.
{"Only moderators can approve voice requests","仅主持人能确认声音请求"}.
@@ -242,7 +258,8 @@
{"Outgoing s2s Connections","出站 s2s 连接"}.
{"Owner privileges required","需要持有人权限"}.
{"Packet","数据包"}.
-{"Password ~b","~b 的密码"}.
+{"Participant","参与人"}.
+{"Password ~b","~b的密码"}.
{"Password Verification:","密码确认:"}.
{"Password Verification","确认密码"}.
{"Password:","密码:"}.
@@ -251,18 +268,21 @@
{"Path to File","文件路径"}.
{"Pending","挂起"}.
{"Period: ","持续时间: "}.
+{"Permanent rooms","永久房间"}.
{"Persist items to storage","持久化内容条目"}.
{"Ping","Ping"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","注意:这些选项仅将备份内置的 Mnesia 数据库. 如果您正在使用 ODBC 模块, 您还需要分别备份您的数据库."}.
+{"Please specify file name.","请指定文件名称."}.
+{"Please specify file size.","请指定文件大小."}.
{"Please, wait for a while before sending new voice request","请稍后再发送新的声音请求"}.
{"Pong","Pong"}.
-{"Port ~b","~b 的端口"}.
+{"Port ~b","~b的端口"}.
{"Port","端口"}.
-{"Present real Jabber IDs to","将真实 Jabber ID 显示给"}.
+{"Present real Jabber IDs to","将真实Jabber ID显示给"}.
{"private, ","保密, "}.
{"Protocol","协议"}.
{"Publish-Subscribe","发行-订阅"}.
-{"PubSub subscriber request","PubSub 订阅人请求"}.
+{"PubSub subscriber request","PubSub订阅人请求"}.
{"Purge all items when the relevant publisher goes offline","相关发布人离线后清除所有选项"}.
{"Queries to the conference members are not allowed in this room","本房间不可以查询会议成员信息"}.
{"RAM and disc copy","内存与磁盘复制"}.
@@ -270,7 +290,8 @@
{"Raw","原始格式"}.
{"Really delete message of the day?","确实要删除每日消息吗?"}.
{"Recipient is not in the conference room","接收人不在会议室"}.
-{"Register a Jabber account","注册 Jabber 帐户"}.
+{"Register a Jabber account","注册Jabber帐户"}.
+{"Registered nicknames","注册的昵称"}.
{"Registered Users:","注册用户:"}.
{"Registered Users","注册用户"}.
{"Register","注册"}.
@@ -288,45 +309,47 @@
{"Restore binary backup immediately:","立即恢复二进制备份:"}.
{"Restore plain text backup immediately:","立即恢复普通文本备份:"}.
{"Restore","恢复"}.
+{"Roles for which Presence is Broadcasted","广播存在性的角色"}.
{"Room Configuration","房间配置"}.
{"Room creation is denied by service policy","创建房间被服务策略拒绝"}.
{"Room description","房间描述"}.
{"Room Occupants","房间人数"}.
{"Room title","房间标题"}.
{"Roster groups allowed to subscribe","允许订阅的花名册组"}.
-{"Roster of ","花名册属于 "}.
+{"Roster of ","花名册属于"}.
{"Roster size","花名册大小"}.
{"Roster","花名册"}.
{"RPC Call Error","RPC 调用错误"}.
{"Running Nodes","运行中的节点"}.
-{"~s access rule configuration","~s 访问规则配置"}.
+{"~s access rule configuration","~s访问规则配置"}.
{"Saturday","星期六"}.
{"Script check","脚本检查"}.
{"Search Results for ","搜索结果属于关键词 "}.
-{"Search users in ","搜索用户于 "}.
+{"Search users in ","搜索用户于"}.
{"Send announcement to all online users on all hosts","发送通知给所有主机的在线用户"}.
{"Send announcement to all online users","发送通知给所有在线用户"}.
{"Send announcement to all users on all hosts","发送通知给所有主机上的所有用户"}.
{"Send announcement to all users","发送通知给所有用户"}.
{"September","九月"}.
-{"Server ~b","服务器 ~b"}.
+{"Server ~b","服务器~b"}.
{"Server:","服务器:"}.
+{"Server","服务器"}.
{"Set message of the day and send to online users","设定每日消息并发送给所有在线用户"}.
{"Set message of the day on all hosts and send to online users","设置所有主机上的每日消息并发送给在线用户"}.
{"Shared Roster Groups","共享的花名册组群"}.
{"Show Integral Table","显示完整列表"}.
{"Show Ordinary Table","显示普通列表"}.
{"Shut Down Service","关闭服务"}.
-{"~s invites you to the room ~s","~s 邀请你到 ~s 房间"}.
+{"~s invites you to the room ~s","~s邀请你到房间~s"}.
{"Some Jabber clients can store your password in the computer, but you should do this only in your personal computer for safety reasons.","某些 Jabber 客户端可以在你的计算机里存储密码. 请仅在你确认你的计算机安全的情况下使用该功能."}.
{"Specify the access model","指定访问范例"}.
{"Specify the event message type","指定事件消息类型"}.
{"Specify the publisher model","指定发布人范例"}.
-{"~s's Offline Messages Queue","~s 的离线消息队列"}.
+{"~s's Offline Messages Queue","~s的离线消息队列"}.
{"Start Modules at ","要启动的模块位于 "}.
{"Start Modules","启动模块"}.
{"Start","开始"}.
-{"Statistics of ~p","~p 的统计"}.
+{"Statistics of ~p","~p的统计"}.
{"Statistics","统计"}.
{"Stop Modules at ","要停止的模块位于 "}.
{"Stop Modules","停止模块"}.
@@ -348,18 +371,22 @@
{"The collections with which a node is affiliated","加入结点的集合"}.
{"The password is too weak","密码强度太弱"}.
{"the password is","密码是"}.
-{"The password of your Jabber account was successfully changed.","你的 Jabber 帐户密码已成功更新."}.
+{"The password of your Jabber account was successfully changed.","你的Jabber帐户密码已成功更新."}.
{"There was an error changing the password: ","修改密码出错: "}.
{"There was an error creating the account: ","帐户创建出错: "}.
{"There was an error deleting the account: ","帐户删除失败: "}.
+{"This IP address is blacklisted in ~s","此IP地址在~s中已被列入黑名单"}.
{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","此处不区分大小写: macbeth 与 MacBeth 和 Macbeth 是一样的."}.
-{"This page allows to create a Jabber account in this Jabber server. Your JID (Jabber IDentifier) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","本页面允许在此服务器上创建 Jabber 帐户. 你的 JID (Jabber ID) 的形式如下: 用户名@服务器. 请仔细阅读说明并正确填写相应字段."}.
-{"This page allows to unregister a Jabber account in this Jabber server.","此页面允许在此 Jabber 服务器上注销 Jabber 帐户"}.
+{"This page allows to create a Jabber account in this Jabber server. Your JID (Jabber IDentifier) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","本页面允许在此服务器上创建Jabber帐户. 你的JID (Jabber ID) 的形式如下: 用户名@服务器. 请仔细阅读说明并正确填写相应字段."}.
+{"This page allows to unregister a Jabber account in this Jabber server.","此页面允许在此Jabber服务器上注销Jabber帐户"}.
{"Thursday","星期四"}.
{"Time delay","时间延迟"}.
{"Time","时间"}.
{"Too many CAPTCHA requests","验证码请求太多"}.
+{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","来自IP地址(~p)的(~s)失败认证太多. 该地址将在UTC时间~s被禁用."}.
+{"Too many unacked stanzas","未被确认的节太多"}.
{"To ~s","发送给~s"}.
+{"Total rooms","所有房间"}.
{"To","到"}.
{"Traffic rate limit is exceeded","已经超过传输率限制"}.
{"Transactions Aborted:","取消的事务:"}.
@@ -369,42 +396,45 @@
{"Tuesday","星期二"}.
{"Unable to generate a CAPTCHA","无法生成验证码"}.
{"Unauthorized","未认证的"}.
-{"Unregister a Jabber account","注销 Jabber 帐户"}.
+{"Unregister a Jabber account","注销Jabber帐户"}.
{"Unregister","取消注册"}.
{"Update message of the day (don't send)","更新每日消息(不发送)"}.
{"Update message of the day on all hosts (don't send)","更新所有主机上的每日消息(不发送)"}.
{"Update plan","更新计划"}.
+{"Update ~p","更新~p"}.
{"Update script","更新脚本"}.
{"Update","更新"}.
{"Uptime:","正常运行时间:"}.
{"Use of STARTTLS required","要求使用 STARTTLS"}.
-{"User JID","用户 JID"}.
+{"User JID","用户JID"}.
{"User Management","用户管理"}.
{"Username:","用户名:"}.
{"Users are not allowed to register accounts so quickly","不允许用户太频繁地注册帐户"}.
{"Users Last Activity","用户上次活动"}.
{"Users","用户"}.
+{"User ~s","用户~s"}.
{"User","用户"}.
{"Validate","确认"}.
-{"vCard User Search","vCard 用户搜索"}.
+{"vCard User Search","vCard用户搜索"}.
{"Virtual Hosts","虚拟主机"}.
{"Visitors are not allowed to change their nicknames in this room","此房间不允许用户更改昵称"}.
{"Visitors are not allowed to send messages to all occupants","不允许访客给所有占有者发送消息"}.
+{"Visitor","访客"}.
{"Voice requests are disabled in this conference","该会议的声音请求以被禁用"}.
{"Voice request","声音请求"}.
{"Wednesday","星期三"}.
{"When to send the last published item","何时发送最新发布的内容条目"}.
{"Whether to allow subscriptions","是否允许订阅"}.
-{"You can later change your password using a Jabber client.","你可以稍后用 Jabber 客户端修改你的密码."}.
+{"You can later change your password using a Jabber client.","你可以稍后用Jabber客户端修改你的密码."}.
{"You have been banned from this room","您已被禁止进入该房间"}.
{"You must fill in field \"Nickname\" in the form","您必须填充表单中\"昵称\"项"}.
{"You need a client that supports x:data and CAPTCHA to register","您需要一个支持 x:data 和验证码的客户端进行注册"}.
{"You need a client that supports x:data to register the nickname","您需要一个支持 x:data 的客户端来注册昵称"}.
-{"You need an x:data capable client to configure mod_irc settings","您需要一个兼容 x:data 的客户端来配置 mod_irc 设置"}.
+{"You need an x:data capable client to configure mod_irc settings","您需要一个兼容 x:data 的客户端来配置mod_irc设置"}.
{"You need an x:data capable client to configure room","您需要一个兼容 x:data 的客户端来配置房间"}.
{"You need an x:data capable client to search","您需要一个兼容 x:data 的客户端来搜索"}.
{"Your active privacy list has denied the routing of this stanza.","你的活跃私聊列表拒绝了在此房间进行路由分发."}.
{"Your contact offline message queue is full. The message has been discarded.","您的联系人离线消息队列已满. 消息已被丢弃"}.
-{"Your Jabber account was successfully created.","你的 Jabber 帐户已成功创建."}.
+{"Your Jabber account was successfully created.","你的Jabber帐户已成功创建."}.
{"Your Jabber account was successfully deleted.","你的 Jabber 帐户已成功删除."}.
-{"Your messages to ~s are being blocked. To unblock them, visit ~s","您发送给 ~s 的消息已被阻止. 要解除阻止, 请访问 ~s"}.
+{"Your messages to ~s are being blocked. To unblock them, visit ~s","您发送给~s的消息已被阻止. 要解除阻止, 请访问 ~s"}.
diff --git a/rebar.config b/rebar.config
index 05d2ac1b..d006dc64 100644
--- a/rebar.config
+++ b/rebar.config
@@ -8,48 +8,71 @@
%%%-------------------------------------------------------------------
{deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.0.2"}}},
- {p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.3"}}},
+ {p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.4"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.2"}}},
- {p1_tls, ".*", {git, "https://github.com/processone/tls", {tag, "1.0.0"}}},
- {p1_stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.1"}}},
- {p1_xml, ".*", {git, "https://github.com/processone/xml", {tag, "1.1.2"}}},
- {p1_stun, ".*", {git, "https://github.com/processone/stun", {tag, "0.9.1"}}},
- {esip, ".*", {git, "https://github.com/processone/p1_sip", "1.0.1"}},
- {p1_yaml, ".*", {git, "https://github.com/processone/p1_yaml", {tag, "1.0.1"}}},
- {jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.5"}}},
- {oauth2, ".*", {git, "https://github.com/kivra/oauth2", "8d129fbf8866930b4ffa6dd84e65bd2b32b9acb8"}},
- {xmlrpc, ".*", {git, "https://github.com/rds13/xmlrpc", {tag, "1.15"}}},
- {if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/mysql", {tag, "1.0.0"}}}},
- {if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/pgsql", {tag, "1.0.0"}}}},
- {if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/alexeyr/erlang-sqlite3", "cbc3505f7a131254265d3ef56191b2581b8cc172"}}},
- {if_var_true, pam, {p1_pam, ".*", {git, "https://github.com/processone/epam", {tag, "1.0.0"}}}},
- {if_var_true, zlib, {p1_zlib, ".*", {git, "https://github.com/processone/zlib", {tag, "1.0.0"}}}},
- {if_var_true, riak, {hamcrest, ".*", {git, "https://github.com/hyperthunk/hamcrest-erlang", "908a24fda4a46776a5135db60ca071e3d783f9f6"}}}, % for riak_pb-2.1.0.7
- {if_var_true, riak, {riakc, ".*", {git, "https://github.com/basho/riak-erlang-client", "527722d12d0433b837cdb92a60900c2cb5df8942"}}},
- {if_var_true, elixir, {elixir, ".*", {git, "https://github.com/elixir-lang/elixir", {tag, "v1.1.0"}}}},
- {if_var_true, elixir, {rebar_elixir_plugin, ".*", {git, "https://github.com/processone/rebar_elixir_plugin", "0.1.0"}}},
- {if_var_true, iconv, {p1_iconv, ".*", {git, "https://github.com/processone/eiconv", {tag, "0.9.0"}}}},
- {if_var_true, tools, {meck, "0.8.2", {git, "https://github.com/eproxus/meck", {tag, "0.8.2"}}}},
- {if_var_true, redis, {eredis, ".*", {git, "https://github.com/wooga/eredis", {tag, "v1.0.8"}}}}]}.
+ {fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.1"}}},
+ {stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.3"}}},
+ {fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.3"}}},
+ {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.1"}}},
+ {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.2"}}},
+ {fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.3"}}},
+ {jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.7"}}},
+ {p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}},
+ {p1_xmlrpc, ".*", {git, "https://github.com/processone/p1_xmlrpc", {tag, "1.15.1"}}},
+ {luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}},
+ {if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
+ {tag, "1.0.1"}}}},
+ {if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
+ {tag, "1.1.0"}}}},
+ {if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
+ {tag, "1.1.5"}}}},
+ {if_var_true, pam, {p1_pam, ".*", {git, "https://github.com/processone/epam",
+ {tag, "1.0.0"}}}},
+ {if_var_true, zlib, {ezlib, ".*", {git, "https://github.com/processone/ezlib",
+ {tag, "1.0.1"}}}},
+ {if_var_true, riak, {riakc, ".*", {git, "https://github.com/basho/riak-erlang-client",
+ "527722d12d0433b837cdb92a60900c2cb5df8942"}}},
+ %% Forces correct dependency for riakc and allow using newer meck version)
+ {if_var_true, riak, {hamcrest, ".*", {git, "https://github.com/hyperthunk/hamcrest-erlang",
+ "908a24fda4a46776a5135db60ca071e3d783f9f6"}}}, % for riak_pb-2.1.0.7
+ {if_var_true, riak, {protobuffs, ".*", {git, "https://github.com/basho/erlang_protobuffs",
+ "6e7fc924506e2dc166a6170e580ce1d95ebbd5bd"}}}, % for riak_pb-2.1.0.7 with correct meck dependency
+ %% Elixir support, needed to run tests
+ {if_var_true, elixir, {elixir, ".*", {git, "https://github.com/elixir-lang/elixir",
+ {tag, "v1.1.1"}}}},
+ %% TODO: When modules are fully migrated to new structure and mix, we will not need anymore rebar_elixir_plugin
+ {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.0"}}}},
+ {if_var_true, tools, {meck, "0.8.*", {git, "https://github.com/eproxus/meck",
+ {tag, "0.8.4"}}}},
+ {if_var_true, tools, {moka, ".*", {git, "https://github.com/processone/moka.git",
+ {tag, "1.0.5b"}}}},
+ {if_var_true, redis, {eredis, ".*", {git, "https://github.com/wooga/eredis",
+ {tag, "v1.0.8"}}}}]}.
{if_var_true, latest_deps,
- {floating_deps, [p1_logger,
- cache_tab,
- p1_tls,
- p1_stringprep,
- p1_xml,
+ {floating_deps, [cache_tab,
+ fast_tls,
+ stringprep,
+ fast_xml,
esip,
- p1_stun,
- p1_yaml,
+ luerl,
+ stun,
+ fast_yaml,
p1_utils,
p1_mysql,
p1_pgsql,
p1_pam,
- p1_zlib,
- p1_iconv]}}.
+ ezlib,
+ iconv]}}.
+
+{erl_first_files, ["src/ejabberd_config.erl"]}.
{erl_opts, [nowarn_deprecated_function,
{if_var_false, debug, no_debug_info},
+ {if_var_true, debug, debug_info},
{if_var_true, roster_gateway_workaround, {d, 'ROSTER_GATWAY_WORKAROUND'}},
{if_var_match, db_type, mssql, {d, 'mssql'}},
{if_var_true, erlang_deprecated_types, {d, 'ERL_DEPRECATED_TYPES'}},
@@ -64,7 +87,10 @@
{if_var_true, elixir, rebar_elixir_compiler},
{if_var_true, elixir, rebar_exunit}]}.
-{lib_dirs, [{if_var_true, elixir, "deps/elixir/lib"}]}.
+{if_var_true, elixir,
+ {lib_dirs, ["deps/elixir/lib"]}}.
+{if_var_true, elixir,
+ {src_dirs, ["include"]}}.
{sub_dirs, ["rel"]}.
@@ -91,15 +117,23 @@
{eunit_compile_opts, [{i, "tools"}]}.
-{post_hook_configure, [{"p1_tls", []},
- {"p1_stringprep", []},
- {"p1_yaml", []},
+{cover_enabled, true}.
+{cover_export_enabled, true}.
+
+{post_hook_configure, [{"fast_tls", []},
+ {"stringprep", []},
+ {"fast_yaml", []},
{"esip", []},
- {"p1_xml", [{if_var_true, full_xml, "--enable-full-xml"}]},
+ {"fast_xml", [{if_var_true, full_xml, "--enable-full-xml"}]},
{if_var_true, pam, {"p1_pam", []}},
- {if_var_true, zlib, {"p1_zlib", []}},
- {if_var_true, iconv, {"p1_iconv", []}}]}.
+ {if_var_true, zlib, {"ezlib", []}},
+ {if_var_true, iconv, {"iconv", []}}]}.
{port_env, [{"CFLAGS", "-g -O2 -Wall"}]}.
{port_specs, [{"priv/lib/jid.so", ["c_src/jid.c"]}]}.
+
+%% Local Variables:
+%% mode: erlang
+%% End:
+%% vim: set filetype=erlang tabstop=8:
diff --git a/rebar.config.script b/rebar.config.script
index a7120b12..90928420 100644
--- a/rebar.config.script
+++ b/rebar.config.script
@@ -7,6 +7,20 @@
%%% Created : 1 May 2013 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
+ModCfg0 = fun(F, Cfg, [Key|Tail], Op, Default) ->
+ {OldVal,PartCfg} = case lists:keytake(Key, 1, Cfg) of
+ {value, {_, V1}, V2} -> {V1, V2};
+ false -> {if Tail == [] -> Default; true -> [] end, Cfg}
+ end,
+ case Tail of
+ [] ->
+ [{Key, Op(OldVal)} | PartCfg];
+ _ ->
+ [{Key, F(F, OldVal, Tail, Op, Default)} | PartCfg]
+ end
+ end,
+ModCfg = fun(Cfg, Keys, Op, Default) -> ModCfg0(ModCfg0, Cfg, Keys, Op, Default) end.
+
Cfg = case file:consult(filename:join(filename:dirname(SCRIPT), "vars.config")) of
{ok, Terms} ->
Terms;
@@ -107,9 +121,20 @@ Conf5 = case lists:keytake(floating_deps, 1, Conf3) of
Conf3
end,
+%% When running Travis test, upload test coverage result to coveralls:
+Conf6 = case os:getenv("TRAVIS") of
+ "true" ->
+ JobId = os:getenv("TRAVIS_JOB_ID"),
+ CfgTemp = ModCfg(Conf5, [deps], fun(V) -> [{coveralls, ".*", {git, "https://github.com/markusn/coveralls-erl.git", "master"}}|V] end, []),
+ ModCfg(CfgTemp, [post_hooks], fun(V) -> V ++ [{ct, "echo '\n%%! -pa ebin/ deps/coveralls/ebin\nmain(_)->{ok,F}=file:open(\"erlang.json\",[write]),io:fwrite(F,\"~s\",[coveralls:convert_file(\"logs/all.coverdata\", \""++JobId++"\", \"travis-ci\")]).' > getcover.erl"},
+ {ct, "escript ./getcover.erl"}] end, []);
+ _ ->
+ Conf5
+ end,
+
%io:format("ejabberd configuration:~n ~p~n", [Conf5]),
-Conf5.
+Conf6.
%% Local Variables:
%% mode: erlang
diff --git a/sql/lite.sql b/sql/lite.sql
index 5132c917..1741ea95 100644
--- a/sql/lite.sql
+++ b/sql/lite.sql
@@ -19,6 +19,9 @@
CREATE TABLE users (
username text PRIMARY KEY,
password text NOT NULL,
+ serverkey text NOT NULL DEFAULT '',
+ salt text NOT NULL DEFAULT '',
+ iterationcount integer NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
@@ -296,3 +299,17 @@ CREATE TABLE archive_prefs (
never text NOT NULL,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);
+
+CREATE TABLE sm (
+ usec bigint NOT NULL,
+ pid text NOT NULL,
+ node text NOT NULL,
+ username text NOT NULL,
+ resource text NOT NULL,
+ priority text NOT NULL,
+ info text NOT NULL
+);
+
+CREATE UNIQUE INDEX i_sm_sid ON sm(usec, pid);
+CREATE INDEX i_sm_node ON sm(node);
+CREATE INDEX i_sm_username ON sm(username);
diff --git a/sql/mssql.sql b/sql/mssql.sql
index 36ce50f4..45378d24 100644
--- a/sql/mssql.sql
+++ b/sql/mssql.sql
Binary files differ
diff --git a/sql/mysql.sql b/sql/mysql.sql
index cc603c1b..b7a86d0e 100644
--- a/sql/mysql.sql
+++ b/sql/mysql.sql
@@ -19,12 +19,15 @@
CREATE TABLE users (
username varchar(191) PRIMARY KEY,
password text NOT NULL,
+ serverkey varchar(64) NOT NULL DEFAULT '',
+ salt varchar(64) NOT NULL DEFAULT '',
+ iterationcount integer NOT NULL DEFAULT 0,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
--- To support SCRAM auth:
--- ALTER TABLE users ADD COLUMN serverkey text NOT NULL DEFAULT '';
--- ALTER TABLE users ADD COLUMN salt text NOT NULL DEFAULT '';
+-- Add support for SCRAM auth to a database created before ejabberd 16.03:
+-- ALTER TABLE users ADD COLUMN serverkey varchar(64) NOT NULL DEFAULT '';
+-- ALTER TABLE users ADD COLUMN salt varchar(64) NOT NULL DEFAULT '';
-- ALTER TABLE users ADD COLUMN iterationcount integer NOT NULL DEFAULT 0;
CREATE TABLE last (
diff --git a/sql/pg.sql b/sql/pg.sql
index 87cccd48..1bc4f397 100644
--- a/sql/pg.sql
+++ b/sql/pg.sql
@@ -19,10 +19,13 @@
CREATE TABLE users (
username text PRIMARY KEY,
"password" text NOT NULL,
+ serverkey text NOT NULL DEFAULT '',
+ salt text NOT NULL DEFAULT '',
+ iterationcount integer NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT now()
);
--- To support SCRAM auth:
+-- Add support for SCRAM auth to a database created before ejabberd 16.03:
-- ALTER TABLE users ADD COLUMN serverkey text NOT NULL DEFAULT '';
-- ALTER TABLE users ADD COLUMN salt text NOT NULL DEFAULT '';
-- ALTER TABLE users ADD COLUMN iterationcount integer NOT NULL DEFAULT 0;
diff --git a/src/acl.erl b/src/acl.erl
index fdf397d8..06202c67 100644
--- a/src/acl.erl
+++ b/src/acl.erl
@@ -31,9 +31,11 @@
-export([start/0, to_record/3, add/3, add_list/3,
add_local/3, add_list_local/3, load_from_config/0,
- match_rule/3, match_acl/3, transform_options/1,
+ match_rule/3, match_access/4, match_acl/3, transform_options/1,
opt_type/1]).
+-export([add_access/3, clear/0]).
+
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
@@ -43,6 +45,7 @@
rules = [] :: [access_rule()]}).
-type regexp() :: binary().
+-type iprange() :: {inet:ip_address(), integer()} | binary().
-type glob() :: binary().
-type access_name() :: atom().
-type access_rule() :: {atom(), any()}.
@@ -61,7 +64,7 @@
{user_glob, {glob(), host()} | glob()} |
{server_glob, glob()} |
{resource_glob, glob()} |
- {ip, {inet:ip_address(), integer()}} |
+ {ip, iprange()} |
{node_glob, {glob(), glob()}}.
-type acl() :: #acl{aclname :: aclname(),
@@ -204,6 +207,12 @@ load_from_config() ->
end, AccessRules)
end, Hosts).
+%% Delete all previous set ACLs and Access rules
+clear() ->
+ mnesia:clear_table(acl),
+ mnesia:clear_table(access),
+ ok.
+
b(S) ->
iolist_to_binary(S).
@@ -246,6 +255,19 @@ normalize_spec(Spec) ->
end
end.
+-spec match_access(global | binary(), access_name(),
+ jid() | ljid() | inet:ip_address(),
+ atom()) -> any().
+
+match_access(_Host, all, _JID, _Default) ->
+ allow;
+match_access(_Host, none, _JID, _Default) ->
+ deny;
+match_access(_Host, {user, UserPattern}, JID, Default) ->
+ match_user_spec({user, UserPattern}, JID, Default);
+match_access(Host, AccessRule, JID, _Default) ->
+ match_rule(Host, AccessRule, JID).
+
-spec match_rule(global | binary(), access_name(),
jid() | ljid() | inet:ip_address()) -> any().
@@ -348,6 +370,16 @@ match_acl(ACL, JID, Host) ->
get_aclspecs(ACL, Host) ->
ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
+
+match_user_spec(Spec, JID, Default) ->
+ case do_match_user_spec(Spec, jid:tolower(JID)) of
+ true -> Default;
+ false -> deny
+ end.
+
+do_match_user_spec({user, {U, S}}, {User, Server, _Resource}) ->
+ U == User andalso S == Server.
+
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
nomatch -> false;
diff --git a/src/adhoc.erl b/src/adhoc.erl
index d252a6cb..83a113a0 100644
--- a/src/adhoc.erl
+++ b/src/adhoc.erl
@@ -51,9 +51,9 @@
parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) ->
?DEBUG("entering parse_request...", []),
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
- SessionID = xml:get_tag_attr_s(<<"sessionid">>, SubEl),
- Action = xml:get_tag_attr_s(<<"action">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
+ SessionID = fxml:get_tag_attr_s(<<"sessionid">>, SubEl),
+ Action = fxml:get_tag_attr_s(<<"action">>, SubEl),
XData = find_xdata_el(SubEl),
#xmlel{children = AllEls} = SubEl,
Others = case XData of
@@ -68,7 +68,9 @@ parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}
xdata = XData,
others = Others
};
-parse_request(_) -> {error, ?ERR_BAD_REQUEST}.
+parse_request(#iq{lang = Lang}) ->
+ Text = <<"Failed to parse ad-hoc command request">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Text)}.
%% Borrowed from mod_vcard.erl
find_xdata_el(#xmlel{children = SubEls}) ->
@@ -76,7 +78,7 @@ find_xdata_el(#xmlel{children = SubEls}) ->
find_xdata_el1([]) -> false;
find_xdata_el1([El | Els]) when is_record(El, xmlel) ->
- case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, El) of
?NS_XDATA -> El;
_ -> find_xdata_el1(Els)
end;
diff --git a/src/cyrsasl.erl b/src/cyrsasl.erl
index 5d2d89d6..21fbc966 100644
--- a/src/cyrsasl.erl
+++ b/src/cyrsasl.erl
@@ -111,12 +111,12 @@ register_mechanism(Mechanism, Module, PasswordType) ->
%%-include("ejabberd.hrl").
%%-include("jlib.hrl").
%%check_authzid(_State, Props) ->
-%% AuthzId = xml:get_attr_s(authzid, Props),
+%% AuthzId = fxml:get_attr_s(authzid, Props),
%% case jid:from_string(AuthzId) of
%% error ->
%% {error, "invalid-authzid"};
%% JID ->
-%% LUser = jid:nodeprep(xml:get_attr_s(username, Props)),
+%% LUser = jid:nodeprep(fxml:get_attr_s(username, Props)),
%% {U, S, R} = jid:tolower(JID),
%% case R of
%% "" ->
@@ -132,7 +132,7 @@ register_mechanism(Mechanism, Module, PasswordType) ->
%% end.
check_credentials(_State, Props) ->
- User = proplists:get_value(username, Props, <<>>),
+ User = proplists:get_value(authzid, Props, <<>>),
case jid:nodeprep(User) of
error -> {error, <<"not-authorized">>};
<<"">> -> {error, <<"not-authorized">>};
diff --git a/src/cyrsasl_anonymous.erl b/src/cyrsasl_anonymous.erl
index 2b2a9f63..802e1cd7 100644
--- a/src/cyrsasl_anonymous.erl
+++ b/src/cyrsasl_anonymous.erl
@@ -45,9 +45,8 @@ mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
mech_step(#state{server = Server} = S, ClientIn) ->
User = iolist_to_binary([randoms:get_string(),
- randoms:get_string(),
- randoms:get_string()]),
+ jlib:integer_to_binary(p1_time_compat:unique_integer([positive]))]),
case ejabberd_auth:is_user_exists(User, Server) of
true -> mech_step(S, ClientIn);
- false -> {ok, [{username, User}, {auth_module, ejabberd_auth_anonymous}]}
+ false -> {ok, [{username, User}, {authzid, User}, {auth_module, ejabberd_auth_anonymous}]}
end.
diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl
index 8ccd9095..e58cb303 100644
--- a/src/cyrsasl_digest.erl
+++ b/src/cyrsasl_digest.erl
@@ -50,7 +50,7 @@
username = <<"">> :: binary(),
authzid = <<"">> :: binary(),
get_password = fun(_) -> {false, <<>>} end :: get_password_fun(),
- check_password = fun(_, _, _, _) -> false end :: check_password_fun(),
+ check_password = fun(_, _, _, _, _) -> false end :: check_password_fun(),
auth_module :: atom(),
host = <<"">> :: binary(),
hostfqdn = <<"">> :: binary()}).
@@ -83,9 +83,7 @@ mech_step(#state{step = 3, nonce = Nonce} = State,
bad -> {error, <<"bad-protocol">>};
KeyVals ->
DigestURI = proplists:get_value(<<"digest-uri">>, KeyVals, <<>>),
- %DigestURI = xml:get_attr_s(<<"digest-uri">>, KeyVals),
UserName = proplists:get_value(<<"username">>, KeyVals, <<>>),
- %UserName = xml:get_attr_s(<<"username">>, KeyVals),
case is_digesturi_valid(DigestURI, State#state.host,
State#state.hostfqdn)
of
@@ -97,13 +95,11 @@ mech_step(#state{step = 3, nonce = Nonce} = State,
{error, <<"not-authorized">>, UserName};
true ->
AuthzId = proplists:get_value(<<"authzid">>, KeyVals, <<>>),
- %AuthzId = xml:get_attr_s(<<"authzid">>, KeyVals),
case (State#state.get_password)(UserName) of
{false, _} -> {error, <<"not-authorized">>, UserName};
{Passwd, AuthModule} ->
- case (State#state.check_password)(UserName, <<"">>,
+ case (State#state.check_password)(UserName, UserName, <<"">>,
proplists:get_value(<<"response">>, KeyVals, <<>>),
- %xml:get_attr_s(<<"response">>, KeyVals),
fun (PW) ->
response(KeyVals,
UserName,
@@ -130,7 +126,11 @@ mech_step(#state{step = 5, auth_module = AuthModule,
username = UserName, authzid = AuthzId},
<<"">>) ->
{ok,
- [{username, UserName}, {authzid, AuthzId},
+ [{username, UserName}, {authzid, case AuthzId of
+ <<"">> -> UserName;
+ _ -> AuthzId
+ end
+ },
{auth_module, AuthModule}]};
mech_step(A, B) ->
?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]),
diff --git a/src/cyrsasl_plain.erl b/src/cyrsasl_plain.erl
index a9c1de05..82d68f87 100644
--- a/src/cyrsasl_plain.erl
+++ b/src/cyrsasl_plain.erl
@@ -45,7 +45,7 @@ mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) ->
mech_step(State, ClientIn) ->
case prepare(ClientIn) of
[AuthzId, User, Password] ->
- case (State#state.check_password)(User, Password) of
+ case (State#state.check_password)(User, AuthzId, Password) of
{true, AuthModule} ->
{ok,
[{username, User}, {authzid, AuthzId},
@@ -60,12 +60,17 @@ prepare(ClientIn) ->
[<<"">>, UserMaybeDomain, Password] ->
case parse_domain(UserMaybeDomain) of
%% <NUL>login@domain<NUL>pwd
- [User, _Domain] -> [UserMaybeDomain, User, Password];
+ [User, _Domain] -> [User, User, Password];
%% <NUL>login<NUL>pwd
- [User] -> [<<"">>, User, Password]
+ [User] -> [User, User, Password]
end;
+ [AuthzId, User, Password] ->
+ case parse_domain(AuthzId) of
%% login@domain<NUL>login<NUL>pwd
- [AuthzId, User, Password] -> [AuthzId, User, Password];
+ [AuthzUser, _Domain] -> [AuthzUser, User, Password];
+ %% login<NUL>login<NUL>pwd
+ [AuthzUser] -> [AuthzUser, User, Password]
+ end;
_ -> error
end.
diff --git a/src/cyrsasl_scram.erl b/src/cyrsasl_scram.erl
index 059938f5..18f52b48 100644
--- a/src/cyrsasl_scram.erl
+++ b/src/cyrsasl_scram.erl
@@ -159,7 +159,8 @@ mech_step(#state{step = 4} = State, ClientIn) ->
ServerSignature =
scram:server_signature(State#state.server_key,
AuthMessage),
- {ok, [{username, State#state.username}],
+ {ok, [{username, State#state.username},
+ {authzid, State#state.username}],
<<"v=",
(jlib:encode_base64(ServerSignature))/binary>>};
true -> {error, <<"bad-auth">>, State#state.username}
diff --git a/src/ejabberd.erl b/src/ejabberd.erl
index e1b92c26..6bd2422a 100644
--- a/src/ejabberd.erl
+++ b/src/ejabberd.erl
@@ -79,7 +79,7 @@ is_loaded() ->
start_app(App, Type, StartFlag) when not is_list(App) ->
start_app([App], Type, StartFlag);
start_app([App|Apps], Type, StartFlag) ->
- case application:start(App) of
+ case application:start(App,Type) of
ok ->
spawn(fun() -> check_app_modules(App, StartFlag) end),
start_app(Apps, Type, StartFlag);
diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl
index 4aa5faed..a22f8f3d 100644
--- a/src/ejabberd_admin.erl
+++ b/src/ejabberd_admin.erl
@@ -192,15 +192,24 @@ get_commands_spec() ->
module = ejabberd_piefxis, function = export_host,
args = [{dir, string}, {host, string}], result = {res, rescode}},
- #ejabberd_commands{name = export_odbc, tags = [mnesia, odbc],
+ #ejabberd_commands{name = export_sql, tags = [mnesia, sql],
desc = "Export all tables as SQL queries to a file",
- module = ejd2odbc, function = export,
+ module = ejd2sql, function = export,
args = [{host, string}, {file, string}], result = {res, rescode}},
- #ejabberd_commands{name = convert_to_scram, tags = [odbc],
+ #ejabberd_commands{name = delete_mnesia, tags = [mnesia, sql],
+ desc = "Export all tables as SQL queries to a file",
+ module = ejd2sql, function = delete,
+ args = [{host, string}], result = {res, rescode}},
+ #ejabberd_commands{name = convert_to_scram, tags = [sql],
desc = "Convert the passwords in 'users' ODBC table to SCRAM",
- module = ejabberd_auth_odbc, function = convert_to_scram,
+ module = ejabberd_auth_sql, function = convert_to_scram,
args = [{host, binary}], result = {res, rescode}},
+ #ejabberd_commands{name = import_prosody, tags = [mnesia, sql, riak],
+ desc = "Import data from Prosody",
+ module = prosody2ejabberd, function = from_dir,
+ args = [{dir, string}], result = {res, rescode}},
+
#ejabberd_commands{name = convert_to_yaml, tags = [config],
desc = "Convert the input file from Erlang to YAML format",
module = ejabberd_config, function = convert_to_yaml,
@@ -216,9 +225,9 @@ get_commands_spec() ->
module = ?MODULE, function = delete_old_messages,
args = [{days, integer}], result = {res, rescode}},
- #ejabberd_commands{name = export2odbc, tags = [mnesia],
+ #ejabberd_commands{name = export2sql, tags = [mnesia],
desc = "Export virtual host information from Mnesia tables to SQL files",
- module = ejd2odbc, function = export,
+ module = ejd2sql, function = export,
args = [{host, string}, {directory, string}],
result = {res, rescode}},
#ejabberd_commands{name = set_master, tags = [mnesia],
diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl
index 628b4a8a..b25bf231 100644
--- a/src/ejabberd_app.erl
+++ b/src/ejabberd_app.erl
@@ -30,7 +30,7 @@
-behaviour(application).
--export([start_modules/0, start/2, prep_stop/1, stop/1,
+-export([start/2, prep_stop/1, stop/1,
init/0, opt_type/1]).
-include("ejabberd.hrl").
@@ -71,7 +71,7 @@ start(normal, _Args) ->
maybe_add_nameservers(),
ejabberd_auth:start(),
ejabberd_oauth:start(),
- start_modules(),
+ gen_mod:start_modules(),
ejabberd_listener:start_listeners(),
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
Sup;
@@ -83,7 +83,7 @@ start(_, _) ->
%% before shutting down the processes of the application.
prep_stop(State) ->
ejabberd_listener:stop_listeners(),
- stop_modules(),
+ gen_mod:stop_modules(),
ejabberd_admin:stop(),
broadcast_c2s_shutdown(),
timer:sleep(5000),
@@ -137,42 +137,6 @@ db_init() ->
ejabberd:start_app(mnesia, permanent),
mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity).
-%% Start all the modules in all the hosts
-start_modules() ->
- lists:foreach(
- fun(Host) ->
- Modules = ejabberd_config:get_option(
- {modules, Host},
- fun(Mods) ->
- lists:map(
- fun({M, A}) when is_atom(M), is_list(A) ->
- {M, A}
- end, Mods)
- end, []),
- lists:foreach(
- fun({Module, Args}) ->
- gen_mod:start_module(Host, Module, Args)
- end, Modules)
- end, ?MYHOSTS).
-
-%% Stop all the modules in all the hosts
-stop_modules() ->
- lists:foreach(
- fun(Host) ->
- Modules = ejabberd_config:get_option(
- {modules, Host},
- fun(Mods) ->
- lists:map(
- fun({M, A}) when is_atom(M), is_list(A) ->
- {M, A}
- end, Mods)
- end, []),
- lists:foreach(
- fun({Module, _Args}) ->
- gen_mod:stop_module_keep_config(Host, Module)
- end, Modules)
- end, ?MYHOSTS).
-
connect_nodes() ->
Nodes = ejabberd_config:get_option(
cluster_nodes,
@@ -252,11 +216,10 @@ start_apps() ->
crypto:start(),
ejabberd:start_app(sasl),
ejabberd:start_app(ssl),
- ejabberd:start_app(p1_yaml),
- ejabberd:start_app(p1_tls),
- ejabberd:start_app(p1_xml),
- ejabberd:start_app(p1_stringprep),
- ejabberd:start_app(p1_zlib),
+ ejabberd:start_app(fast_yaml),
+ ejabberd:start_app(fast_tls),
+ ejabberd:start_app(fast_xml),
+ ejabberd:start_app(stringprep),
ejabberd:start_app(cache_tab).
opt_type(net_ticktime) ->
diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl
index 2cc37c6e..0267a219 100644
--- a/src/ejabberd_auth.erl
+++ b/src/ejabberd_auth.erl
@@ -32,9 +32,9 @@
-author('alexey@process-one.net').
%% External exports
--export([start/0, set_password/3, check_password/3,
- check_password/5, check_password_with_authmodule/3,
- check_password_with_authmodule/5, try_register/3,
+-export([start/0, set_password/3, check_password/4,
+ check_password/6, check_password_with_authmodule/4,
+ check_password_with_authmodule/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2, export/1, import/1,
get_vh_registered_users_number/1, import/3,
@@ -63,8 +63,8 @@
-callback remove_user(binary(), binary()) -> any().
-callback remove_user(binary(), binary(), binary()) -> any().
-callback is_user_exists(binary(), binary()) -> boolean() | {error, atom()}.
--callback check_password(binary(), binary(), binary()) -> boolean().
--callback check_password(binary(), binary(), binary(), binary(),
+-callback check_password(binary(), binary(), binary(), binary()) -> boolean().
+-callback check_password(binary(), binary(), binary(), binary(), binary(),
fun((binary()) -> binary())) -> boolean().
-callback try_register(binary(), binary(), binary()) -> {atomic, atom()} |
{error, atom()}.
@@ -102,10 +102,10 @@ store_type(Server) ->
end,
plain, auth_modules(Server)).
--spec check_password(binary(), binary(), binary()) -> boolean().
+-spec check_password(binary(), binary(), binary(), binary()) -> boolean().
-check_password(User, Server, Password) ->
- case check_password_with_authmodule(User, Server,
+check_password(User, AuthzId, Server, Password) ->
+ case check_password_with_authmodule(User, AuthzId, Server,
Password)
of
{true, _AuthModule} -> true;
@@ -113,15 +113,15 @@ check_password(User, Server, Password) ->
end.
%% @doc Check if the user and password can login in server.
-%% @spec (User::string(), Server::string(), Password::string(),
+%% @spec (User::string(), AuthzId::string(), Server::string(), Password::string(),
%% Digest::string(), DigestGen::function()) ->
%% true | false
--spec check_password(binary(), binary(), binary(), binary(),
+-spec check_password(binary(), binary(), binary(), binary(), binary(),
fun((binary()) -> binary())) -> boolean().
-check_password(User, Server, Password, Digest,
+check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
- case check_password_with_authmodule(User, Server,
+ case check_password_with_authmodule(User, AuthzId, Server,
Password, Digest, DigestGen)
of
{true, _AuthModule} -> true;
@@ -132,28 +132,28 @@ check_password(User, Server, Password, Digest,
%% The user can login if at least an authentication method accepts the user
%% and the password.
%% The first authentication method that accepts the credentials is returned.
-%% @spec (User::string(), Server::string(), Password::string()) ->
+%% @spec (User::string(), AuthzId::string(), Server::string(), Password::string()) ->
%% {true, AuthModule} | false
%% where
%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external
%% | ejabberd_auth_internal | ejabberd_auth_ldap
-%% | ejabberd_auth_odbc | ejabberd_auth_pam
--spec check_password_with_authmodule(binary(), binary(), binary()) -> false |
+%% | ejabberd_auth_sql | ejabberd_auth_pam | ejabberd_auth_riak
+-spec check_password_with_authmodule(binary(), binary(), binary(), binary()) -> false |
{true, atom()}.
-check_password_with_authmodule(User, Server,
+check_password_with_authmodule(User, AuthzId, Server,
Password) ->
check_password_loop(auth_modules(Server),
- [User, Server, Password]).
+ [User, AuthzId, Server, Password]).
--spec check_password_with_authmodule(binary(), binary(), binary(), binary(),
+-spec check_password_with_authmodule(binary(), binary(), binary(), binary(), binary(),
fun((binary()) -> binary())) -> false |
{true, atom()}.
-check_password_with_authmodule(User, Server, Password,
+check_password_with_authmodule(User, AuthzId, Server, Password,
Digest, DigestGen) ->
check_password_loop(auth_modules(Server),
- [User, Server, Password, Digest, DigestGen]).
+ [User, AuthzId, Server, Password, Digest, DigestGen]).
check_password_loop([], _Args) -> false;
check_password_loop([AuthModule | AuthModules], Args) ->
diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl
index 7b21752a..5a5b395b 100644
--- a/src/ejabberd_auth_anonymous.erl
+++ b/src/ejabberd_auth_anonymous.erl
@@ -38,8 +38,8 @@
unregister_connection/3
]).
--export([login/2, set_password/3, check_password/3,
- check_password/5, try_register/3,
+-export([login/2, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
@@ -56,7 +56,7 @@
%% Create the anonymous table if at least one virtual host has anonymous features enabled
%% Register to login / logout events
-record(anonymous, {us = {<<"">>, <<"">>} :: {binary(), binary()},
- sid = {p1_time_compat:timestamp(), self()} :: ejabberd_sm:sid()}).
+ sid = ejabberd_sm:make_sid() :: ejabberd_sm:sid()}).
start(Host) ->
%% TODO: Check cluster mode
@@ -175,11 +175,11 @@ purge_hook(true, LUser, LServer) ->
%% When anonymous login is enabled, check the password for permenant users
%% before allowing access
-check_password(User, Server, Password) ->
- check_password(User, Server, Password, undefined,
+check_password(User, AuthzId, Server, Password) ->
+ check_password(User, AuthzId, Server, Password, undefined,
undefined).
-check_password(User, Server, _Password, _Digest,
+check_password(User, _AuthzId, Server, _Password, _Digest,
_DigestGen) ->
case
ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl
index 2a1cbf08..5897fba5 100644
--- a/src/ejabberd_auth_external.erl
+++ b/src/ejabberd_auth_external.erl
@@ -31,8 +31,8 @@
-behaviour(ejabberd_auth).
--export([start/1, set_password/3, check_password/3,
- check_password/5, try_register/3,
+-export([start/1, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
@@ -76,16 +76,20 @@ plain_password_required() -> true.
store_type() -> external.
-check_password(User, Server, Password) ->
+check_password(User, AuthzId, Server, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
case get_cache_option(Server) of
- false -> check_password_extauth(User, Server, Password);
+ false -> check_password_extauth(User, AuthzId, Server, Password);
{true, CacheTime} ->
- check_password_cache(User, Server, Password, CacheTime)
+ check_password_cache(User, AuthzId, Server, Password, CacheTime)
+ end
end.
-check_password(User, Server, Password, _Digest,
+check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
- check_password(User, Server, Password).
+ check_password(User, AuthzId, Server, Password).
set_password(User, Server, Password) ->
case extauth:set_password(User, Server, Password) of
@@ -178,8 +182,8 @@ get_cache_option(Host) ->
CacheTime -> {true, CacheTime}
end.
-%% @spec (User, Server, Password) -> true | false
-check_password_extauth(User, Server, Password) ->
+%% @spec (User, AuthzId, Server, Password) -> true | false
+check_password_extauth(User, _AuthzId, Server, Password) ->
extauth:check_password(User, Server, Password) andalso
Password /= <<"">>.
@@ -187,42 +191,42 @@ check_password_extauth(User, Server, Password) ->
try_register_extauth(User, Server, Password) ->
extauth:try_register(User, Server, Password).
-check_password_cache(User, Server, Password, 0) ->
- check_password_external_cache(User, Server, Password);
-check_password_cache(User, Server, Password,
+check_password_cache(User, AuthzId, Server, Password, 0) ->
+ check_password_external_cache(User, AuthzId, Server, Password);
+check_password_cache(User, AuthzId, Server, Password,
CacheTime) ->
case get_last_access(User, Server) of
online ->
- check_password_internal(User, Server, Password);
+ check_password_internal(User, AuthzId, Server, Password);
never ->
- check_password_external_cache(User, Server, Password);
+ check_password_external_cache(User, AuthzId, Server, Password);
mod_last_required ->
?ERROR_MSG("extauth is used, extauth_cache is enabled "
"but mod_last is not enabled in that "
"host",
[]),
- check_password_external_cache(User, Server, Password);
+ check_password_external_cache(User, AuthzId, Server, Password);
TimeStamp ->
case is_fresh_enough(TimeStamp, CacheTime) of
%% If no need to refresh, check password against Mnesia
true ->
- case check_password_internal(User, Server, Password) of
+ case check_password_internal(User, AuthzId, Server, Password) of
%% If password valid in Mnesia, accept it
true -> true;
%% Else (password nonvalid in Mnesia), check in extauth and cache result
false ->
- check_password_external_cache(User, Server, Password)
+ check_password_external_cache(User, AuthzId, Server, Password)
end;
%% Else (need to refresh), check in extauth and cache result
false ->
- check_password_external_cache(User, Server, Password)
+ check_password_external_cache(User, AuthzId, Server, Password)
end
end.
get_password_internal(User, Server) ->
ejabberd_auth_internal:get_password(User, Server).
-%% @spec (User, Server, CacheTime) -> false | Password::string()
+-spec get_password_cache(User::binary(), Server::binary(), CacheTime::integer()) -> Password::string() | false.
get_password_cache(User, Server, CacheTime) ->
case get_last_access(User, Server) of
online -> get_password_internal(User, Server);
@@ -241,8 +245,8 @@ get_password_cache(User, Server, CacheTime) ->
end.
%% Check the password using extauth; if success then cache it
-check_password_external_cache(User, Server, Password) ->
- case check_password_extauth(User, Server, Password) of
+check_password_external_cache(User, AuthzId, Server, Password) ->
+ case check_password_extauth(User, AuthzId, Server, Password) of
true ->
set_password_internal(User, Server, Password), true;
false -> false
@@ -256,9 +260,9 @@ try_register_external_cache(User, Server, Password) ->
_ -> {error, not_allowed}
end.
-%% @spec (User, Server, Password) -> true | false
-check_password_internal(User, Server, Password) ->
- ejabberd_auth_internal:check_password(User, Server,
+%% @spec (User, AuthzId, Server, Password) -> true | false
+check_password_internal(User, AuthzId, Server, Password) ->
+ ejabberd_auth_internal:check_password(User, AuthzId, Server,
Password).
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
@@ -273,10 +277,10 @@ is_fresh_enough(TimeStampLast, CacheTime) ->
Now = p1_time_compat:system_time(seconds),
TimeStampLast + CacheTime > Now.
-%% @spec (User, Server) -> online | never | mod_last_required | TimeStamp::integer()
%% Code copied from mod_configure.erl
%% Code copied from web/ejabberd_web_admin.erl
%% TODO: Update time format to XEP-0202: Entity Time
+-spec(get_last_access(User::binary(), Server::binary()) -> (online | never | mod_last_required | integer())).
get_last_access(User, Server) ->
case ejabberd_sm:get_user_resources(User, Server) of
[] ->
diff --git a/src/ejabberd_auth_internal.erl b/src/ejabberd_auth_internal.erl
index d60e0fc5..acbbfe50 100644
--- a/src/ejabberd_auth_internal.erl
+++ b/src/ejabberd_auth_internal.erl
@@ -31,8 +31,8 @@
-behaviour(ejabberd_auth).
--export([start/1, set_password/3, check_password/3,
- check_password/5, try_register/3,
+-export([start/1, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
@@ -86,9 +86,12 @@ store_type() ->
true -> scram %% allows: PLAIN SCRAM
end.
-check_password(User, Server, Password) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+check_password(User, AuthzId, Server, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Password}]
@@ -98,12 +101,16 @@ check_password(User, Server, Password) ->
when is_record(Scram, scram) ->
is_password_scram_valid(Password, Scram);
_ -> false
+ end
end.
-check_password(User, Server, Password, Digest,
+check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Passwd}] when is_binary(Passwd) ->
@@ -125,6 +132,7 @@ check_password(User, Server, Password, Digest,
true -> (Passwd == Password) and (Password /= <<"">>)
end;
_ -> false
+ end
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
@@ -466,8 +474,8 @@ export(_Server) ->
[{passwd,
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- Pass = ejabberd_odbc:escape(Password),
+ Username = ejabberd_sql:escape(LUser),
+ Pass = ejabberd_sql:escape(Password),
[[<<"delete from users where username='">>, Username, <<"';">>],
[<<"insert into users(username, password) "
"values ('">>, Username, <<"', '">>, Pass, <<"');">>]];
diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl
index dd5d54a7..51b466ef 100644
--- a/src/ejabberd_auth_ldap.erl
+++ b/src/ejabberd_auth_ldap.erl
@@ -37,7 +37,7 @@
handle_cast/2, terminate/2, code_change/3]).
-export([start/1, stop/1, start_link/1, set_password/3,
- check_password/3, check_password/5, try_register/3,
+ check_password/4, check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
@@ -116,7 +116,10 @@ plain_password_required() -> true.
store_type() -> external.
-check_password(User, Server, Password) ->
+check_password(User, AuthzId, Server, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
if Password == <<"">> -> false;
true ->
case catch check_password_ldap(User, Server, Password)
@@ -124,11 +127,12 @@ check_password(User, Server, Password) ->
{'EXIT', _} -> false;
Result -> Result
end
+ end
end.
-check_password(User, Server, Password, _Digest,
+check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
- check_password(User, Server, Password).
+ check_password(User, AuthzId, Server, Password).
set_password(User, Server, Password) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
diff --git a/src/ejabberd_auth_pam.erl b/src/ejabberd_auth_pam.erl
index ee5123c5..fa4b9f07 100644
--- a/src/ejabberd_auth_pam.erl
+++ b/src/ejabberd_auth_pam.erl
@@ -30,8 +30,8 @@
-behaviour(ejabberd_auth).
--export([start/1, set_password/3, check_password/3,
- check_password/5, try_register/3,
+-export([start/1, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
@@ -46,11 +46,14 @@ start(_Host) ->
set_password(_User, _Server, _Password) ->
{error, not_allowed}.
-check_password(User, Server, Password, _Digest,
+check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
- check_password(User, Server, Password).
+ check_password(User, AuthzId, Server, Password).
-check_password(User, Host, Password) ->
+check_password(User, AuthzId, Host, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
Service = get_pam_service(Host),
UserInfo = case get_pam_userinfotype(Host) of
username -> User;
@@ -61,6 +64,7 @@ check_password(User, Host, Password) ->
of
true -> true;
_ -> false
+ end
end.
try_register(_User, _Server, _Password) ->
diff --git a/src/ejabberd_auth_riak.erl b/src/ejabberd_auth_riak.erl
index 64bf12c6..c48b9441 100644
--- a/src/ejabberd_auth_riak.erl
+++ b/src/ejabberd_auth_riak.erl
@@ -30,8 +30,8 @@
-behaviour(ejabberd_auth).
%% External exports
--export([start/1, set_password/3, check_password/3,
- check_password/5, try_register/3,
+-export([start/1, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
@@ -66,9 +66,12 @@ store_type() ->
passwd_schema() ->
{record_info(fields, passwd), #passwd{}}.
-check_password(User, Server, Password) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+check_password(User, AuthzId, Server, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}} when is_binary(Password) ->
Password /= <<"">>;
@@ -76,12 +79,16 @@ check_password(User, Server, Password) ->
is_password_scram_valid(Password, Scram);
_ ->
false
+ end
end.
-check_password(User, Server, Password, Digest,
+check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Passwd}} when is_binary(Passwd) ->
DigRes = if Digest /= <<"">> ->
@@ -102,6 +109,7 @@ check_password(User, Server, Password, Digest,
true -> (Passwd == Password) and (Password /= <<"">>)
end;
_ -> false
+ end
end.
set_password(User, Server, Password) ->
@@ -283,8 +291,8 @@ export(_Server) ->
[{passwd,
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- Pass = ejabberd_odbc:escape(Password),
+ Username = ejabberd_sql:escape(LUser),
+ Pass = ejabberd_sql:escape(Password),
[[<<"delete from users where username='">>, Username, <<"';">>],
[<<"insert into users(username, password) "
"values ('">>, Username, <<"', '">>, Pass, <<"');">>]];
diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_sql.erl
index b8b4594b..000b4a4f 100644
--- a/src/ejabberd_auth_odbc.erl
+++ b/src/ejabberd_auth_sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : ejabberd_auth_odbc.erl
+%%% File : ejabberd_auth_sql.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Authentification via ODBC
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
@@ -23,7 +23,7 @@
%%%
%%%----------------------------------------------------------------------
--module(ejabberd_auth_odbc).
+-module(ejabberd_auth_sql).
-behaviour(ejabberd_config).
@@ -31,8 +31,8 @@
-behaviour(ejabberd_auth).
--export([start/1, set_password/3, check_password/3,
- check_password/5, try_register/3,
+-export([start/1, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
@@ -63,31 +63,30 @@ store_type() ->
true -> scram %% allows: PLAIN SCRAM
end.
-%% @spec (User, Server, Password) -> true | false | {error, Error}
-check_password(User, Server, Password) ->
- LServer = jid:nameprep(Server),
- LUser = jid:nodeprep(User),
+%% @spec (User, AuthzId, Server, Password) -> true | false | {error, Error}
+check_password(User, AuthzId, Server, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
false;
(LUser == <<>>) or (LServer == <<>>) ->
false;
true ->
- Username = ejabberd_odbc:escape(LUser),
case is_scrammed() of
true ->
- try odbc_queries:get_password_scram(LServer, Username) of
- {selected, [<<"password">>, <<"serverkey">>,
- <<"salt">>, <<"iterationcount">>],
- [[StoredKey, ServerKey, Salt, IterationCount]]} ->
+ try sql_queries:get_password_scram(LServer, LUser) of
+ {selected,
+ [{StoredKey, ServerKey, Salt, IterationCount}]} ->
Scram =
#scram{storedkey = StoredKey,
serverkey = ServerKey,
salt = Salt,
- iterationcount = binary_to_integer(
- IterationCount)},
+ iterationcount = IterationCount},
is_password_scram_valid(Password, Scram);
- {selected, [<<"password">>, <<"serverkey">>,
- <<"salt">>, <<"iterationcount">>], []} ->
+ {selected, []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
@@ -96,12 +95,12 @@ check_password(User, Server, Password) ->
false %% Typical error is database not accessible
end;
false ->
- try odbc_queries:get_password(LServer, Username) of
- {selected, [<<"password">>], [[Password]]} ->
+ try sql_queries:get_password(LServer, LUser) of
+ {selected, [{Password}]} ->
Password /= <<"">>;
- {selected, [<<"password">>], [[_Password2]]} ->
+ {selected, [{_Password2}]} ->
false; %% Password is not correct
- {selected, [<<"password">>], []} ->
+ {selected, []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
@@ -110,13 +109,17 @@ check_password(User, Server, Password) ->
false %% Typical error is database not accessible
end
end
+ end
end.
-%% @spec (User, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
-check_password(User, Server, Password, Digest,
+%% @spec (User, AuthzId, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
+check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
- LServer = jid:nameprep(Server),
- LUser = jid:nodeprep(User),
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
false;
(LUser == <<>>) or (LServer == <<>>) ->
@@ -124,10 +127,9 @@ check_password(User, Server, Password, Digest,
true ->
case is_scrammed() of
false ->
- Username = ejabberd_odbc:escape(LUser),
- try odbc_queries:get_password(LServer, Username) of
+ try sql_queries:get_password(LServer, LUser) of
%% Account exists, check if password is valid
- {selected, [<<"password">>], [[Passwd]]} ->
+ {selected, [{Passwd}]} ->
DigRes = if Digest /= <<"">> ->
Digest == DigestGen(Passwd);
true -> false
@@ -135,7 +137,7 @@ check_password(User, Server, Password, Digest,
if DigRes -> true;
true -> (Passwd == Password) and (Password /= <<"">>)
end;
- {selected, [<<"password">>], []} ->
+ {selected, []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
@@ -146,6 +148,7 @@ check_password(User, Server, Password, Digest,
true ->
false
end
+ end
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
@@ -158,26 +161,24 @@ set_password(User, Server, Password) ->
(LUser == <<>>) or (LServer == <<>>) ->
{error, invalid_jid};
true ->
- Username = ejabberd_odbc:escape(LUser),
case is_scrammed() of
true ->
Scram = password_to_scram(Password),
- case catch odbc_queries:set_password_scram_t(
+ case catch sql_queries:set_password_scram_t(
LServer,
- Username,
- ejabberd_odbc:escape(Scram#scram.storedkey),
- ejabberd_odbc:escape(Scram#scram.serverkey),
- ejabberd_odbc:escape(Scram#scram.salt),
- integer_to_binary(Scram#scram.iterationcount)
+ LUser,
+ Scram#scram.storedkey,
+ Scram#scram.serverkey,
+ Scram#scram.salt,
+ Scram#scram.iterationcount
)
of
{atomic, ok} -> ok;
Other -> {error, Other}
end;
false ->
- Pass = ejabberd_odbc:escape(Password),
- case catch odbc_queries:set_password_t(LServer,
- Username, Pass)
+ case catch sql_queries:set_password_t(LServer,
+ LUser, Password)
of
{atomic, ok} -> ok;
Other -> {error, Other}
@@ -194,26 +195,23 @@ try_register(User, Server, Password) ->
(LUser == <<>>) or (LServer == <<>>) ->
{error, invalid_jid};
true ->
- Username = ejabberd_odbc:escape(LUser),
case is_scrammed() of
true ->
Scram = password_to_scram(Password),
- case catch odbc_queries:add_user_scram(
+ case catch sql_queries:add_user_scram(
LServer,
- Username,
- ejabberd_odbc:escape(Scram#scram.storedkey),
- ejabberd_odbc:escape(Scram#scram.serverkey),
- ejabberd_odbc:escape(Scram#scram.salt),
- integer_to_binary(Scram#scram.iterationcount)
+ LUser,
+ Scram#scram.storedkey,
+ Scram#scram.serverkey,
+ Scram#scram.salt,
+ Scram#scram.iterationcount
) of
{updated, 1} -> {atomic, ok};
_ -> {atomic, exists}
end;
false ->
- Pass = ejabberd_odbc:escape(Password),
- case catch odbc_queries:add_user(LServer, Username,
- Pass)
- of
+ case catch sql_queries:add_user(LServer, LUser,
+ Password) of
{updated, 1} -> {atomic, ok};
_ -> {atomic, exists}
end
@@ -221,42 +219,58 @@ try_register(User, Server, Password) ->
end.
dirty_get_registered_users() ->
- Servers = ejabberd_config:get_vh_by_auth_method(odbc),
+ Servers = ejabberd_config:get_vh_by_auth_method(sql),
lists:flatmap(fun (Server) ->
get_vh_registered_users(Server)
end,
Servers).
get_vh_registered_users(Server) ->
- LServer = jid:nameprep(Server),
- case catch odbc_queries:list_users(LServer) of
- {selected, [<<"username">>], Res} ->
- [{U, LServer} || [U] <- Res];
- _ -> []
+ case jid:nameprep(Server) of
+ error -> [];
+ <<>> -> [];
+ LServer ->
+ case catch sql_queries:list_users(LServer) of
+ {selected, Res} ->
+ [{U, LServer} || {U} <- Res];
+ _ -> []
+ end
end.
get_vh_registered_users(Server, Opts) ->
- LServer = jid:nameprep(Server),
- case catch odbc_queries:list_users(LServer, Opts) of
- {selected, [<<"username">>], Res} ->
- [{U, LServer} || [U] <- Res];
- _ -> []
+ case jid:nameprep(Server) of
+ error -> [];
+ <<>> -> [];
+ LServer ->
+ case catch sql_queries:list_users(LServer, Opts) of
+ {selected, Res} ->
+ [{U, LServer} || {U} <- Res];
+ _ -> []
+ end
end.
get_vh_registered_users_number(Server) ->
- LServer = jid:nameprep(Server),
- case catch odbc_queries:users_number(LServer) of
- {selected, [_], [[Res]]} ->
- jlib:binary_to_integer(Res);
- _ -> 0
+ case jid:nameprep(Server) of
+ error -> 0;
+ <<>> -> 0;
+ LServer ->
+ case catch sql_queries:users_number(LServer) of
+ {selected, [{Res}]} ->
+ Res;
+ _ -> 0
+ end
end.
get_vh_registered_users_number(Server, Opts) ->
- LServer = jid:nameprep(Server),
- case catch odbc_queries:users_number(LServer, Opts) of
- {selected, [_], [[Res]]} ->
- jlib:binary_to_integer(Res);
- _Other -> 0
+ case jid:nameprep(Server) of
+ error -> 0;
+ <<>> -> 0;
+ LServer ->
+ case catch sql_queries:users_number(LServer, Opts) of
+ {selected, [{Res}]} ->
+ Res;
+ _Other -> 0
+ end
end.
get_password(User, Server) ->
@@ -267,24 +281,22 @@ get_password(User, Server) ->
(LUser == <<>>) or (LServer == <<>>) ->
false;
true ->
- Username = ejabberd_odbc:escape(LUser),
case is_scrammed() of
true ->
- case catch odbc_queries:get_password_scram(
- LServer, Username) of
- {selected, [<<"password">>, <<"serverkey">>,
- <<"salt">>, <<"iterationcount">>],
- [[StoredKey, ServerKey, Salt, IterationCount]]} ->
+ case catch sql_queries:get_password_scram(
+ LServer, LUser) of
+ {selected,
+ [{StoredKey, ServerKey, Salt, IterationCount}]} ->
{jlib:decode_base64(StoredKey),
jlib:decode_base64(ServerKey),
jlib:decode_base64(Salt),
- binary_to_integer(IterationCount)};
+ IterationCount};
_ -> false
end;
false ->
- case catch odbc_queries:get_password(LServer, Username)
+ case catch sql_queries:get_password(LServer, LUser)
of
- {selected, [<<"password">>], [[Password]]} -> Password;
+ {selected, [{Password}]} -> Password;
_ -> false
end
end
@@ -300,9 +312,8 @@ get_password_s(User, Server) ->
true ->
case is_scrammed() of
false ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_password(LServer, Username) of
- {selected, [<<"password">>], [[Password]]} -> Password;
+ case catch sql_queries:get_password(LServer, LUser) of
+ {selected, [{Password}]} -> Password;
_ -> <<"">>
end;
true -> <<"">>
@@ -311,15 +322,17 @@ get_password_s(User, Server) ->
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
- case jid:nodeprep(User) of
- error -> false;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jid:nameprep(Server),
- try odbc_queries:get_password(LServer, Username) of
- {selected, [<<"password">>], [[_Password]]} ->
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ false;
+ (LUser == <<>>) or (LServer == <<>>) ->
+ false;
+ true ->
+ try sql_queries:get_password(LServer, LUser) of
+ {selected, [{_Password}]} ->
true; %% Account exists
- {selected, [<<"password">>], []} ->
+ {selected, []} ->
false; %% Account does not exist
{error, Error} -> {error, Error}
catch
@@ -331,12 +344,14 @@ is_user_exists(User, Server) ->
%% @doc Remove user.
%% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) ->
- case jid:nodeprep(User) of
- error -> error;
- LUser ->
- Username = ejabberd_odbc:escape(LUser),
- LServer = jid:nameprep(Server),
- catch odbc_queries:del_user(LServer, Username),
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ error;
+ (LUser == <<>>) or (LServer == <<>>) ->
+ error;
+ true ->
+ catch sql_queries:del_user(LServer, LUser),
ok
end.
@@ -352,27 +367,23 @@ remove_user(User, Server, Password) ->
true ->
case is_scrammed() of
true ->
- case check_password(User, Server, Password) of
+ case check_password(User, <<"">>, Server, Password) of
true ->
remove_user(User, Server),
ok;
false -> not_allowed
end;
false ->
- Username = ejabberd_odbc:escape(LUser),
- Pass = ejabberd_odbc:escape(Password),
F = fun () ->
- Result = odbc_queries:del_user_return_password(
- LServer, Username, Pass),
+ Result = sql_queries:del_user_return_password(
+ LServer, LUser, Password),
case Result of
- {selected, [<<"password">>],
- [[Password]]} -> ok;
- {selected, [<<"password">>],
- []} -> not_exists;
+ {selected, [{Password}]} -> ok;
+ {selected, []} -> not_exists;
_ -> not_allowed
end
end,
- {atomic, Result} = odbc_queries:sql_transaction(
+ {atomic, Result} = sql_queries:sql_transaction(
LServer, F),
Result
end
@@ -416,7 +427,7 @@ is_password_scram_valid(Password, Scram) ->
set_password_scram_t(Username,
StoredKey, ServerKey, Salt, IterationCount) ->
- odbc_queries:update_t(<<"users">>,
+ sql_queries:update_t(<<"users">>,
[<<"username">>,
<<"password">>,
<<"serverkey">>,
@@ -436,7 +447,7 @@ convert_to_scram(Server) ->
{error, {incorrect_server_name, Server}};
true ->
F = fun () ->
- case ejabberd_odbc:sql_query_t(
+ case ejabberd_sql:sql_query_t(
[<<"select username, password from users where "
"iterationcount=0 limit ">>,
integer_to_binary(?BATCH_SIZE),
@@ -446,13 +457,13 @@ convert_to_scram(Server) ->
{selected, [<<"username">>, <<"password">>], Rs} ->
lists:foreach(
fun([LUser, Password]) ->
- Username = ejabberd_odbc:escape(LUser),
+ Username = ejabberd_sql:escape(LUser),
Scram = password_to_scram(Password),
set_password_scram_t(
Username,
- ejabberd_odbc:escape(Scram#scram.storedkey),
- ejabberd_odbc:escape(Scram#scram.serverkey),
- ejabberd_odbc:escape(Scram#scram.salt),
+ ejabberd_sql:escape(Scram#scram.storedkey),
+ ejabberd_sql:escape(Scram#scram.serverkey),
+ ejabberd_sql:escape(Scram#scram.salt),
integer_to_binary(Scram#scram.iterationcount)
)
end, Rs),
@@ -460,7 +471,7 @@ convert_to_scram(Server) ->
Err -> {bad_reply, Err}
end
end,
- case odbc_queries:sql_transaction(LServer, F) of
+ case sql_queries:sql_transaction(LServer, F) of
{atomic, ok} -> ok;
{atomic, continue} -> convert_to_scram(Server);
{atomic, Error} -> {error, Error};
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 04e41f46..948e2027 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -115,6 +115,7 @@
mgmt_resend,
mgmt_stanzas_in = 0,
mgmt_stanzas_out = 0,
+ ask_offline = true,
lang = <<"">>}).
%-define(DBGFSM, true).
@@ -133,13 +134,13 @@
%% session:
-define(C2S_OPEN_TIMEOUT, 60000).
--define(C2S_HIBERNATE_TIMEOUT, 90000).
+-define(C2S_HIBERNATE_TIMEOUT, ejabberd_config:get_option(c2s_hibernate, fun(X) when is_integer(X); X == hibernate-> X end, 90000)).
-define(STREAM_HEADER,
<<"<?xml version='1.0'?><stream:stream "
"xmlns='jabber:client' xmlns:stream='http://et"
- "herx.jabber.org/streams' id='~s' from='~s'~s~"
- "s>">>).
+ "herx.jabber.org/streams' id='~s' from='~s'~s"
+ "~s>">>).
-define(STREAM_TRAILER, <<"</stream:stream>">>).
@@ -326,7 +327,7 @@ init([{SockMod, Socket}, Opts]) ->
xml_socket = XMLSocket, zlib = Zlib, tls = TLS,
tls_required = StartTLSRequired,
tls_enabled = TLSEnabled, tls_options = TLSOpts,
- sid = {p1_time_compat:timestamp(), self()}, streamid = new_id(),
+ sid = ejabberd_sm:make_sid(), streamid = new_id(),
access = Access, shaper = Shaper, ip = IP,
mgmt_state = StreamMgmtState,
mgmt_max_queue = MaxAckQueue,
@@ -342,15 +343,15 @@ get_subscribed(FsmRef) ->
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
DefaultLang = ?MYLANG,
- case xml:get_attr_s(<<"xmlns:stream">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns:stream">>, Attrs) of
?NS_STREAM ->
Server =
case StateData#state.server of
<<"">> ->
- jid:nameprep(xml:get_attr_s(<<"to">>, Attrs));
+ jid:nameprep(fxml:get_attr_s(<<"to">>, Attrs));
S -> S
end,
- Lang = case xml:get_attr_s(<<"xml:lang">>, Attrs) of
+ Lang = case fxml:get_attr_s(<<"xml:lang">>, Attrs) of
Lang1 when byte_size(Lang1) =< 35 ->
%% As stated in BCP47, 4.4.1:
%% Protocols or specifications that
@@ -367,7 +368,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
case lists:member(Server, ?MYHOSTS) of
true when IsBlacklistedIP == false ->
change_shaper(StateData, jid:make(<<"">>, Server, <<"">>)),
- case xml:get_attr_s(<<"version">>, Attrs) of
+ case fxml:get_attr_s(<<"version">>, Attrs) of
<<"1.0">> ->
send_header(StateData, Server, <<"1.0">>, DefaultLang),
case StateData#state.authenticated of
@@ -381,13 +382,13 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
ejabberd_auth:get_password_with_authmodule(
U, Server)
end,
- fun (U, P) ->
+ fun(U, AuthzId, P) ->
ejabberd_auth:check_password_with_authmodule(
- U, Server, P)
+ U, AuthzId, Server, P)
end,
- fun (U, P, D, DG) ->
+ fun(U, AuthzId, P, D, DG) ->
ejabberd_auth:check_password_with_authmodule(
- U, Server, P, D, DG)
+ U, AuthzId, Server, P, D, DG)
end),
Mechs =
case TLSEnabled or not TLSRequired of
@@ -408,7 +409,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
(StateData#state.sockmod):get_sockmod(StateData#state.socket),
Zlib = StateData#state.zlib,
CompressFeature = case Zlib andalso
- ((SockMod == gen_tcp) orelse (SockMod == p1_tls)) of
+ ((SockMod == gen_tcp) orelse (SockMod == fast_tls)) of
true ->
[#xmlel{name = <<"compression">>,
attrs = [{<<"xmlns">>, ?NS_FEATURE_COMPRESS}],
@@ -468,6 +469,22 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
false ->
[]
end,
+ SockMod =
+ (StateData#state.sockmod):get_sockmod(
+ StateData#state.socket),
+ Zlib = StateData#state.zlib,
+ CompressFeature =
+ case Zlib andalso
+ ((SockMod == gen_tcp) orelse (SockMod == fast_tls)) of
+ true ->
+ [#xmlel{name = <<"compression">>,
+ attrs = [{<<"xmlns">>, ?NS_FEATURE_COMPRESS}],
+ children = [#xmlel{name = <<"method">>,
+ attrs = [],
+ children = [{xmlcdata, <<"zlib">>}]}]}];
+ _ ->
+ []
+ end,
StreamFeatures1 = [#xmlel{name = <<"bind">>,
attrs = [{<<"xmlns">>, ?NS_BIND}],
children = []},
@@ -478,6 +495,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
++
RosterVersioningFeature ++
StreamManagementFeature ++
+ CompressFeature ++
ejabberd_hooks:run_fold(c2s_post_auth_features,
Server, [], [Server]),
StreamFeatures = ejabberd_hooks:run_fold(c2s_stream_features,
@@ -504,7 +522,6 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
send_element(StateData,
?POLICY_VIOLATION_ERR(Lang,
<<"Use of STARTTLS required">>)),
- send_trailer(StateData),
{stop, normal, StateData};
true ->
fsm_next_state(wait_for_auth,
@@ -519,34 +536,28 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
[jlib:ip_to_list(IP), LogReason]),
send_header(StateData, Server, <<"">>, DefaultLang),
send_element(StateData, ?POLICY_VIOLATION_ERR(Lang, ReasonT)),
- send_trailer(StateData),
{stop, normal, StateData};
_ ->
send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
send_element(StateData, ?HOST_UNKNOWN_ERR),
- send_trailer(StateData),
{stop, normal, StateData}
end;
_ ->
send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
send_element(StateData, ?INVALID_NS_ERR),
- send_trailer(StateData),
{stop, normal, StateData}
end;
wait_for_stream(timeout, StateData) ->
{stop, normal, StateData};
wait_for_stream({xmlstreamelement, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream({xmlstreamend, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream({xmlstreamerror, _}, StateData) ->
send_header(StateData, ?MYNAME, <<"1.0">>, <<"">>),
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream(closed, StateData) ->
{stop, normal, StateData};
@@ -601,8 +612,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
send_element(StateData, Res),
fsm_next_state(wait_for_auth, StateData);
{auth, _ID, set, {_U, _P, _D, <<"">>}} ->
- Err = jlib:make_error_reply(El,
- ?ERR_AUTH_NO_RESOURCE_PROVIDED((StateData#state.lang))),
+ Lang = StateData#state.lang,
+ Txt = <<"No resource provided">>,
+ Err = jlib:make_error_reply(El, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)),
send_element(StateData, Err),
fsm_next_state(wait_for_auth, StateData);
{auth, _ID, set, {U, P, D, R}} ->
@@ -616,7 +628,7 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
DGen = fun (PW) ->
p1_sha:sha(<<(StateData#state.streamid)/binary, PW/binary>>)
end,
- case ejabberd_auth:check_password_with_authmodule(U,
+ case ejabberd_auth:check_password_with_authmodule(U, U,
StateData#state.server,
P, D, DGen)
of
@@ -667,7 +679,10 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
[false, U, StateData#state.server,
StateData#state.ip]),
- Err = jlib:make_error_reply(El, ?ERR_NOT_AUTHORIZED),
+ Lang = StateData#state.lang,
+ Txt = <<"Legacy authentication failed">>,
+ Err = jlib:make_error_reply(
+ El, ?ERRT_NOT_AUTHORIZED(Lang, Txt)),
send_element(StateData, Err),
fsm_next_state(wait_for_auth, StateData)
end;
@@ -688,7 +703,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
[false, U, StateData#state.server,
StateData#state.ip]),
- Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED),
+ Lang = StateData#state.lang,
+ Txt = <<"Legacy authentication forbidden">>,
+ Err = jlib:make_error_reply(El, ?ERRT_NOT_ALLOWED(Lang, Txt)),
send_element(StateData, Err),
fsm_next_state(wait_for_auth, StateData)
end
@@ -700,10 +717,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
wait_for_auth(timeout, StateData) ->
{stop, normal, StateData};
wait_for_auth({xmlstreamend, _Name}, StateData) ->
- send_trailer(StateData), {stop, normal, StateData};
+ {stop, normal, StateData};
wait_for_auth({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
wait_for_auth(closed, StateData) ->
{stop, normal, StateData};
@@ -724,19 +740,17 @@ wait_for_feature_request({xmlstreamelement, El},
TLSRequired = StateData#state.tls_required,
SockMod =
(StateData#state.sockmod):get_sockmod(StateData#state.socket),
- case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
{?NS_SASL, <<"auth">>}
when TLSEnabled or not TLSRequired ->
- Mech = xml:get_attr_s(<<"mechanism">>, Attrs),
- ClientIn = jlib:decode_base64(xml:get_cdata(Els)),
+ Mech = fxml:get_attr_s(<<"mechanism">>, Attrs),
+ ClientIn = jlib:decode_base64(fxml:get_cdata(Els)),
case cyrsasl:server_start(StateData#state.sasl_state,
Mech, ClientIn)
of
{ok, Props} ->
(StateData#state.sockmod):reset_stream(StateData#state.socket),
- %U = xml:get_attr_s(username, Props),
- U = proplists:get_value(username, Props, <<>>),
- %AuthModule = xml:get_attr_s(auth_module, Props),
+ U = identity(Props),
AuthModule = proplists:get_value(auth_module, Props, undefined),
?INFO_MSG("(~w) Accepted authentication for ~s "
"by ~p from ~s",
@@ -802,7 +816,7 @@ wait_for_feature_request({xmlstreamelement, El},
StateData#state.tls_options)]
end,
Socket = StateData#state.socket,
- BProceed = xml:element_to_binary(#xmlel{name = <<"proceed">>,
+ BProceed = fxml:element_to_binary(#xmlel{name = <<"proceed">>,
attrs = [{<<"xmlns">>, ?NS_TLS}]}),
TLSSocket = (StateData#state.sockmod):starttls(Socket,
TLSOpts,
@@ -813,46 +827,14 @@ wait_for_feature_request({xmlstreamelement, El},
tls_enabled = true});
{?NS_COMPRESS, <<"compress">>}
when Zlib == true,
- (SockMod == gen_tcp) or (SockMod == p1_tls) ->
- case xml:get_subtag(El, <<"method">>) of
- false ->
- send_element(StateData,
- #xmlel{name = <<"failure">>,
- attrs = [{<<"xmlns">>, ?NS_COMPRESS}],
- children =
- [#xmlel{name = <<"setup-failed">>,
- attrs = [], children = []}]}),
- fsm_next_state(wait_for_feature_request, StateData);
- Method ->
- case xml:get_tag_cdata(Method) of
- <<"zlib">> ->
- Socket = StateData#state.socket,
- BCompressed = xml:element_to_binary(#xmlel{name = <<"compressed">>,
- attrs = [{<<"xmlns">>, ?NS_COMPRESS}]}),
- ZlibSocket = (StateData#state.sockmod):compress(Socket,
- BCompressed),
- fsm_next_state(wait_for_stream,
- StateData#state{socket = ZlibSocket,
- streamid = new_id()});
- _ ->
- send_element(StateData,
- #xmlel{name = <<"failure">>,
- attrs = [{<<"xmlns">>, ?NS_COMPRESS}],
- children =
- [#xmlel{name =
- <<"unsupported-method">>,
- attrs = [],
- children = []}]}),
- fsm_next_state(wait_for_feature_request, StateData)
- end
- end;
+ (SockMod == gen_tcp) or (SockMod == fast_tls) ->
+ process_compression_request(El, wait_for_feature_request, StateData);
_ ->
if TLSRequired and not TLSEnabled ->
Lang = StateData#state.lang,
send_element(StateData,
?POLICY_VIOLATION_ERR(Lang,
<<"Use of STARTTLS required">>)),
- send_trailer(StateData),
{stop, normal, StateData};
true ->
process_unauthenticated_stanza(StateData, El),
@@ -863,11 +845,10 @@ wait_for_feature_request(timeout, StateData) ->
{stop, normal, StateData};
wait_for_feature_request({xmlstreamend, _Name},
StateData) ->
- send_trailer(StateData), {stop, normal, StateData};
+ {stop, normal, StateData};
wait_for_feature_request({xmlstreamerror, _},
StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
wait_for_feature_request(closed, StateData) ->
{stop, normal, StateData};
@@ -880,18 +861,16 @@ wait_for_sasl_response({xmlstreamelement, #xmlel{name = Name} = El}, StateData)
wait_for_sasl_response({xmlstreamelement, El},
StateData) ->
#xmlel{name = Name, attrs = Attrs, children = Els} = El,
- case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
{?NS_SASL, <<"response">>} ->
- ClientIn = jlib:decode_base64(xml:get_cdata(Els)),
+ ClientIn = jlib:decode_base64(fxml:get_cdata(Els)),
case cyrsasl:server_step(StateData#state.sasl_state,
ClientIn)
of
{ok, Props} ->
catch
(StateData#state.sockmod):reset_stream(StateData#state.socket),
-% U = xml:get_attr_s(username, Props),
- U = proplists:get_value(username, Props, <<>>),
-% AuthModule = xml:get_attr_s(auth_module, Props),
+ U = identity(Props),
AuthModule = proplists:get_value(auth_module, Props, <<>>),
?INFO_MSG("(~w) Accepted authentication for ~s "
"by ~p from ~s",
@@ -912,9 +891,7 @@ wait_for_sasl_response({xmlstreamelement, El},
user = U});
{ok, Props, ServerOut} ->
(StateData#state.sockmod):reset_stream(StateData#state.socket),
-% U = xml:get_attr_s(username, Props),
- U = proplists:get_value(username, Props, <<>>),
-% AuthModule = xml:get_attr_s(auth_module, Props),
+ U = identity(Props),
AuthModule = proplists:get_value(auth_module, Props, undefined),
?INFO_MSG("(~w) Accepted authentication for ~s "
"by ~p from ~s",
@@ -976,11 +953,10 @@ wait_for_sasl_response(timeout, StateData) ->
{stop, normal, StateData};
wait_for_sasl_response({xmlstreamend, _Name},
StateData) ->
- send_trailer(StateData), {stop, normal, StateData};
+ {stop, normal, StateData};
wait_for_sasl_response({xmlstreamerror, _},
StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
wait_for_sasl_response(closed, StateData) ->
{stop, normal, StateData};
@@ -1012,7 +988,7 @@ resource_conflict_action(U, S, R) ->
acceptnew -> {accept_resource, R};
closenew -> closenew;
setresource ->
- Rnew = iolist_to_binary([randoms:get_string(),randoms:get_string(),randoms:get_string()]),
+ Rnew = new_uniq_id(),
{accept_resource, Rnew}
end.
@@ -1032,20 +1008,20 @@ wait_for_bind({xmlstreamelement, #xmlel{name = Name, attrs = Attrs} = El},
end;
wait_for_bind({xmlstreamelement, El}, StateData) ->
case jlib:iq_query_info(El) of
- #iq{type = set, xmlns = ?NS_BIND, sub_el = SubEl} =
+ #iq{type = set, lang = Lang, xmlns = ?NS_BIND, sub_el = SubEl} =
IQ ->
U = StateData#state.user,
- R1 = xml:get_path_s(SubEl,
+ R1 = fxml:get_path_s(SubEl,
[{elem, <<"resource">>}, cdata]),
R = case jid:resourceprep(R1) of
error -> error;
- <<"">> ->
- iolist_to_binary([randoms:get_string(),randoms:get_string(),randoms:get_string()]);
+ <<"">> -> new_uniq_id();
Resource -> Resource
end,
case R of
error ->
- Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST),
+ Txt = <<"Malformed resource">>,
+ Err = jlib:make_error_reply(El, ?ERRT_BAD_REQUEST(Lang, Txt)),
send_element(StateData, Err),
fsm_next_state(wait_for_bind, StateData);
_ ->
@@ -1088,15 +1064,26 @@ wait_for_bind({xmlstreamelement, El}, StateData) ->
end
end
end;
- _ -> fsm_next_state(wait_for_bind, StateData)
+ _ ->
+ #xmlel{name = Name, attrs = Attrs, children = _Els} = El,
+ Zlib = StateData#state.zlib,
+ SockMod =
+ (StateData#state.sockmod):get_sockmod(StateData#state.socket),
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
+ {?NS_COMPRESS, <<"compress">>}
+ when Zlib == true,
+ (SockMod == gen_tcp) or (SockMod == fast_tls) ->
+ process_compression_request(El, wait_for_bind, StateData);
+ _ ->
+ fsm_next_state(wait_for_bind, StateData)
+ end
end;
wait_for_bind(timeout, StateData) ->
{stop, normal, StateData};
wait_for_bind({xmlstreamend, _Name}, StateData) ->
- send_trailer(StateData), {stop, normal, StateData};
+ {stop, normal, StateData};
wait_for_bind({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
wait_for_bind(closed, StateData) ->
{stop, normal, StateData};
@@ -1107,6 +1094,7 @@ open_session(StateData) ->
U = StateData#state.user,
R = StateData#state.resource,
JID = StateData#state.jid,
+ Lang = StateData#state.lang,
case acl:match_rule(StateData#state.server,
StateData#state.access, JID) of
allow ->
@@ -1144,7 +1132,8 @@ open_session(StateData) ->
StateData#state.server, [JID]),
?INFO_MSG("(~w) Forbidden session for ~s",
[StateData#state.socket, jid:to_string(JID)]),
- {error, ?ERR_NOT_ALLOWED}
+ Txt = <<"Denied by ACL">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
end.
session_established({xmlstreamelement, #xmlel{name = Name} = El}, StateData)
@@ -1167,7 +1156,6 @@ session_established({xmlstreamelement, El},
case check_from(El, FromJID) of
'invalid-from' ->
send_element(StateData, ?INVALID_FROM),
- send_trailer(StateData),
{stop, normal, StateData};
_NewEl ->
session_established2(El, StateData)
@@ -1180,17 +1168,15 @@ session_established(timeout, StateData) ->
[?MODULE, Options, session_established, StateData]),
fsm_next_state(session_established, StateData);
session_established({xmlstreamend, _Name}, StateData) ->
- send_trailer(StateData), {stop, normal, StateData};
+ {stop, normal, StateData};
session_established({xmlstreamerror,
<<"XML stanza is too big">> = E},
StateData) ->
send_element(StateData,
?POLICY_VIOLATION_ERR((StateData#state.lang), E)),
- send_trailer(StateData),
{stop, normal, StateData};
session_established({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
- send_trailer(StateData),
{stop, normal, StateData};
session_established(closed, #state{mgmt_state = active} = StateData) ->
catch (StateData#state.sockmod):close(StateData#state.socket),
@@ -1207,24 +1193,24 @@ session_established2(El, StateData) ->
User = NewStateData#state.user,
Server = NewStateData#state.server,
FromJID = NewStateData#state.jid,
- To = xml:get_attr_s(<<"to">>, Attrs),
+ To = fxml:get_attr_s(<<"to">>, Attrs),
ToJID = case To of
<<"">> -> jid:make(User, Server, <<"">>);
_ -> jid:from_string(To)
end,
NewEl1 = jlib:remove_attr(<<"xmlns">>, El),
- NewEl = case xml:get_attr_s(<<"xml:lang">>, Attrs) of
+ NewEl = case fxml:get_attr_s(<<"xml:lang">>, Attrs) of
<<"">> ->
case NewStateData#state.lang of
<<"">> -> NewEl1;
Lang ->
- xml:replace_tag_attr(<<"xml:lang">>, Lang, NewEl1)
+ fxml:replace_tag_attr(<<"xml:lang">>, Lang, NewEl1)
end;
_ -> NewEl1
end,
NewState = case ToJID of
error ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> NewStateData;
<<"result">> -> NewStateData;
_ ->
@@ -1345,7 +1331,6 @@ handle_info(kick, StateName, StateData) ->
handle_info({kick, kicked_by_admin, Xmlelement}, StateName, StateData);
handle_info({kick, Reason, Xmlelement}, _StateName, StateData) ->
send_element(StateData, Xmlelement),
- send_trailer(StateData),
{stop, normal,
StateData#state{authenticated = Reason}};
handle_info({route, _From, _To, {broadcast, Data}},
@@ -1358,7 +1343,6 @@ handle_info({route, _From, _To, {broadcast, Data}},
{exit, Reason} ->
Lang = StateData#state.lang,
send_element(StateData, ?SERRT_CONFLICT(Lang, Reason)),
- catch send_trailer(StateData),
{stop, normal, StateData};
{privacy_list, PrivList, PrivListName} ->
case ejabberd_hooks:run_fold(privacy_updated_list,
@@ -1408,7 +1392,7 @@ handle_info({route, From, To,
StateData,
[{From, To,
Packet}]),
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"probe">> ->
LFrom = jid:tolower(From),
LBFrom =
@@ -1625,7 +1609,7 @@ handle_info({route, From, To,
allow ->
{true, Attrs, StateData};
deny ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"groupchat">> -> ok;
<<"headline">> -> ok;
@@ -1672,11 +1656,9 @@ handle_info(system_shutdown, StateName, StateData) ->
wait_for_stream ->
send_header(StateData, ?MYNAME, <<"1.0">>, <<"en">>),
send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
- send_trailer(StateData),
ok;
_ ->
send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
- send_trailer(StateData),
ok
end,
{stop, normal, StateData};
@@ -1737,6 +1719,8 @@ handle_info({broadcast, Type, From, Packet}, StateName, StateData) ->
From, jid:make(USR), Packet)
end, lists:usort(Recipients)),
fsm_next_state(StateName, StateData);
+handle_info(dont_ask_offline, StateName, StateData) ->
+ fsm_next_state(StateName, StateData#state{ask_offline = false});
handle_info(Info, StateName, StateData) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
fsm_next_state(StateName, StateData).
@@ -1806,6 +1790,7 @@ terminate(_Reason, StateName, StateData) ->
ok
end
end,
+ catch send_trailer(StateData),
(StateData#state.sockmod):close(StateData#state.socket),
ok.
@@ -1844,7 +1829,7 @@ send_element(StateData, El) when StateData#state.xml_socket ->
(StateData#state.sockmod):send_xml(StateData#state.socket,
{xmlstreamelement, El});
send_element(StateData, El) ->
- send_text(StateData, xml:element_to_binary(El)).
+ send_text(StateData, fxml:element_to_binary(El)).
send_stanza(StateData, Stanza) when StateData#state.csi_state == inactive ->
csi_filter_stanza(StateData, Stanza);
@@ -1912,6 +1897,10 @@ send_trailer(StateData) ->
new_id() -> randoms:get_string().
+new_uniq_id() ->
+ iolist_to_binary([randoms:get_string(),
+ jlib:integer_to_binary(p1_time_compat:unique_integer([positive]))]).
+
is_auth_packet(El) ->
case jlib:iq_query_info(El) of
#iq{id = ID, type = Type, xmlns = ?NS_AUTH, sub_el = SubEl} ->
@@ -1924,7 +1913,7 @@ is_auth_packet(El) ->
is_stanza(#xmlel{name = Name, attrs = Attrs}) when Name == <<"message">>;
Name == <<"presence">>;
Name == <<"iq">> ->
- case xml:get_attr(<<"xmlns">>, Attrs) of
+ case fxml:get_attr(<<"xmlns">>, Attrs) of
{value, NS} when NS /= <<"jabber:client">>,
NS /= <<"jabber:server">> ->
false;
@@ -1936,7 +1925,7 @@ is_stanza(_El) ->
get_auth_tags([#xmlel{name = Name, children = Els} | L],
U, P, D, R) ->
- CData = xml:get_cdata(Els),
+ CData = fxml:get_cdata(Els),
case Name of
<<"username">> -> get_auth_tags(L, CData, P, D, R);
<<"password">> -> get_auth_tags(L, U, CData, D, R);
@@ -1955,11 +1944,11 @@ get_auth_tags([], U, P, D, R) ->
get_conn_type(StateData) ->
case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of
gen_tcp -> c2s;
- p1_tls -> c2s_tls;
+ fast_tls -> c2s_tls;
ezlib ->
case ezlib:get_sockmod((StateData#state.socket)#socket_state.socket) of
gen_tcp -> c2s_compressed;
- p1_tls -> c2s_compressed_tls
+ fast_tls -> c2s_compressed_tls
end;
ejabberd_http_bind -> http_bind;
ejabberd_http_ws -> websocket;
@@ -2003,11 +1992,11 @@ process_presence_probe(From, To, StateData) ->
%% User updates his presence (non-directed presence packet)
presence_update(From, Packet, StateData) ->
#xmlel{attrs = Attrs} = Packet,
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"unavailable">> ->
- Status = case xml:get_subtag(Packet, <<"status">>) of
+ Status = case fxml:get_subtag(Packet, <<"status">>) of
false -> <<"">>;
- StatusTag -> xml:get_tag_cdata(StatusTag)
+ StatusTag -> fxml:get_tag_cdata(StatusTag)
end,
Info = [{ip, StateData#state.ip},
{conn, StateData#state.conn},
@@ -2068,7 +2057,7 @@ presence_track(From, To, Packet, StateData) ->
LTo = jid:tolower(To),
User = StateData#state.user,
Server = StateData#state.server,
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"unavailable">> ->
A = remove_element(LTo, StateData#state.pres_a),
check_privacy_route(From, StateData#state{pres_a = A}, From, To, Packet);
@@ -2265,11 +2254,11 @@ update_priority(Priority, Packet, StateData) ->
StateData#state.resource, Priority, Packet, Info).
get_priority_from_presence(PresencePacket) ->
- case xml:get_subtag(PresencePacket, <<"priority">>) of
+ case fxml:get_subtag(PresencePacket, <<"priority">>) of
false -> 0;
SubEl ->
case catch
- jlib:binary_to_integer(xml:get_tag_cdata(SubEl))
+ jlib:binary_to_integer(fxml:get_tag_cdata(SubEl))
of
P when is_integer(P) -> P;
_ -> 0
@@ -2277,30 +2266,32 @@ get_priority_from_presence(PresencePacket) ->
end.
process_privacy_iq(From, To,
- #iq{type = Type, sub_el = SubEl} = IQ, StateData) ->
- {Res, NewStateData} = case Type of
- get ->
- R = ejabberd_hooks:run_fold(privacy_iq_get,
- StateData#state.server,
- {error,
- ?ERR_FEATURE_NOT_IMPLEMENTED},
- [From, To, IQ,
- StateData#state.privacy_list]),
- {R, StateData};
- set ->
- case ejabberd_hooks:run_fold(privacy_iq_set,
- StateData#state.server,
- {error,
- ?ERR_FEATURE_NOT_IMPLEMENTED},
- [From, To, IQ])
- of
- {result, R, NewPrivList} ->
- {{result, R},
- StateData#state{privacy_list =
- NewPrivList}};
- R -> {R, StateData}
- end
- end,
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ, StateData) ->
+ Txt = <<"No module is handling this query">>,
+ {Res, NewStateData} =
+ case Type of
+ get ->
+ R = ejabberd_hooks:run_fold(
+ privacy_iq_get,
+ StateData#state.server,
+ {error, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)},
+ [From, To, IQ,
+ StateData#state.privacy_list]),
+ {R, StateData};
+ set ->
+ case ejabberd_hooks:run_fold(
+ privacy_iq_set,
+ StateData#state.server,
+ {error, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)},
+ [From, To, IQ])
+ of
+ {result, R, NewPrivList} ->
+ {{result, R},
+ StateData#state{privacy_list =
+ NewPrivList}};
+ R -> {R, StateData}
+ end
+ end,
IQRes = case Res of
{result, Result} ->
IQ#iq{type = result, sub_el = Result};
@@ -2310,7 +2301,7 @@ process_privacy_iq(From, To,
ejabberd_router:route(To, From, jlib:iq_to_xml(IQRes)),
NewStateData.
-resend_offline_messages(StateData) ->
+resend_offline_messages(#state{ask_offline = true} = StateData) ->
case ejabberd_hooks:run_fold(resend_offline_messages_hook,
StateData#state.server, [],
[StateData#state.user, StateData#state.server])
@@ -2331,7 +2322,9 @@ resend_offline_messages(StateData) ->
end
end,
Rs)
- end.
+ end;
+resend_offline_messages(_StateData) ->
+ ok.
resend_subscription_requests(#state{user = User,
server = Server} = StateData) ->
@@ -2346,34 +2339,35 @@ resend_subscription_requests(#state{user = User,
get_showtag(undefined) -> <<"unavailable">>;
get_showtag(Presence) ->
- case xml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of
+ case fxml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of
<<"">> -> <<"available">>;
ShowTag -> ShowTag
end.
get_statustag(undefined) -> <<"">>;
get_statustag(Presence) ->
- xml:get_path_s(Presence, [{elem, <<"status">>}, cdata]).
+ fxml:get_path_s(Presence, [{elem, <<"status">>}, cdata]).
process_unauthenticated_stanza(StateData, El) ->
- NewEl = case xml:get_tag_attr_s(<<"xml:lang">>, El) of
+ NewEl = case fxml:get_tag_attr_s(<<"xml:lang">>, El) of
<<"">> ->
case StateData#state.lang of
<<"">> -> El;
- Lang -> xml:replace_tag_attr(<<"xml:lang">>, Lang, El)
+ Lang -> fxml:replace_tag_attr(<<"xml:lang">>, Lang, El)
end;
_ -> El
end,
case jlib:iq_query_info(NewEl) of
- #iq{} = IQ ->
+ #iq{lang = L} = IQ ->
Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq,
StateData#state.server, empty,
[StateData#state.server, IQ,
StateData#state.ip]),
case Res of
empty ->
+ Txt = <<"Authentication required">>,
ResIQ = IQ#iq{type = error,
- sub_el = [?ERR_SERVICE_UNAVAILABLE]},
+ sub_el = [?ERRT_SERVICE_UNAVAILABLE(L, Txt)]},
Res1 = jlib:replace_from_to(jid:make(<<"">>,
StateData#state.server,
<<"">>),
@@ -2419,7 +2413,6 @@ fsm_next_state(session_established, #state{mgmt_max_queue = exceeded} =
Err = ?SERRT_POLICY_VIOLATION(StateData#state.lang,
<<"Too many unacked stanzas">>),
send_element(StateData, Err),
- send_trailer(StateData),
{stop, normal, StateData#state{mgmt_resend = false}};
fsm_next_state(session_established, #state{mgmt_state = pending} = StateData) ->
fsm_next_state(wait_for_resume, StateData);
@@ -2462,7 +2455,7 @@ is_ip_blacklisted({IP, _Port}, Lang) ->
%% Check from attributes
%% returns invalid-from|NewElement
check_from(El, FromJID) ->
- case xml:get_tag_attr(<<"from">>, El) of
+ case fxml:get_tag_attr(<<"from">>, El) of
false ->
El;
{value, SJID} ->
@@ -2505,6 +2498,41 @@ bounce_messages() ->
after 0 -> ok
end.
+process_compression_request(El, StateName, StateData) ->
+ case fxml:get_subtag(El, <<"method">>) of
+ false ->
+ send_element(StateData,
+ #xmlel{name = <<"failure">>,
+ attrs = [{<<"xmlns">>, ?NS_COMPRESS}],
+ children =
+ [#xmlel{name = <<"setup-failed">>,
+ attrs = [], children = []}]}),
+ fsm_next_state(StateName, StateData);
+ Method ->
+ case fxml:get_tag_cdata(Method) of
+ <<"zlib">> ->
+ Socket = StateData#state.socket,
+ BCompressed = fxml:element_to_binary(
+ #xmlel{name = <<"compressed">>,
+ attrs = [{<<"xmlns">>,
+ ?NS_COMPRESS}]}),
+ ZlibSocket = (StateData#state.sockmod):compress(
+ Socket, BCompressed),
+ fsm_next_state(wait_for_stream,
+ StateData#state{socket = ZlibSocket,
+ streamid = new_id()});
+ _ ->
+ send_element(StateData,
+ #xmlel{name = <<"failure">>,
+ attrs = [{<<"xmlns">>, ?NS_COMPRESS}],
+ children =
+ [#xmlel{name = <<"unsupported-method">>,
+ attrs = [],
+ children = []}]}),
+ fsm_next_state(StateName, StateData)
+ end
+ end.
+
%%%----------------------------------------------------------------------
%%% XEP-0191
%%%----------------------------------------------------------------------
@@ -2573,7 +2601,7 @@ negotiate_stream_mgmt(_El, #state{resource = <<"">>} = StateData) ->
send_element(StateData, ?MGMT_UNEXPECTED_REQUEST(?NS_STREAM_MGMT_3)),
StateData;
negotiate_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
Xmlns when ?IS_SUPPORTED_MGMT_XMLNS(Xmlns) ->
case stream_mgmt_enabled(StateData) of
true ->
@@ -2601,7 +2629,7 @@ negotiate_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) ->
end.
perform_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
Xmlns when Xmlns == StateData#state.mgmt_xmlns ->
case Name of
<<"r">> ->
@@ -2626,10 +2654,10 @@ perform_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) ->
handle_enable(#state{mgmt_timeout = DefaultTimeout,
mgmt_max_timeout = MaxTimeout} = StateData, Attrs) ->
- Timeout = case xml:get_attr_s(<<"resume">>, Attrs) of
+ Timeout = case fxml:get_attr_s(<<"resume">>, Attrs) of
ResumeAttr when ResumeAttr == <<"true">>;
ResumeAttr == <<"1">> ->
- MaxAttr = xml:get_attr_s(<<"max">>, Attrs),
+ MaxAttr = fxml:get_attr_s(<<"max">>, Attrs),
case catch jlib:binary_to_integer(MaxAttr) of
Max when is_integer(Max), Max > 0, Max =< MaxTimeout ->
Max;
@@ -2669,7 +2697,7 @@ handle_r(StateData) ->
StateData.
handle_a(StateData, Attrs) ->
- case catch jlib:binary_to_integer(xml:get_attr_s(<<"h">>, Attrs)) of
+ case catch jlib:binary_to_integer(fxml:get_attr_s(<<"h">>, Attrs)) of
H when is_integer(H), H >= 0 ->
check_h_attribute(StateData, H);
_ ->
@@ -2679,12 +2707,12 @@ handle_a(StateData, Attrs) ->
end.
handle_resume(StateData, Attrs) ->
- R = case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ R = case fxml:get_attr_s(<<"xmlns">>, Attrs) of
Xmlns when ?IS_SUPPORTED_MGMT_XMLNS(Xmlns) ->
case stream_mgmt_enabled(StateData) of
true ->
- case {xml:get_attr(<<"previd">>, Attrs),
- catch jlib:binary_to_integer(xml:get_attr_s(<<"h">>, Attrs))}
+ case {fxml:get_attr(<<"previd">>, Attrs),
+ catch jlib:binary_to_integer(fxml:get_attr_s(<<"h">>, Attrs))}
of
{{value, PrevID}, H} when is_integer(H), H >= 0 ->
case inherit_session_state(StateData, PrevID) of
@@ -2815,9 +2843,9 @@ handle_unacked_stanzas(StateData, F)
[N, jid:to_string(StateData#state.jid)]),
lists:foreach(
fun({_, Time, #xmlel{attrs = Attrs} = El}) ->
- From_s = xml:get_attr_s(<<"from">>, Attrs),
+ From_s = fxml:get_attr_s(<<"from">>, Attrs),
From = jid:from_string(From_s),
- To_s = xml:get_attr_s(<<"to">>, Attrs),
+ To_s = fxml:get_attr_s(<<"to">>, Attrs),
To = jid:from_string(To_s),
F(From, To, El, Time)
end, queue:to_list(Queue))
@@ -2833,9 +2861,18 @@ handle_unacked_stanzas(StateData)
Resend when is_boolean(Resend) ->
Resend;
if_offline ->
- ejabberd_sm:get_user_resources(StateData#state.user,
- StateData#state.server) == []
+ Resource = StateData#state.resource,
+ case ejabberd_sm:get_user_resources(StateData#state.user,
+ StateData#state.server) of
+ [Resource] -> % Same resource opened new session
+ true;
+ [] ->
+ true;
+ _ ->
+ false
+ end
end,
+ Lang = StateData#state.lang,
ReRoute = case ResendOnTimeout of
true ->
fun(From, To, El, Time) ->
@@ -2844,9 +2881,11 @@ handle_unacked_stanzas(StateData)
end;
false ->
fun(From, To, El, _Time) ->
+ Txt = <<"User session not found">>,
Err =
- jlib:make_error_reply(El,
- ?ERR_SERVICE_UNAVAILABLE),
+ jlib:make_error_reply(
+ El,
+ ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end
end,
@@ -2854,7 +2893,9 @@ handle_unacked_stanzas(StateData)
?DEBUG("Dropping presence stanza from ~s",
[jid:to_string(From)]);
(From, To, #xmlel{name = <<"iq">>} = El, _Time) ->
- Err = jlib:make_error_reply(El, ?ERR_SERVICE_UNAVAILABLE),
+ Txt = <<"User session not found">>,
+ Err = jlib:make_error_reply(
+ El, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err);
(From, To, El, Time) ->
%% We'll drop the stanza if it was <forwarded/> by some
@@ -2867,7 +2908,7 @@ handle_unacked_stanzas(StateData)
case is_encapsulated_forward(El) of
true ->
?DEBUG("Dropping forwarded message stanza from ~s",
- [xml:get_attr_s(<<"from">>, El#xmlel.attrs)]);
+ [fxml:get_attr_s(<<"from">>, El#xmlel.attrs)]);
false ->
case ejabberd_hooks:run_fold(message_is_archived,
StateData#state.server,
@@ -2886,9 +2927,9 @@ handle_unacked_stanzas(_StateData) ->
ok.
is_encapsulated_forward(#xmlel{name = <<"message">>} = El) ->
- SubTag = case {xml:get_subtag(El, <<"sent">>),
- xml:get_subtag(El, <<"received">>),
- xml:get_subtag(El, <<"result">>)} of
+ SubTag = case {fxml:get_subtag(El, <<"sent">>),
+ fxml:get_subtag(El, <<"received">>),
+ fxml:get_subtag(El, <<"result">>)} of
{false, false, false} ->
false;
{Tag, false, false} ->
@@ -2901,7 +2942,7 @@ is_encapsulated_forward(#xmlel{name = <<"message">>} = El) ->
if SubTag == false ->
false;
true ->
- case xml:get_subtag(SubTag, <<"forwarded">>) of
+ case fxml:get_subtag(SubTag, <<"forwarded">>) of
false ->
false;
_ ->
@@ -2989,7 +3030,7 @@ csi_filter_stanza(#state{csi_state = CsiState, jid = JID} = StateData,
queue -> csi_queue_add(StateData, Stanza);
drop -> StateData;
send ->
- From = xml:get_tag_attr_s(<<"from">>, Stanza),
+ From = fxml:get_tag_attr_s(<<"from">>, Stanza),
StateData1 = csi_queue_send(StateData, From),
StateData2 = send_stanza(StateData1#state{csi_state = active},
Stanza),
@@ -3000,7 +3041,7 @@ csi_queue_add(#state{csi_queue = Queue} = StateData, Stanza) ->
case length(StateData#state.csi_queue) >= csi_max_queue(StateData) of
true -> csi_queue_add(csi_queue_flush(StateData), Stanza);
false ->
- From = xml:get_tag_attr_s(<<"from">>, Stanza),
+ From = fxml:get_tag_attr_s(<<"from">>, Stanza),
NewQueue = lists:keystore(From, 1, Queue, {From, p1_time_compat:timestamp(), Stanza}),
StateData#state{csi_queue = NewQueue}
end.
@@ -3080,6 +3121,12 @@ pack_string(String, Pack) ->
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
+identity(Props) ->
+ case proplists:get_value(authzid, Props, <<>>) of
+ <<>> -> proplists:get_value(username, Props, <<>>);
+ AuthzId -> AuthzId
+ end.
+
opt_type(domain_certfile) -> fun iolist_to_binary/1;
opt_type(max_fsm_queue) ->
fun (I) when is_integer(I), I > 0 -> I end;
diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl
index cc3e2e9f..157700c4 100644
--- a/src/ejabberd_captcha.erl
+++ b/src/ejabberd_captcha.erl
@@ -320,7 +320,7 @@ build_captcha_html(Id, Lang) ->
-spec process_reply(xmlel()) -> ok | {error, bad_match | not_found | malformed}.
process_reply(#xmlel{} = El) ->
- case xml:get_subtag(El, <<"x">>) of
+ case fxml:get_subtag(El, <<"x">>) of
false -> {error, malformed};
Xdata ->
Fields = jlib:parse_xdata_submit(Xdata),
diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl
index 265d7141..55ecba5d 100644
--- a/src/ejabberd_commands.erl
+++ b/src/ejabberd_commands.erl
@@ -90,7 +90,8 @@
%%% PowFloat = math:pow(Base, Exponent),
%%% round(PowFloat).</pre>
%%%
-%%% Since this function will be called by ejabberd_commands, it must be exported.
+%%% Since this function will be called by ejabberd_commands, it must
+%%% be exported.
%%% Add to your module:
%%% <pre>-export([calc_power/2]).</pre>
%%%
@@ -201,24 +202,34 @@
%%% TODO: consider this feature:
%%% All commands are catched. If an error happens, return the restuple:
%%% {error, flattened error string}
-%%% This means that ecomm call APIs (ejabberd_ctl, ejabberd_xmlrpc) need to allows this.
-%%% And ejabberd_xmlrpc must be prepared to handle such an unexpected response.
+%%% This means that ecomm call APIs (ejabberd_ctl, ejabberd_xmlrpc)
+%%% need to allows this. And ejabberd_xmlrpc must be prepared to
+%%% handle such an unexpected response.
-module(ejabberd_commands).
-author('badlop@process-one.net').
+-define(DEFAULT_VERSION, 1000000).
+
-export([init/0,
list_commands/0,
+ list_commands/1,
get_command_format/1,
- get_command_format/2,
+ get_command_format/2,
+ get_command_format/3,
+ get_command_policy/1,
get_command_definition/1,
+ get_command_definition/2,
get_tags_commands/0,
+ get_tags_commands/1,
get_commands/0,
register_commands/1,
unregister_commands/1,
execute_command/2,
- execute_command/4,
+ execute_command/3,
+ execute_command/4,
+ execute_command/5,
opt_type/1,
get_commands_spec/0
]).
@@ -226,6 +237,7 @@
-include("ejabberd_commands.hrl").
-include("ejabberd.hrl").
-include("logger.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
-define(POLICY_ACCESS, '$policy').
@@ -260,23 +272,26 @@ get_commands_spec() ->
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
result_example = ok}].
init() ->
- ets:new(ejabberd_commands, [named_table, set, public,
- {keypos, #ejabberd_commands.name}]),
+ mnesia:delete_table(ejabberd_commands),
+ mnesia:create_table(ejabberd_commands,
+ [{ram_copies, [node()]},
+ {local_content, true},
+ {attributes, record_info(fields, ejabberd_commands)},
+ {type, bag}]),
+ mnesia:add_table_copy(ejabberd_commands, node(), ram_copies),
register_commands(get_commands_spec()).
-spec register_commands([ejabberd_commands()]) -> ok.
%% @doc Register ejabberd commands.
-%% If a command is already registered, a warning is printed and the old command is preserved.
+%% If a command is already registered, a warning is printed and the
+%% old command is preserved.
register_commands(Commands) ->
lists:foreach(
fun(Command) ->
- case ets:insert_new(ejabberd_commands, Command) of
- true ->
- ok;
- false ->
- ?DEBUG("This command is already defined:~n~p", [Command])
- end
+ % XXX check if command exists
+ mnesia:dirty_write(Command)
+ % ?DEBUG("This command is already defined:~n~p", [Command])
end,
Commands).
@@ -286,7 +301,7 @@ register_commands(Commands) ->
unregister_commands(Commands) ->
lists:foreach(
fun(Command) ->
- ets:delete_object(ejabberd_commands, Command)
+ mnesia:dirty_delete_object(Command)
end,
Commands).
@@ -294,94 +309,194 @@ unregister_commands(Commands) ->
%% @doc Get a list of all the available commands, arguments and description.
list_commands() ->
- Commands = ets:match(ejabberd_commands,
- #ejabberd_commands{name = '$1',
- args = '$2',
- desc = '$3',
- _ = '_'}),
- [{A, B, C} || [A, B, C] <- Commands].
-
--spec list_commands_policy() -> [{atom(), [aterm()], string(), atom()}].
-
-%% @doc Get a list of all the available commands, arguments, description, and
-%% policy.
-list_commands_policy() ->
- Commands = ets:match(ejabberd_commands,
- #ejabberd_commands{name = '$1',
- args = '$2',
- desc = '$3',
- policy = '$4',
- _ = '_'}),
- [{A, B, C, D} || [A, B, C, D] <- Commands].
-
--spec get_command_format(atom()) -> {[aterm()], rterm()} | {error, command_unknown}.
+ list_commands(?DEFAULT_VERSION).
+
+-spec list_commands(integer()) -> [{atom(), [aterm()], string()}].
+
+%% @doc Get a list of all the available commands, arguments and
+%% description in a given API verion.
+list_commands(Version) ->
+ Commands = get_commands_definition(Version),
+ [{Name, Args, Desc} || #ejabberd_commands{name = Name,
+ args = Args,
+ desc = Desc} <- Commands].
+
+
+-spec list_commands_policy(integer()) ->
+ [{atom(), [aterm()], string(), atom()}].
+
+%% @doc Get a list of all the available commands, arguments,
+%% description, and policy in a given API version.
+list_commands_policy(Version) ->
+ Commands = get_commands_definition(Version),
+ [{Name, Args, Desc, Policy} ||
+ #ejabberd_commands{name = Name,
+ args = Args,
+ desc = Desc,
+ policy = Policy} <- Commands].
+
+-spec get_command_format(atom()) -> {[aterm()], rterm()}.
%% @doc Get the format of arguments and result of a command.
get_command_format(Name) ->
- get_command_format(Name, noauth).
-
-get_command_format(Name, Auth) ->
+ get_command_format(Name, noauth, ?DEFAULT_VERSION).
+get_command_format(Name, Version) when is_integer(Version) ->
+ get_command_format(Name, noauth, Version);
+get_command_format(Name, Auth) ->
+ get_command_format(Name, Auth, ?DEFAULT_VERSION).
+
+-spec get_command_format(atom(),
+ {binary(), binary(), binary(), boolean()} |
+ noauth | admin,
+ integer()) ->
+ {[aterm()], rterm()}.
+
+get_command_format(Name, Auth, Version) ->
Admin = is_admin(Name, Auth),
- Matched = ets:match(ejabberd_commands,
- #ejabberd_commands{name = Name,
- args = '$1',
- result = '$2',
- policy = '$3',
- _ = '_'}),
- case Matched of
- [] ->
- {error, command_unknown};
- [[Args, Result, user]] when Admin;
- Auth == noauth ->
+ #ejabberd_commands{args = Args,
+ result = Result,
+ policy = Policy} =
+ get_command_definition(Name, Version),
+ case Policy of
+ user when Admin;
+ Auth == noauth ->
{[{user, binary}, {server, binary} | Args], Result};
- [[Args, Result, _]] ->
+ _ ->
{Args, Result}
end.
--spec get_command_definition(atom()) -> ejabberd_commands() | command_not_found.
+-spec get_command_policy(atom()) -> {ok, open|user|admin|restricted} | {error, command_not_found}.
+
+%% @doc return command policy.
+get_command_policy(Name) ->
+ case get_command_definition(Name) of
+ #ejabberd_commands{policy = Policy} ->
+ {ok, Policy};
+ command_not_found ->
+ {error, command_not_found}
+ end.
+
+-spec get_command_definition(atom()) -> ejabberd_commands().
%% @doc Get the definition record of a command.
get_command_definition(Name) ->
- case ets:lookup(ejabberd_commands, Name) of
- [E] -> E;
- [] -> command_not_found
+ get_command_definition(Name, ?DEFAULT_VERSION).
+
+-spec get_command_definition(atom(), integer()) -> ejabberd_commands().
+
+%% @doc Get the definition record of a command in a given API version.
+get_command_definition(Name, Version) ->
+ case lists:reverse(
+ lists:sort(
+ mnesia:dirty_select(
+ ejabberd_commands,
+ ets:fun2ms(
+ fun(#ejabberd_commands{name = N, version = V} = C)
+ when N == Name, V =< Version ->
+ {V, C}
+ end)))) of
+ [{_, Command} | _ ] -> Command;
+ _E -> throw(unknown_command)
end.
-%% @spec (Name::atom(), Arguments) -> ResultTerm | {error, command_unknown}
+-spec get_commands_definition(integer()) -> [ejabberd_commands()].
+
+% @doc Returns all commands for a given API version
+get_commands_definition(Version) ->
+ L = lists:reverse(
+ lists:sort(
+ mnesia:dirty_select(
+ ejabberd_commands,
+ ets:fun2ms(
+ fun(#ejabberd_commands{name = Name, version = V} = C)
+ when V =< Version ->
+ {Name, V, C}
+ end)))),
+ F = fun({_Name, _V, Command}, []) ->
+ [Command];
+ ({Name, _V, _Command}, [#ejabberd_commands{name=Name}|_T] = Acc) ->
+ Acc;
+ ({_Name, _V, Command}, Acc) -> [Command | Acc]
+ end,
+ lists:foldl(F, [], L).
+
+%% @spec (Name::atom(), Arguments) -> ResultTerm
+%% where
+%% Arguments = [any()]
%% @doc Execute a command.
+%% Can return the following exceptions:
+%% command_unknown | account_unprivileged | invalid_account_data |
+%% no_auth_provided
execute_command(Name, Arguments) ->
- execute_command([], noauth, Name, Arguments).
+ execute_command(Name, Arguments, ?DEFAULT_VERSION).
--spec execute_command([{atom(), [atom()], [any()]}],
+-spec execute_command(atom(),
+ [any()],
+ integer() |
+ {binary(), binary(), binary(), boolean()} |
+ noauth | admin
+ ) -> any().
+
+%% @spec (Name::atom(), Arguments, integer() | Auth) -> ResultTerm
+%% where
+%% Auth = {User::string(), Server::string(), Password::string(),
+%% Admin::boolean()}
+%% | noauth
+%% | admin
+%% Arguments = [any()]
+%%
+%% @doc Execute a command in a given API version
+%% Can return the following exceptions:
+%% command_unknown | account_unprivileged | invalid_account_data |
+%% no_auth_provided
+execute_command(Name, Arguments, Version) when is_integer(Version) ->
+ execute_command([], noauth, Name, Arguments, Version);
+execute_command(Name, Arguments, Auth) ->
+ execute_command([], Auth, Name, Arguments, ?DEFAULT_VERSION).
+
+%% @spec (AccessCommands, Auth, Name::atom(), Arguments) ->
+%% ResultTerm | {error, Error}
+%% where
+%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
+%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
+%% | noauth
+%% | admin
+%% Arguments = [any()]
+%%
+%% @doc Execute a command
+%% Can return the following exceptions:
+%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
+execute_command(AccessCommands, Auth, Name, Arguments) ->
+ execute_command(AccessCommands, Auth, Name, Arguments, ?DEFAULT_VERSION).
+
+-spec execute_command([{atom(), [atom()], [any()]}] | undefined,
{binary(), binary(), binary(), boolean()} |
noauth | admin,
atom(),
- [any()]
+ [any()],
+ integer()
) -> any().
-%% @spec (AccessCommands, Auth, Name::atom(), Arguments) -> ResultTerm | {error, Error}
+%% @spec (AccessCommands, Auth, Name::atom(), Arguments, integer()) -> ResultTerm
%% where
-%% AccessCommands = [{Access, CommandNames, Arguments}]
+%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
%% | noauth
%% | admin
-%% Method = atom()
%% Arguments = [any()]
-%% Error = command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
-execute_command(AccessCommands1, Auth1, Name, Arguments) ->
+%%
+%% @doc Execute a command in a given API version
+%% Can return the following exceptions:
+%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
+execute_command(AccessCommands1, Auth1, Name, Arguments, Version) ->
Auth = case is_admin(Name, Auth1) of
true -> admin;
false -> Auth1
end,
- case ets:lookup(ejabberd_commands, Name) of
- [Command] ->
- AccessCommands = get_access_commands(AccessCommands1),
- try check_access_commands(AccessCommands, Auth, Name, Command, Arguments) of
- ok -> execute_command2(Auth, Command, Arguments)
- catch
- {error, Error} -> {error, Error}
- end;
- [] -> {error, command_unknown}
+ Command = get_command_definition(Name, Version),
+ AccessCommands = get_access_commands(AccessCommands1, Version),
+ case check_access_commands(AccessCommands, Auth, Name, Command, Arguments) of
+ ok -> execute_command2(Auth, Command, Arguments)
end.
execute_command2(
@@ -407,26 +522,25 @@ execute_command2(Command, Arguments) ->
Module = Command#ejabberd_commands.module,
Function = Command#ejabberd_commands.function,
?DEBUG("Executing command ~p:~p with Args=~p", [Module, Function, Arguments]),
- try apply(Module, Function, Arguments) of
- Response ->
- Response
- catch
- Problem ->
- {error, Problem}
- end.
+ apply(Module, Function, Arguments).
-spec get_tags_commands() -> [{string(), [string()]}].
%% @spec () -> [{Tag::string(), [CommandName::string()]}]
%% @doc Get all the tags and associated commands.
get_tags_commands() ->
- CommandTags = ets:match(ejabberd_commands,
- #ejabberd_commands{
- name = '$1',
- tags = '$2',
- _ = '_'}),
+ get_tags_commands(?DEFAULT_VERSION).
+
+-spec get_tags_commands(integer()) -> [{string(), [string()]}].
+
+%% @spec (integer) -> [{Tag::string(), [CommandName::string()]}]
+%% @doc Get all the tags and associated commands in a given API version
+get_tags_commands(Version) ->
+ CommandTags = [{Name, Tags} ||
+ #ejabberd_commands{name = Name, tags = Tags}
+ <- get_commands_definition(Version)],
Dict = lists:foldl(
- fun([CommandNameAtom, CTags], D) ->
+ fun({CommandNameAtom, CTags}, D) ->
CommandName = atom_to_list(CommandNameAtom),
case CTags of
[] ->
@@ -445,7 +559,6 @@ get_tags_commands() ->
CommandTags),
orddict:to_list(Dict).
-
%% -----------------------------
%% Access verification
%% -----------------------------
@@ -465,7 +578,7 @@ check_access_commands([], _Auth, _Method, _Command, _Arguments) ->
check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
Command =
case {Command1#ejabberd_commands.policy, Auth} of
- {user, {_, _, _}} ->
+ {user, {_, _, _, _}} ->
Command1;
{user, _} ->
Command1#ejabberd_commands{
@@ -479,7 +592,8 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
fun({Access, Commands, ArgumentRestrictions}) ->
case check_access(Command, Access, Auth) of
true ->
- check_access_command(Commands, Command, ArgumentRestrictions,
+ check_access_command(Commands, Command,
+ ArgumentRestrictions,
Method, Arguments);
false ->
false
@@ -488,7 +602,8 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
ArgumentRestrictions = [],
case check_access(Command, Access, Auth) of
true ->
- check_access_command(Commands, Command, ArgumentRestrictions,
+ check_access_command(Commands, Command,
+ ArgumentRestrictions,
Method, Arguments);
false ->
false
@@ -517,7 +632,7 @@ check_auth(Command, {User, Server, {oauth, Token}, _}) ->
end;
check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) ->
%% Check the account exists and password is valid
- case ejabberd_auth:check_password(User, Server, Password) of
+ case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
true -> {ok, User, Server};
_ -> throw({error, invalid_account_data})
end.
@@ -551,9 +666,11 @@ check_access2(Access, User, Server) ->
deny -> false
end.
-check_access_command(Commands, Command, ArgumentRestrictions, Method, Arguments) ->
+check_access_command(Commands, Command, ArgumentRestrictions,
+ Method, Arguments) ->
case Commands==all orelse lists:member(Method, Commands) of
- true -> check_access_arguments(Command, ArgumentRestrictions, Arguments);
+ true -> check_access_arguments(Command, ArgumentRestrictions,
+ Arguments);
false -> false
end.
@@ -577,25 +694,28 @@ tag_arguments(ArgsDefs, Args) ->
Args).
-get_access_commands(undefined) ->
- Cmds = get_commands(),
+get_access_commands(undefined, Version) ->
+ Cmds = get_commands(Version),
[{?POLICY_ACCESS, Cmds, []}];
-get_access_commands(AccessCommands) ->
+get_access_commands(AccessCommands, _Version) ->
AccessCommands.
get_commands() ->
- Opts = ejabberd_config:get_option(
+ get_commands(?DEFAULT_VERSION).
+get_commands(Version) ->
+ Opts0 = ejabberd_config:get_option(
commands,
fun(V) when is_list(V) -> V end,
[]),
- CommandsList = list_commands_policy(),
+ Opts = lists:map(fun(V) when is_tuple(V) -> [V]; (V) -> V end, Opts0),
+ CommandsList = list_commands_policy(Version),
OpenCmds = [N || {N, _, _, open} <- CommandsList],
RestrictedCmds = [N || {N, _, _, restricted} <- CommandsList],
AdminCmds = [N || {N, _, _, admin} <- CommandsList],
UserCmds = [N || {N, _, _, user} <- CommandsList],
Cmds =
lists:foldl(
- fun({add_commands, L}, Acc) ->
+ fun([{add_commands, L}], Acc) ->
Cmds = case L of
open -> OpenCmds;
restricted -> RestrictedCmds;
@@ -604,7 +724,7 @@ get_commands() ->
_ when is_list(L) -> L
end,
lists:usort(Cmds ++ Acc);
- ({remove_commands, L}, Acc) ->
+ ([{remove_commands, L}], Acc) ->
Cmds = case L of
open -> OpenCmds;
restricted -> RestrictedCmds;
diff --git a/src/ejabberd_commands_doc.erl b/src/ejabberd_commands_doc.erl
index 277ed0c7..dc00c5d2 100644
--- a/src/ejabberd_commands_doc.erl
+++ b/src/ejabberd_commands_doc.erl
@@ -32,7 +32,7 @@
-include("ejabberd_commands.hrl").
-include("ejabberd.hrl").
--define(RAW(V), if HTMLOutput -> xml:crypt(iolist_to_binary(V)); true -> iolist_to_binary(V) end).
+-define(RAW(V), if HTMLOutput -> fxml:crypt(iolist_to_binary(V)); true -> iolist_to_binary(V) end).
-define(TAG(N), if HTMLOutput -> [<<"<", ??N, "/>">>]; true -> md_tag(N, <<"">>) end).
-define(TAG(N, V), if HTMLOutput -> [<<"<", ??N, ">">>, V, <<"</", ??N, ">">>]; true -> md_tag(N, V) end).
-define(TAG(N, C, V), if HTMLOutput -> [<<"<", ??N, " class='", C, "'>">>, V, <<"</", ??N, ">">>]; true -> md_tag(N, V) end).
@@ -79,7 +79,7 @@ md_tag(pre, V) ->
md_tag(p, V) ->
[<<"\n\n">>, V, <<"\n">>];
md_tag(h1, V) ->
- [<<"\\nn## ">>, V, <<"\n">>];
+ [<<"\n\n## ">>, V, <<"\n">>];
md_tag(h2, V) ->
[<<"\n\n### ">>, V, <<"\n">>];
md_tag(strong, V) ->
@@ -158,7 +158,7 @@ java_call(Name, ArgsDesc, Values, HTMLOutput) ->
Indent, ?ID_L("client"), ?OP_L("."), ?ID_L("setConfig"), ?OP_L("("), ?ID_L("config"), ?OP_L(");"), ?BR, Indent, ?BR,
Indent, ?ID_L("client"), ?OP_L("."), ?ID_L("execute"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "),
java_gen_map(lists:map(fun({A,B})->java_gen(A, B, Indent, HTMLOutput) end, lists:zip(ArgsDesc, Values)), Indent, HTMLOutput),
- ?OP_L(");"), ?BR].
+ ?OP_L(");")].
-define(XML_S(N, V), ?OP_L("<"), ?FIELD_L(??N), ?OP_L(">"), V).
-define(XML_E(N), ?OP_L("</"), ?FIELD_L(??N), ?OP_L(">")).
@@ -360,8 +360,9 @@ gen_doc(#ejabberd_commands{name=Name, tags=_Tags, desc=Desc, longdesc=LongDesc,
none ->
[?RAW(io_lib:format("~p", [Result]))];
_ ->
- [?RAW(io_lib:format("~p", [Result])),
- ?TAG_R(p, ResultDesc)]
+ [?TAG(dl, [
+ ?TAG(dt, io_lib:format("~p", [Result])),
+ ?TAG_R(dd, ResultDesc)])]
end,
[?TAG(h1, [?TAG(strong, atom_to_list(Name)), <<" - ">>, ?RAW(Desc)]),
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index 8d2d1997..f73474fe 100644
--- a/src/ejabberd_config.erl
+++ b/src/ejabberd_config.erl
@@ -38,6 +38,8 @@
convert_to_yaml/1, convert_to_yaml/2,
env_binary_to_list/2, opt_type/1, may_hide_data/1]).
+-export([start/2]).
+
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_config.hrl").
@@ -52,22 +54,12 @@
%% @type macro_value() = term().
-
start() ->
- case catch mnesia:table_info(local_config, storage_type) of
- disc_copies ->
- mnesia:delete_table(local_config);
- _ ->
- ok
- end,
- mnesia:create_table(local_config,
- [{ram_copies, [node()]},
- {local_content, true},
- {attributes, record_info(fields, local_config)}]),
- mnesia:add_table_copy(local_config, node(), ram_copies),
+ mnesia_init(),
Config = get_ejabberd_config_path(),
State0 = read_file(Config),
- State = validate_opts(State0),
+ State1 = hosts_to_start(State0),
+ State2 = validate_opts(State1),
%% This start time is used by mod_last:
UnixTime = p1_time_compat:system_time(seconds),
SharedKey = case erlang:get_cookie() of
@@ -76,9 +68,45 @@ start() ->
Cookie ->
p1_sha:sha(jlib:atom_to_binary(Cookie))
end,
- State1 = set_option({node_start, global}, UnixTime, State),
- State2 = set_option({shared_key, global}, SharedKey, State1),
- set_opts(State2).
+ State3 = set_option({node_start, global}, UnixTime, State2),
+ State4 = set_option({shared_key, global}, SharedKey, State3),
+ set_opts(State4).
+
+%% When starting ejabberd for testing, we sometimes want to start a
+%% subset of hosts from the one define in the config file.
+%% This function override the host list read from config file by the
+%% one we provide.
+%% Hosts to start are defined in an ejabberd application environment
+%% variable 'hosts' to make it easy to ignore some host in config
+%% file.
+hosts_to_start(State) ->
+ case application:get_env(ejabberd, hosts) of
+ undefined ->
+ %% Start all hosts as defined in config file
+ State;
+ {ok, Hosts} ->
+ set_hosts_in_options(Hosts, State)
+ end.
+
+%% @private
+%% At the moment, these functions are mainly used to setup unit tests.
+-spec(start/2 :: (Hosts :: [binary()], Opts :: [acl:acl() | local_config()]) -> ok).
+start(Hosts, Opts) ->
+ mnesia_init(),
+ set_opts(set_hosts_in_options(Hosts, #state{opts = Opts})).
+
+mnesia_init() ->
+ case catch mnesia:table_info(local_config, storage_type) of
+ disc_copies ->
+ mnesia:delete_table(local_config);
+ _ ->
+ ok
+ end,
+ mnesia:create_table(local_config,
+ [{ram_copies, [node()]},
+ {local_content, true},
+ {attributes, record_info(fields, local_config)}]),
+ mnesia:add_table_copy(local_config, node(), ram_copies).
%% @doc Get the filename of the ejabberd configuration file.
%% The filename can be specified with: erl -config "/path/to/ejabberd.yml".
@@ -112,7 +140,7 @@ get_env_config() ->
%% @doc Read the ejabberd configuration file.
%% It also includes additional configuration files and replaces macros.
%% This function will crash if finds some error in the configuration file.
-%% @spec (File::string()) -> #state{}.
+%% @spec (File::string()) -> #state{}
read_file(File) ->
read_file(File, [{replace_macros, true},
{include_files, true},
@@ -162,7 +190,7 @@ convert_to_yaml(File, Output) ->
fun({Host, Opts1}) ->
{host_config, [{Host, Opts1}]}
end, HOpts),
- Data = p1_yaml:encode(lists:reverse(NewOpts)),
+ Data = fast_yaml:encode(lists:reverse(NewOpts)),
case Output of
stdout ->
io:format("~s~n", [Data]);
@@ -226,14 +254,14 @@ get_plain_terms_file(File1, Opts) ->
consult(File) ->
case filename:extension(File) of
Ex when (Ex == ".yml") or (Ex == ".yaml") ->
- case p1_yaml:decode_from_file(File, [plain_as_atom]) of
+ case fast_yaml:decode_from_file(File, [plain_as_atom]) of
{ok, []} ->
{ok, []};
{ok, [Document|_]} ->
{ok, parserl(Document)};
{error, Err} ->
Msg1 = "Cannot load " ++ File ++ ": ",
- Msg2 = p1_yaml:format_error(Err),
+ Msg2 = fast_yaml:format_error(Err),
{error, Msg1 ++ Msg2}
end;
_ ->
@@ -277,7 +305,7 @@ search_hosts(Term, State) ->
{host, Host} ->
if
State#state.hosts == [] ->
- add_hosts_to_option([Host], State);
+ set_hosts_in_options([Host], State);
true ->
?ERROR_MSG("Can't load config file: "
"too many hosts definitions", []),
@@ -286,7 +314,7 @@ search_hosts(Term, State) ->
{hosts, Hosts} ->
if
State#state.hosts == [] ->
- add_hosts_to_option(Hosts, State);
+ set_hosts_in_options(Hosts, State);
true ->
?ERROR_MSG("Can't load config file: "
"too many hosts definitions", []),
@@ -296,9 +324,12 @@ search_hosts(Term, State) ->
State
end.
-add_hosts_to_option(Hosts, State) ->
+set_hosts_in_options(Hosts, State) ->
PrepHosts = normalize_hosts(Hosts),
- set_option({hosts, global}, PrepHosts, State#state{hosts = PrepHosts}).
+ NewOpts = lists:filter(fun({local_config,{hosts,global},_}) -> false;
+ (_) -> true
+ end, State#state.opts),
+ set_option({hosts, global}, PrepHosts, State#state{hosts = PrepHosts, opts = NewOpts}).
normalize_hosts(Hosts) ->
normalize_hosts(Hosts,[]).
@@ -408,7 +439,7 @@ maps_to_lists(IMap) ->
end, [], IMap).
merge_configs(Terms, ResMap) ->
- lists:foldl(fun({Name, Val}, Map) when is_list(Val) ->
+ lists:foldl(fun({Name, Val}, Map) when is_list(Val), Name =/= auth_method ->
Old = maps:get(Name, Map, #{}),
New = lists:foldl(fun(SVal, OMap) ->
NVal = if Name == host_config orelse Name == append_host_config ->
@@ -620,14 +651,27 @@ process_host_term(Term, Host, State, Action) ->
{hosts, _} ->
State;
{Opt, Val} when Action == set ->
- set_option({Opt, Host}, Val, State);
+ set_option({rename_option(Opt), Host}, Val, State);
{Opt, Val} when Action == append ->
- append_option({Opt, Host}, Val, State);
+ append_option({rename_option(Opt), Host}, Val, State);
Opt ->
?WARNING_MSG("Ignore invalid (outdated?) option ~p", [Opt]),
State
end.
+rename_option(Option) when is_atom(Option) ->
+ case atom_to_list(Option) of
+ "odbc_" ++ T ->
+ NewOption = list_to_atom("sql_" ++ T),
+ ?WARNING_MSG("Option '~s' is obsoleted, use '~s' instead",
+ [Option, NewOption]),
+ NewOption;
+ _ ->
+ Option
+ end;
+rename_option(Option) ->
+ Option.
+
set_option(Opt, Val, State) ->
State#state{opts = [#local_config{key = Opt, value = Val} |
State#state.opts]}.
@@ -836,20 +880,20 @@ get_mylang() ->
fun iolist_to_binary/1,
<<"en">>).
-replace_module(mod_announce_odbc) -> {mod_announce, odbc};
-replace_module(mod_blocking_odbc) -> {mod_blocking, odbc};
-replace_module(mod_caps_odbc) -> {mod_caps, odbc};
-replace_module(mod_irc_odbc) -> {mod_irc, odbc};
-replace_module(mod_last_odbc) -> {mod_last, odbc};
-replace_module(mod_muc_odbc) -> {mod_muc, odbc};
-replace_module(mod_offline_odbc) -> {mod_offline, odbc};
-replace_module(mod_privacy_odbc) -> {mod_privacy, odbc};
-replace_module(mod_private_odbc) -> {mod_private, odbc};
-replace_module(mod_roster_odbc) -> {mod_roster, odbc};
-replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, odbc};
-replace_module(mod_vcard_odbc) -> {mod_vcard, odbc};
-replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, odbc};
-replace_module(mod_pubsub_odbc) -> {mod_pubsub, odbc};
+replace_module(mod_announce_odbc) -> {mod_announce, sql};
+replace_module(mod_blocking_odbc) -> {mod_blocking, sql};
+replace_module(mod_caps_odbc) -> {mod_caps, sql};
+replace_module(mod_irc_odbc) -> {mod_irc, sql};
+replace_module(mod_last_odbc) -> {mod_last, sql};
+replace_module(mod_muc_odbc) -> {mod_muc, sql};
+replace_module(mod_offline_odbc) -> {mod_offline, sql};
+replace_module(mod_privacy_odbc) -> {mod_privacy, sql};
+replace_module(mod_private_odbc) -> {mod_private, sql};
+replace_module(mod_roster_odbc) -> {mod_roster, sql};
+replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, sql};
+replace_module(mod_vcard_odbc) -> {mod_vcard, sql};
+replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, sql};
+replace_module(mod_pubsub_odbc) -> {mod_pubsub, sql};
replace_module(Module) ->
case is_elixir_module(Module) of
true -> expand_elixir_module(Module);
@@ -954,7 +998,7 @@ transform_terms(Terms) ->
mod_last,
ejabberd_s2s,
ejabberd_listener,
- ejabberd_odbc_sup,
+ ejabberd_sql_sup,
shaper,
ejabberd_s2s_out,
acl,
diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl
index 2bf20c2e..bb16e91e 100644
--- a/src/ejabberd_ctl.erl
+++ b/src/ejabberd_ctl.erl
@@ -57,6 +57,8 @@
-include("ejabberd.hrl").
-include("logger.hrl").
+-define(DEFAULT_VERSION, 1000000).
+
%%-----------------------------
%% Module
@@ -69,7 +71,7 @@ start() ->
[SNode3 | Args3] ->
[SNode3, 60000, Args3];
_ ->
- print_usage(),
+ print_usage(?DEFAULT_VERSION),
halt(?STATUS_USAGE)
end,
SNode1 = case string:tokens(SNode, "@") of
@@ -93,6 +95,9 @@ start() ->
[Node, Reason]),
%% TODO: show minimal start help
?STATUS_BADRPC;
+ {invalid_version, V} ->
+ print("Invalid API version number: ~p~n", [V]),
+ ?STATUS_ERROR;
S ->
S
end,
@@ -126,11 +131,17 @@ unregister_commands(CmdDescs, Module, Function) ->
%% Process
%%-----------------------------
+
-spec process([string()]) -> non_neg_integer().
+process(Args) ->
+ process(Args, ?DEFAULT_VERSION).
+
+
+-spec process([string()], non_neg_integer()) -> non_neg_integer().
%% The commands status, stop and restart are defined here to ensure
%% they are usable even if ejabberd is completely stopped.
-process(["status"]) ->
+process(["status"], _Version) ->
{InternalStatus, ProvidedStatus} = init:get_status(),
print("The node ~p is ~p with status: ~p~n",
[node(), InternalStatus, ProvidedStatus]),
@@ -146,24 +157,24 @@ process(["status"]) ->
?STATUS_SUCCESS
end;
-process(["stop"]) ->
+process(["stop"], _Version) ->
%%ejabberd_cover:stop(),
init:stop(),
?STATUS_SUCCESS;
-process(["restart"]) ->
+process(["restart"], _Version) ->
init:restart(),
?STATUS_SUCCESS;
-process(["mnesia"]) ->
+process(["mnesia"], _Version) ->
print("~p~n", [mnesia:system_info(all)]),
?STATUS_SUCCESS;
-process(["mnesia", "info"]) ->
+process(["mnesia", "info"], _Version) ->
mnesia:info(),
?STATUS_SUCCESS;
-process(["mnesia", Arg]) ->
+process(["mnesia", Arg], _Version) ->
case catch mnesia:system_info(list_to_atom(Arg)) of
{'EXIT', Error} -> print("Error: ~p~n", [Error]);
Return -> print("~p~n", [Return])
@@ -172,23 +183,23 @@ process(["mnesia", Arg]) ->
%% The arguments --long and --dual are not documented because they are
%% automatically selected depending in the number of columns of the shell
-process(["help" | Mode]) ->
+process(["help" | Mode], Version) ->
{MaxC, ShCode} = get_shell_info(),
case Mode of
[] ->
- print_usage(dual, MaxC, ShCode),
+ print_usage(dual, MaxC, ShCode, Version),
?STATUS_USAGE;
["--dual"] ->
- print_usage(dual, MaxC, ShCode),
+ print_usage(dual, MaxC, ShCode, Version),
?STATUS_USAGE;
["--long"] ->
- print_usage(long, MaxC, ShCode),
+ print_usage(long, MaxC, ShCode, Version),
?STATUS_USAGE;
["--tags"] ->
- print_usage_tags(MaxC, ShCode),
+ print_usage_tags(MaxC, ShCode, Version),
?STATUS_SUCCESS;
["--tags", Tag] ->
- print_usage_tags(Tag, MaxC, ShCode),
+ print_usage_tags(Tag, MaxC, ShCode, Version),
?STATUS_SUCCESS;
["help"] ->
print_usage_help(MaxC, ShCode),
@@ -196,13 +207,22 @@ process(["help" | Mode]) ->
[CmdString | _] ->
CmdStringU = ejabberd_regexp:greplace(
list_to_binary(CmdString), <<"-">>, <<"_">>),
- print_usage_commands(binary_to_list(CmdStringU), MaxC, ShCode),
+ print_usage_commands2(binary_to_list(CmdStringU), MaxC, ShCode, Version),
?STATUS_SUCCESS
end;
-process(Args) ->
+process(["--version", Arg | Args], _) ->
+ Version =
+ try
+ list_to_integer(Arg)
+ catch _:_ ->
+ throw({invalid_version, Arg})
+ end,
+ process(Args, Version);
+
+process(Args, Version) ->
AccessCommands = get_accesscommands(),
- {String, Code} = process2(Args, AccessCommands),
+ {String, Code} = process2(Args, AccessCommands, Version),
case String of
[] -> ok;
_ ->
@@ -211,18 +231,25 @@ process(Args) ->
Code.
%% @spec (Args::[string()], AccessCommands) -> {String::string(), Code::integer()}
-process2(["--auth", User, Server, Pass | Args], AccessCommands) ->
- process2(Args, {list_to_binary(User), list_to_binary(Server), list_to_binary(Pass), true}, AccessCommands);
process2(Args, AccessCommands) ->
- process2(Args, admin, AccessCommands).
+ process2(Args, AccessCommands, ?DEFAULT_VERSION).
+
+%% @spec (Args::[string()], AccessCommands, Version) -> {String::string(), Code::integer()}
+process2(["--auth", User, Server, Pass | Args], AccessCommands, Version) ->
+ process2(Args, AccessCommands, {list_to_binary(User), list_to_binary(Server),
+ list_to_binary(Pass), true}, Version);
+process2(Args, AccessCommands, Version) ->
+ process2(Args, AccessCommands, admin, Version).
+
+
-process2(Args, Auth, AccessCommands) ->
- case try_run_ctp(Args, Auth, AccessCommands) of
+process2(Args, AccessCommands, Auth, Version) ->
+ case try_run_ctp(Args, Auth, AccessCommands, Version) of
{String, wrong_command_arguments}
when is_list(String) ->
io:format(lists:flatten(["\n" | String]++["\n"])),
[CommandString | _] = Args,
- process(["help" | [CommandString]]),
+ process(["help" | [CommandString]], Version),
{lists:flatten(String), ?STATUS_ERROR};
{String, Code}
when is_list(String) and is_integer(Code) ->
@@ -246,29 +273,29 @@ get_accesscommands() ->
%%-----------------------------
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()}
-try_run_ctp(Args, Auth, AccessCommands) ->
+try_run_ctp(Args, Auth, AccessCommands, Version) ->
try ejabberd_hooks:run_fold(ejabberd_ctl_process, false, [Args]) of
false when Args /= [] ->
- try_call_command(Args, Auth, AccessCommands);
+ try_call_command(Args, Auth, AccessCommands, Version);
false ->
- print_usage(),
+ print_usage(Version),
{"", ?STATUS_USAGE};
Status ->
{"", Status}
catch
exit:Why ->
- print_usage(),
+ print_usage(Version),
{io_lib:format("Error in ejabberd ctl process: ~p", [Why]), ?STATUS_USAGE};
Error:Why ->
%% In this case probably ejabberd is not started, so let's show Status
- process(["status"]),
+ process(["status"], Version),
print("~n", []),
{io_lib:format("Error in ejabberd ctl process: '~p' ~p", [Error, Why]), ?STATUS_USAGE}
end.
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()}
-try_call_command(Args, Auth, AccessCommands) ->
- try call_command(Args, Auth, AccessCommands) of
+try_call_command(Args, Auth, AccessCommands, Version) ->
+ try call_command(Args, Auth, AccessCommands, Version) of
{error, command_unknown} ->
{io_lib:format("Error: command ~p not known.", [hd(Args)]), ?STATUS_ERROR};
{error, wrong_command_arguments} ->
@@ -276,24 +303,28 @@ try_call_command(Args, Auth, AccessCommands) ->
Res ->
Res
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}
end.
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType}
-call_command([CmdString | Args], Auth, AccessCommands) ->
+call_command([CmdString | Args], Auth, AccessCommands, Version) ->
CmdStringU = ejabberd_regexp:greplace(
list_to_binary(CmdString), <<"-">>, <<"_">>),
Command = list_to_atom(binary_to_list(CmdStringU)),
- case ejabberd_commands:get_command_format(Command, Auth) of
+ case ejabberd_commands:get_command_format(Command, Auth, Version) of
{error, command_unknown} ->
{error, command_unknown};
{ArgsFormat, ResultFormat} ->
case (catch format_args(Args, ArgsFormat)) of
ArgsFormatted when is_list(ArgsFormatted) ->
- Result = ejabberd_commands:execute_command(AccessCommands, Auth, Command,
- ArgsFormatted),
+ Result = ejabberd_commands:execute_command(AccessCommands,
+ Auth, Command,
+ ArgsFormatted,
+ Version),
format_result(Result, ResultFormat);
{'EXIT', {function_clause,[{lists,zip,[A1, A2], _} | _]}} ->
{NumCompa, TextCompa} =
@@ -324,7 +355,7 @@ format_args(Args, ArgsFormat) ->
format_arg(Arg, integer) ->
format_arg2(Arg, "~d");
format_arg(Arg, binary) ->
- list_to_binary(format_arg(Arg, string));
+ unicode:characters_to_binary(Arg, utf8);
format_arg("", string) ->
"";
format_arg(Arg, string) ->
@@ -349,7 +380,7 @@ format_result(Atom, {_Name, atom}) ->
format_result(Int, {_Name, integer}) ->
io_lib:format("~p", [Int]);
-format_result(String, {_Name, string}) when is_list(String) ->
+format_result([A|_]=String, {_Name, string}) when is_list(String) and is_integer(A) ->
io_lib:format("~s", [String]);
format_result(Binary, {_Name, string}) when is_binary(Binary) ->
@@ -404,8 +435,8 @@ make_status(ok) -> ?STATUS_SUCCESS;
make_status(true) -> ?STATUS_SUCCESS;
make_status(_Error) -> ?STATUS_ERROR.
-get_list_commands() ->
- try ejabberd_commands:list_commands() of
+get_list_commands(Version) ->
+ try ejabberd_commands:list_commands(Version) of
Commands ->
[tuple_command_help(Command)
|| {N,_,_}=Command <- Commands,
@@ -458,10 +489,10 @@ get_list_ctls() ->
-define(U2, "\e[24m").
-define(U(S), case ShCode of true -> [?U1, S, ?U2]; false -> S end).
-print_usage() ->
+print_usage(Version) ->
{MaxC, ShCode} = get_shell_info(),
- print_usage(dual, MaxC, ShCode).
-print_usage(HelpMode, MaxC, ShCode) ->
+ print_usage(dual, MaxC, ShCode, Version).
+print_usage(HelpMode, MaxC, ShCode, Version) ->
AllCommands =
[
{"status", [], "Get ejabberd status"},
@@ -469,12 +500,11 @@ print_usage(HelpMode, MaxC, ShCode) ->
{"restart", [], "Restart ejabberd"},
{"help", ["[--tags [tag] | com?*]"], "Show help (try: ejabberdctl help help)"},
{"mnesia", ["[info]"], "show information of Mnesia system"}] ++
- get_list_commands() ++
+ get_list_commands(Version) ++
get_list_ctls(),
print(
- ["Usage: ", ?B("ejabberdctl"), " [--no-timeout] [--node ", ?U("nodename"), "] [--auth ",
- ?U("user"), " ", ?U("host"), " ", ?U("password"), "] ",
+ ["Usage: ", ?B("ejabberdctl"), " [--no-timeout] [--node ", ?U("nodename"), "] [--version ", ?U("api_version"), "] ",
?U("command"), " [", ?U("options"), "]\n"
"\n"
"Available commands in this ejabberd node:\n"], []),
@@ -598,9 +628,9 @@ format_command_lines(CALD, _MaxCmdLen, MaxC, ShCode, long) ->
%% Print Tags
%%-----------------------------
-print_usage_tags(MaxC, ShCode) ->
+print_usage_tags(MaxC, ShCode, Version) ->
print("Available tags and commands:", []),
- TagsCommands = ejabberd_commands:get_tags_commands(),
+ TagsCommands = ejabberd_commands:get_tags_commands(Version),
lists:foreach(
fun({Tag, Commands} = _TagCommands) ->
print(["\n\n ", ?B(Tag), "\n "], []),
@@ -611,10 +641,10 @@ print_usage_tags(MaxC, ShCode) ->
TagsCommands),
print("\n\n", []).
-print_usage_tags(Tag, MaxC, ShCode) ->
+print_usage_tags(Tag, MaxC, ShCode, Version) ->
print(["Available commands with tag ", ?B(Tag), ":", "\n"], []),
HelpMode = long,
- TagsCommands = ejabberd_commands:get_tags_commands(),
+ TagsCommands = ejabberd_commands:get_tags_commands(Version),
CommandsNames = case lists:keysearch(Tag, 1, TagsCommands) of
{value, {Tag, CNs}} -> CNs;
false -> []
@@ -622,7 +652,7 @@ print_usage_tags(Tag, MaxC, ShCode) ->
CommandsList = lists:map(
fun(NameString) ->
C = ejabberd_commands:get_command_definition(
- list_to_atom(NameString)),
+ list_to_atom(NameString), Version),
#ejabberd_commands{name = Name,
args = Args,
desc = Desc} = C,
@@ -673,20 +703,20 @@ print_usage_help(MaxC, ShCode) ->
%%-----------------------------
%% @spec (CmdSubString::string(), MaxC::integer(), ShCode::boolean()) -> ok
-print_usage_commands(CmdSubString, MaxC, ShCode) ->
+print_usage_commands2(CmdSubString, MaxC, ShCode, Version) ->
%% Get which command names match this substring
- AllCommandsNames = [atom_to_list(Name) || {Name, _, _} <- ejabberd_commands:list_commands()],
+ AllCommandsNames = [atom_to_list(Name) || {Name, _, _} <- ejabberd_commands:list_commands(Version)],
Cmds = filter_commands(AllCommandsNames, CmdSubString),
case Cmds of
- [] -> io:format("Error: not command found that match: ~p~n", [CmdSubString]);
- _ -> print_usage_commands2(lists:sort(Cmds), MaxC, ShCode)
+ [] -> io:format("Error: no command found that match: ~p~n", [CmdSubString]);
+ _ -> print_usage_commands3(lists:sort(Cmds), MaxC, ShCode, Version)
end.
-print_usage_commands2(Cmds, MaxC, ShCode) ->
+print_usage_commands3(Cmds, MaxC, ShCode, Version) ->
%% Then for each one print it
lists:mapfoldl(
fun(Cmd, Remaining) ->
- print_usage_command(Cmd, MaxC, ShCode),
+ print_usage_command(Cmd, MaxC, ShCode, Version),
case Remaining > 1 of
true -> print([" ", lists:duplicate(MaxC, 126), " \n"], []);
false -> ok
@@ -716,16 +746,16 @@ filter_commands_regexp(All, Glob) ->
All).
%% @spec (Cmd::string(), MaxC::integer(), ShCode::boolean()) -> ok
-print_usage_command(Cmd, MaxC, ShCode) ->
+print_usage_command(Cmd, MaxC, ShCode, Version) ->
Name = list_to_atom(Cmd),
- case ejabberd_commands:get_command_definition(Name) of
+ case ejabberd_commands:get_command_definition(Name, Version) of
command_not_found ->
io:format("Error: command ~p not known.~n", [Cmd]);
C ->
- print_usage_command(Cmd, C, MaxC, ShCode)
+ print_usage_command2(Cmd, C, MaxC, ShCode)
end.
-print_usage_command(Cmd, C, MaxC, ShCode) ->
+print_usage_command2(Cmd, C, MaxC, ShCode) ->
#ejabberd_commands{
tags = TagsAtoms,
desc = Desc,
diff --git a/src/ejabberd_frontend_socket.erl b/src/ejabberd_frontend_socket.erl
index 09eeded9..b8e706f2 100644
--- a/src/ejabberd_frontend_socket.erl
+++ b/src/ejabberd_frontend_socket.erl
@@ -148,20 +148,20 @@ init([Module, SockMod, Socket, Opts, Receiver]) ->
receiver = Receiver}}.
handle_call({starttls, TLSOpts}, _From, State) ->
- {ok, TLSSocket} = p1_tls:tcp_to_tls(State#state.socket, TLSOpts),
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(State#state.socket, TLSOpts),
ejabberd_receiver:starttls(State#state.receiver, TLSSocket),
Reply = ok,
- {reply, Reply, State#state{socket = TLSSocket, sockmod = p1_tls},
+ {reply, Reply, State#state{socket = TLSSocket, sockmod = fast_tls},
?HIBERNATE_TIMEOUT};
handle_call({starttls, TLSOpts, Data}, _From, State) ->
- {ok, TLSSocket} = p1_tls:tcp_to_tls(State#state.socket, TLSOpts),
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(State#state.socket, TLSOpts),
ejabberd_receiver:starttls(State#state.receiver, TLSSocket),
catch (State#state.sockmod):send(
State#state.socket, Data),
Reply = ok,
{reply, Reply,
- State#state{socket = TLSSocket, sockmod = p1_tls},
+ State#state{socket = TLSSocket, sockmod = fast_tls},
?HIBERNATE_TIMEOUT};
handle_call({compress, Data}, _From, State) ->
{ok, ZlibSocket} =
@@ -187,10 +187,10 @@ handle_call(get_sockmod, _From, State) ->
Reply = State#state.sockmod,
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_peer_certificate, _From, State) ->
- Reply = p1_tls:get_peer_certificate(State#state.socket),
+ Reply = fast_tls:get_peer_certificate(State#state.socket),
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_verify_result, _From, State) ->
- Reply = p1_tls:get_verify_result(State#state.socket),
+ Reply = fast_tls:get_verify_result(State#state.socket),
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(close, _From, State) ->
ejabberd_receiver:close(State#state.receiver),
@@ -236,9 +236,9 @@ check_starttls(SockMod, Socket, Receiver, Opts) ->
(_) -> false
end, Opts),
if TLSEnabled ->
- {ok, TLSSocket} = p1_tls:tcp_to_tls(Socket, TLSOpts),
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(Socket, TLSOpts),
ejabberd_receiver:starttls(Receiver, TLSSocket),
- {p1_tls, TLSSocket};
+ {fast_tls, TLSSocket};
true ->
{SockMod, Socket}
end.
diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl
index b95f9820..6b53f46c 100644
--- a/src/ejabberd_http.erl
+++ b/src/ejabberd_http.erl
@@ -117,9 +117,9 @@ init({SockMod, Socket}, Opts) ->
TLSOpts = [verify_none | TLSOpts3],
{SockMod1, Socket1} = if TLSEnabled ->
inet:setopts(Socket, [{recbuf, 8192}]),
- {ok, TLSSocket} = p1_tls:tcp_to_tls(Socket,
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(Socket,
TLSOpts),
- {p1_tls, TLSSocket};
+ {fast_tls, TLSSocket};
true -> {SockMod, Socket}
end,
Captcha = case proplists:get_bool(captcha, Opts) of
@@ -328,8 +328,8 @@ get_transfer_protocol(SockMod, HostPort) ->
{gen_tcp, []} -> {Host, 80, http};
{gen_tcp, [Port]} ->
{Host, jlib:binary_to_integer(Port), http};
- {p1_tls, []} -> {Host, 443, https};
- {p1_tls, [Port]} ->
+ {fast_tls, []} -> {Host, 443, https};
+ {fast_tls, [Port]} ->
{Host, jlib:binary_to_integer(Port), https}
end.
@@ -532,10 +532,10 @@ make_xhtml_output(State, Status, Headers, XHTML) ->
Data = case lists:member(html, Headers) of
true ->
iolist_to_binary([?HTML_DOCTYPE,
- xml:element_to_binary(XHTML)]);
+ fxml:element_to_binary(XHTML)]);
_ ->
iolist_to_binary([?XHTML_DOCTYPE,
- xml:element_to_binary(XHTML)])
+ fxml:element_to_binary(XHTML)])
end,
Headers1 = case lists:keysearch(<<"Content-Type">>, 1,
Headers)
@@ -753,6 +753,7 @@ code_to_phrase(503) -> <<"Service Unavailable">>;
code_to_phrase(504) -> <<"Gateway Timeout">>;
code_to_phrase(505) -> <<"HTTP Version Not Supported">>.
+-spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | undefined.
parse_auth(<<"Basic ", Auth64/binary>>) ->
Auth = jlib:decode_base64(Auth64),
%% Auth should be a string with the format: user@server:password
diff --git a/src/ejabberd_http_bind.erl b/src/ejabberd_http_bind.erl
index c4cfdddd..ea8cd792 100644
--- a/src/ejabberd_http_bind.erl
+++ b/src/ejabberd_http_bind.erl
@@ -224,7 +224,7 @@ process_request(Data, IP, HOpts) ->
of
%% No existing session:
{ok, {<<"">>, Rid, Attrs, Payload}} ->
- case xml:get_attr_s(<<"to">>, Attrs) of
+ case fxml:get_attr_s(<<"to">>, Attrs) of
<<"">> ->
?DEBUG("Session not created (Improper addressing)", []),
{200, ?HEADER,
@@ -248,13 +248,13 @@ process_request(Data, IP, HOpts) ->
end;
%% Existing session
{ok, {Sid, Rid, Attrs, Payload1}} ->
- StreamStart = case xml:get_attr_s(<<"xmpp:restart">>,
+ StreamStart = case fxml:get_attr_s(<<"xmpp:restart">>,
Attrs)
of
<<"true">> -> true;
_ -> false
end,
- Payload2 = case xml:get_attr_s(<<"type">>, Attrs) of
+ Payload2 = case fxml:get_attr_s(<<"type">>, Attrs) of
<<"terminate">> ->
Payload1 ++ [{xmlstreamend, <<"stream:stream">>}];
_ -> Payload1
@@ -280,7 +280,7 @@ process_request(Data, IP, HOpts) ->
handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
Payload, PayloadSize, IP) ->
?DEBUG("got pid: ~p", [Pid]),
- Wait = case str:to_integer(xml:get_attr_s(<<"wait">>,
+ Wait = case str:to_integer(fxml:get_attr_s(<<"wait">>,
Attrs))
of
{error, _} -> ?MAX_WAIT;
@@ -289,7 +289,7 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
true -> CWait
end
end,
- Hold = case str:to_integer(xml:get_attr_s(<<"hold">>,
+ Hold = case str:to_integer(fxml:get_attr_s(<<"hold">>,
Attrs))
of
{error, _} -> (?MAX_REQUESTS) - 1;
@@ -299,7 +299,7 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
end
end,
Pdelay = case
- str:to_integer(xml:get_attr_s(<<"process-delay">>,
+ str:to_integer(fxml:get_attr_s(<<"process-delay">>,
Attrs))
of
{error, _} -> ?PROCESS_DELAY_DEFAULT;
@@ -312,12 +312,12 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
?PROCESS_DELAY_MIN])
end,
Version = case catch
- list_to_float(binary_to_list(xml:get_attr_s(<<"ver">>, Attrs)))
+ list_to_float(binary_to_list(fxml:get_attr_s(<<"ver">>, Attrs)))
of
{'EXIT', _} -> 0.0;
V -> V
end,
- XmppVersion = xml:get_attr_s(<<"xmpp:version">>, Attrs),
+ XmppVersion = fxml:get_attr_s(<<"xmpp:version">>, Attrs),
?DEBUG("Create session: ~p", [Sid]),
mnesia:dirty_write(
#http_bind{id = Sid,
@@ -589,8 +589,8 @@ process_http_put(#http_put{rid = Rid, attrs = Attrs,
Request,
StateName, StateData, RidAllow) ->
?DEBUG("Actually processing request: ~p", [Request]),
- Key = xml:get_attr_s(<<"key">>, Attrs),
- NewKey = xml:get_attr_s(<<"newkey">>, Attrs),
+ Key = fxml:get_attr_s(<<"key">>, Attrs),
+ NewKey = fxml:get_attr_s(<<"newkey">>, Attrs),
KeyAllow = case RidAllow of
repeat -> true;
false -> false;
@@ -801,7 +801,7 @@ handle_http_put_error(Reason,
case Reason of
not_exists ->
{200, ?HEADER,
- xml:element_to_binary(#xmlel{name = <<"body">>,
+ fxml:element_to_binary(#xmlel{name = <<"body">>,
attrs =
[{<<"xmlns">>, ?NS_HTTP_BIND},
{<<"type">>, <<"terminate">>},
@@ -810,7 +810,7 @@ handle_http_put_error(Reason,
children = []})};
bad_key ->
{200, ?HEADER,
- xml:element_to_binary(#xmlel{name = <<"body">>,
+ fxml:element_to_binary(#xmlel{name = <<"body">>,
attrs =
[{<<"xmlns">>, ?NS_HTTP_BIND},
{<<"type">>, <<"terminate">>},
@@ -819,7 +819,7 @@ handle_http_put_error(Reason,
children = []})};
polling_too_frequently ->
{200, ?HEADER,
- xml:element_to_binary(#xmlel{name = <<"body">>,
+ fxml:element_to_binary(#xmlel{name = <<"body">>,
attrs =
[{<<"xmlns">>, ?NS_HTTP_BIND},
{<<"type">>, <<"terminate">>},
@@ -855,7 +855,7 @@ rid_allow(OldRid, NewRid, Attrs, Hold, MaxPause) ->
%% We did not miss any packet, we can process it immediately:
NewRid == OldRid + 1 ->
case catch
- jlib:binary_to_integer(xml:get_attr_s(<<"pause">>,
+ jlib:binary_to_integer(fxml:get_attr_s(<<"pause">>,
Attrs))
of
{'EXIT', _} -> {true, 0};
@@ -929,9 +929,9 @@ prepare_outpacket_response(#http_bind{id = Sid,
_Rid, OutPacket, true) ->
case OutPacket of
[{xmlstreamstart, _, OutAttrs} | Els] ->
- AuthID = xml:get_attr_s(<<"id">>, OutAttrs),
- From = xml:get_attr_s(<<"from">>, OutAttrs),
- Version = xml:get_attr_s(<<"version">>, OutAttrs),
+ AuthID = fxml:get_attr_s(<<"id">>, OutAttrs),
+ From = fxml:get_attr_s(<<"from">>, OutAttrs),
+ Version = fxml:get_attr_s(<<"version">>, OutAttrs),
OutEls = case Els of
[] -> [];
[{xmlstreamelement,
@@ -968,7 +968,7 @@ prepare_outpacket_response(#http_bind{id = Sid,
MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY),
MaxPause = get_max_pause(To),
{200, ?HEADER,
- xml:element_to_binary(#xmlel{name = <<"body">>,
+ fxml:element_to_binary(#xmlel{name = <<"body">>,
attrs =
[{<<"xmlns">>, ?NS_HTTP_BIND},
{<<"sid">>, Sid},
@@ -1032,7 +1032,7 @@ send_outpacket(#http_bind{pid = FsmRef}, OutPacket) ->
TypedEls = lists:foldl(fun ({xmlstreamelement, El},
Acc) ->
Acc ++
- [xml:element_to_binary(check_default_xmlns(El))];
+ [fxml:element_to_binary(check_default_xmlns(El))];
({xmlstreamraw, R}, Acc) ->
Acc ++ [R]
end,
@@ -1067,7 +1067,7 @@ send_outpacket(#http_bind{pid = FsmRef}, OutPacket) ->
|| {xmlstreamelement, OEl} <- StreamTail]
end,
{200, ?HEADER,
- xml:element_to_binary(#xmlel{name = <<"body">>,
+ fxml:element_to_binary(#xmlel{name = <<"body">>,
attrs =
[{<<"xmlns">>,
?NS_HTTP_BIND}],
@@ -1114,14 +1114,14 @@ parse_request(Data, PayloadSize, MaxStanzaSize) ->
?DEBUG("--- incoming data --- ~n~s~n --- END "
"--- ",
[Data]),
- case xml_stream:parse_element(Data) of
+ case fxml_stream:parse_element(Data) of
#xmlel{name = <<"body">>, attrs = Attrs,
children = Els} ->
- Xmlns = xml:get_attr_s(<<"xmlns">>, Attrs),
+ Xmlns = fxml:get_attr_s(<<"xmlns">>, Attrs),
if Xmlns /= (?NS_HTTP_BIND) -> {error, bad_request};
true ->
case catch
- jlib:binary_to_integer(xml:get_attr_s(<<"rid">>,
+ jlib:binary_to_integer(fxml:get_attr_s(<<"rid">>,
Attrs))
of
{'EXIT', _} -> {error, bad_request};
@@ -1133,7 +1133,7 @@ parse_request(Data, PayloadSize, MaxStanzaSize) ->
end
end,
Els),
- Sid = xml:get_attr_s(<<"sid">>, Attrs),
+ Sid = fxml:get_attr_s(<<"sid">>, Attrs),
if PayloadSize =< MaxStanzaSize ->
{ok, {Sid, Rid, Attrs, FixedEls}};
true -> {size_limit, Sid}
@@ -1165,7 +1165,7 @@ set_inactivity_timer(_Pause, MaxInactivity) ->
elements_to_string([]) -> [];
elements_to_string([El | Els]) ->
- [xml:element_to_binary(El) | elements_to_string(Els)].
+ [fxml:element_to_binary(El) | elements_to_string(Els)].
%% @spec (To, Default::integer()) -> integer()
%% where To = [] | {Host::string(), Version::string()}
@@ -1188,7 +1188,7 @@ get_max_pause(_) -> ?MAX_PAUSE.
check_default_xmlns(#xmlel{name = Name, attrs = Attrs,
children = Els} =
El) ->
- case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, El) of
<<"">> ->
#xmlel{name = Name,
attrs = [{<<"xmlns">>, ?NS_CLIENT} | Attrs],
diff --git a/src/ejabberd_http_ws.erl b/src/ejabberd_http_ws.erl
index 3b50e44a..e66cf33a 100644
--- a/src/ejabberd_http_ws.erl
+++ b/src/ejabberd_http_ws.erl
@@ -171,11 +171,11 @@ handle_sync_event({send_xml, Packet}, _From, StateName,
{true, {xmlstreamelement, #xmlel{name=Name2} = El2}} ->
El3 = case Name2 of
<<"stream:", _/binary>> ->
- xml:replace_tag_attr(<<"xmlns:stream">>, ?NS_STREAM, El2);
+ fxml:replace_tag_attr(<<"xmlns:stream">>, ?NS_STREAM, El2);
_ ->
- case xml:get_tag_attr_s(<<"xmlns">>, El2) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, El2) of
<<"">> ->
- xml:replace_tag_attr(<<"xmlns">>, <<"jabber:client">>, El2);
+ fxml:replace_tag_attr(<<"xmlns">>, <<"jabber:client">>, El2);
_ ->
El2
end
@@ -186,12 +186,12 @@ handle_sync_event({send_xml, Packet}, _From, StateName,
end,
case Packet2 of
{xmlstreamstart, Name, Attrs3} ->
- B = xml:element_to_binary(#xmlel{name = Name, attrs = Attrs3}),
+ B = fxml:element_to_binary(#xmlel{name = Name, attrs = Attrs3}),
WsPid ! {send, <<(binary:part(B, 0, byte_size(B)-2))/binary, ">">>};
{xmlstreamend, Name} ->
WsPid ! {send, <<"</", Name/binary, ">">>};
{xmlstreamelement, El} ->
- WsPid ! {send, xml:element_to_binary(El)};
+ WsPid ! {send, fxml:element_to_binary(El)};
{xmlstreamraw, Bin} ->
WsPid ! {send, Bin};
{xmlstreamcdata, Bin2} ->
@@ -210,7 +210,7 @@ handle_sync_event(close, _From, StateName, #state{ws = {_, WsPid}, rfc_compilant
when StateName /= stream_end_sent ->
Close = #xmlel{name = <<"close">>,
attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]},
- WsPid ! {send, xml:element_to_binary(Close)},
+ WsPid ! {send, fxml:element_to_binary(Close)},
{stop, normal, StateData};
handle_sync_event(close, _From, _StateName, StateData) ->
{stop, normal, StateData}.
@@ -316,9 +316,9 @@ get_human_html_xmlel() ->
parse(#state{rfc_compilant = C} = State, Data) ->
case C of
undefined ->
- P = xml_stream:new(self()),
- P2 = xml_stream:parse(P, Data),
- xml_stream:close(P2),
+ P = fxml_stream:new(self()),
+ P2 = fxml_stream:parse(P, Data),
+ fxml_stream:close(P2),
case parsed_items([]) of
error ->
{State#state{rfc_compilant = true}, <<"parse error">>};
@@ -330,7 +330,7 @@ parse(#state{rfc_compilant = C} = State, Data) ->
parse(State#state{rfc_compilant = false}, Data)
end;
true ->
- El = xml_stream:parse_element(Data),
+ El = fxml_stream:parse_element(Data),
case El of
#xmlel{name = <<"open">>, attrs = Attrs} ->
Attrs2 = [{<<"xmlns:stream">>, ?NS_STREAM}, {<<"xmlns">>, <<"jabber:client">>} |
diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl
index 66f530c8..5566073e 100644
--- a/src/ejabberd_local.erl
+++ b/src/ejabberd_local.erl
@@ -32,7 +32,7 @@
%% API
-export([start_link/0]).
--export([route/3, route_iq/4, route_iq/5,
+-export([route/3, route_iq/4, route_iq/5, process_iq/3,
process_iq_reply/3, register_iq_handler/4,
register_iq_handler/5, register_iq_response_handler/4,
register_iq_response_handler/5, unregister_iq_handler/2,
@@ -74,7 +74,7 @@ start_link() ->
process_iq(From, To, Packet) ->
IQ = jlib:iq_query_info(Packet),
case IQ of
- #iq{xmlns = XMLNS} ->
+ #iq{xmlns = XMLNS, lang = Lang} ->
Host = To#jid.lserver,
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
[{_, Module, Function}] ->
@@ -87,8 +87,10 @@ process_iq(From, To, Packet) ->
gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, IQ);
[] ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_FEATURE_NOT_IMPLEMENTED),
+ Txt = <<"No module is handling this query">>,
+ Err = jlib:make_error_reply(
+ Packet,
+ ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end;
reply ->
@@ -166,8 +168,10 @@ refresh_iq_handlers() ->
ejabberd_local ! refresh_iq_handlers.
bounce_resource_packet(From, To, Packet) ->
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"No available resource found">>,
Err = jlib:make_error_reply(Packet,
- ?ERR_ITEM_NOT_FOUND),
+ ?ERRT_ITEM_NOT_FOUND(Lang, Txt)),
ejabberd_router:route(To, From, Err),
stop.
@@ -178,6 +182,7 @@ bounce_resource_packet(From, To, Packet) ->
init([]) ->
lists:foreach(fun (Host) ->
ejabberd_router:register_route(Host,
+ Host,
{apply, ?MODULE,
route}),
ejabberd_hooks:add(local_send_to_resource_hook, Host,
@@ -272,7 +277,7 @@ do_route(From, To, Packet) ->
end;
true ->
#xmlel{attrs = Attrs} = Packet,
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
diff --git a/src/ejabberd_logger.erl b/src/ejabberd_logger.erl
index 05499b45..795d4f39 100644
--- a/src/ejabberd_logger.erl
+++ b/src/ejabberd_logger.erl
@@ -50,6 +50,7 @@
%% "ejabberd.log" in current directory.
%% Note: If the directory where to place the ejabberd log file to not exist,
%% it is not created and no log file will be generated.
+%% @spec () -> string()
get_log_path() ->
case ejabberd_config:env_binary_to_list(ejabberd, log_path) of
{ok, Path} ->
@@ -99,7 +100,33 @@ get_string_env(Name, Default) ->
Default
end.
+%% @spec () -> ok
start() ->
+ StartedApps = application:which_applications(5000),
+ case lists:keyfind(logger, 1, StartedApps) of
+ %% Elixir logger is started. We assume everything is in place
+ %% to use lager to Elixir logger bridge.
+ {logger, _, _} ->
+ error_logger:info_msg("Ignoring ejabberd logger options, using Elixir Logger.", []),
+ %% Do not start lager, we rely on Elixir Logger
+ do_start_for_logger();
+ _ ->
+ do_start()
+ end.
+
+do_start_for_logger() ->
+ application:load(sasl),
+ application:set_env(sasl, sasl_error_logger, false),
+ application:load(lager),
+ application:set_env(lager, error_logger_redirect, false),
+ application:set_env(lager, error_logger_whitelist, ['Elixir.Logger.ErrorHandler']),
+ application:set_env(lager, crash_log, false),
+ application:set_env(lager, handlers, [{elixir_logger_backend, [{level, info}]}]),
+ ejabberd:start_app(lager),
+ ok.
+
+%% Start lager
+do_start() ->
application:load(sasl),
application:set_env(sasl, sasl_error_logger, false),
application:load(lager),
@@ -126,10 +153,12 @@ start() ->
ejabberd:start_app(lager),
ok.
+%% @spec () -> ok
reopen_log() ->
%% Lager detects external log rotation automatically.
ok.
+%% @spec () -> ok
rotate_log() ->
lager_crash_log ! rotate,
lists:foreach(
@@ -139,8 +168,9 @@ rotate_log() ->
ok
end, gen_event:which_handlers(lager_event)).
+%% @spec () -> {loglevel(), atom(), string()}
get() ->
- case lager:get_loglevel(lager_console_backend) of
+ case get_lager_loglevel() of
none -> {0, no_log, "No log"};
emergency -> {1, critical, "Critical"};
alert -> {1, critical, "Critical"};
@@ -152,6 +182,7 @@ get() ->
debug -> {5, debug, "Debug"}
end.
+%% @spec (loglevel() | {loglevel(), list()}) -> {module, module()}
set(LogLevel) when is_integer(LogLevel) ->
LagerLogLevel = case LogLevel of
0 -> none;
@@ -162,7 +193,7 @@ set(LogLevel) when is_integer(LogLevel) ->
5 -> debug;
E -> throw({wrong_loglevel, E})
end,
- case lager:get_loglevel(lager_console_backend) of
+ case get_lager_loglevel() of
LagerLogLevel ->
ok;
_ ->
@@ -172,6 +203,8 @@ set(LogLevel) when is_integer(LogLevel) ->
lager:set_loglevel(H, LagerLogLevel);
(lager_console_backend = H) ->
lager:set_loglevel(H, LagerLogLevel);
+ (elixir_logger_backend = H) ->
+ lager:set_loglevel(H, LagerLogLevel);
(_) ->
ok
end, gen_event:which_handlers(lager_event))
@@ -180,3 +213,22 @@ set(LogLevel) when is_integer(LogLevel) ->
set({_LogLevel, _}) ->
error_logger:error_msg("custom loglevels are not supported for 'lager'"),
{module, lager}.
+
+get_lager_loglevel() ->
+ Handlers = get_lager_handlers(),
+ lists:foldl(fun(lager_console_backend, _Acc) ->
+ lager:get_loglevel(lager_console_backend);
+ (elixir_logger_backend, _Acc) ->
+ lager:get_loglevel(elixir_logger_backend);
+ (_, Acc) ->
+ Acc
+ end,
+ none, Handlers).
+
+get_lager_handlers() ->
+ case catch gen_event:which_handlers(lager_event) of
+ {'EXIT',noproc} ->
+ [];
+ Result ->
+ Result
+ end.
diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl
index a688bb39..d8e0a435 100644
--- a/src/ejabberd_oauth.erl
+++ b/src/ejabberd_oauth.erl
@@ -134,7 +134,7 @@ authenticate_user({User, Server}, {password, Password} = Ctx) ->
none),
case acl:match_rule(JID#jid.lserver, Access, JID) of
allow ->
- case ejabberd_auth:check_password(User, Server, Password) of
+ case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
true ->
{ok, {Ctx, {user, User, Server}}};
false ->
@@ -149,7 +149,7 @@ authenticate_user({User, Server}, {password, Password} = Ctx) ->
authenticate_client(Client, Ctx) -> {ok, {Ctx, {client, Client}}}.
-verify_resowner_scope({user, User, Server}, Scope, Ctx) ->
+verify_resowner_scope({user, _User, _Server}, Scope, Ctx) ->
Cmds = ejabberd_commands:get_commands(),
Cmds1 = [sasl_auth | Cmds],
RegisteredScope = [atom_to_binary(C, utf8) || C <- Cmds1],
@@ -164,7 +164,7 @@ verify_resowner_scope(_, _, _) ->
{error, badscope}.
-associate_access_code(AccessCode, Context, AppContext) ->
+associate_access_code(_AccessCode, _Context, AppContext) ->
%put(?ACCESS_CODE_TABLE, AccessCode, Context),
{ok, AppContext}.
@@ -184,7 +184,7 @@ associate_access_token(AccessToken, Context, AppContext) ->
mnesia:dirty_write(R),
{ok, AppContext}.
-associate_refresh_token(RefreshToken, Context, AppContext) ->
+associate_refresh_token(_RefreshToken, _Context, AppContext) ->
%put(?REFRESH_TOKEN_TABLE, RefreshToken, Context),
{ok, AppContext}.
@@ -303,7 +303,7 @@ process(_Handlers,
process(_Handlers,
#request{method = 'POST', q = Q, lang = _Lang,
path = [_, <<"authorization_token">>]}) ->
- ResponseType = proplists:get_value(<<"response_type">>, Q, <<"">>),
+ _ResponseType = proplists:get_value(<<"response_type">>, Q, <<"">>),
ClientId = proplists:get_value(<<"client_id">>, Q, <<"">>),
RedirectURI = proplists:get_value(<<"redirect_uri">>, Q, <<"">>),
SScope = proplists:get_value(<<"scope">>, Q, <<"">>),
diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl
index 50c8e323..75800123 100644
--- a/src/ejabberd_piefxis.erl
+++ b/src/ejabberd_piefxis.erl
@@ -28,8 +28,8 @@
%%% Not implemented:
%%% - write mod_piefxis with ejabberdctl commands
-%%% - Export from mod_offline_odbc.erl
-%%% - Export from mod_private_odbc.erl
+%%% - Export from mod_offline_sql.erl
+%%% - Export from mod_private_sql.erl
%%% - XEP-227: 6. Security Considerations
%%% - Other schemas of XInclude are not tested, and may not be imported correctly.
%%% - If a host has many users, split that host in XML files with 50 users each.
@@ -64,7 +64,7 @@
-define(NS_PIEFXIS, <<"http://www.xmpp.org/extensions/xep-0227.html#ns">>).
-define(NS_XI, <<"http://www.w3.org/2001/XInclude">>).
--record(state, {xml_stream_state :: xml_stream:xml_stream_state(),
+-record(state, {xml_stream_state :: fxml_stream:xml_stream_state(),
user = <<"">> :: binary(),
server = <<"">> :: binary(),
fd :: file:io_device(),
@@ -80,12 +80,11 @@ import_file(FileName) ->
import_file(FileName, #state{}).
-spec import_file(binary(), state()) -> ok | {error, atom()}.
-
import_file(FileName, State) ->
case file:open(FileName, [read, binary]) of
{ok, Fd} ->
Dir = filename:dirname(FileName),
- XMLStreamState = xml_stream:new(self(), infinity),
+ XMLStreamState = fxml_stream:new(self(), infinity),
Res = process(State#state{xml_stream_state = XMLStreamState,
fd = Fd,
dir = Dir}),
@@ -97,72 +96,14 @@ import_file(FileName, State) ->
{error, Reason}
end.
-%%%==================================
-%%%% Process Elements
-%%%==================================
-%%%% Process Element
-%%%==================================
-%%%% Add user
-%% @spec (El::xmlel(), Domain::string(), User::binary(), Password::binary() | none)
-%% -> ok | {error, ErrorText::string()}
-%% @doc Add a new user to the database.
-%% If user already exists, it will be only updated.
-spec export_server(binary()) -> any().
-
-%% @spec (User::string(), Password::string(), Domain::string())
-%% -> ok | {atomic, exists} | {error, not_allowed}
-%% @doc Create a new user
export_server(Dir) ->
export_hosts(?MYHOSTS, Dir).
-%%%==================================
-%%%% Populate user
-%% @spec (User::string(), Domain::string(), El::xml())
-%% -> ok | {error, not_found}
-%%
-%% @doc Add a new user from a XML file with a roster list.
-%%
-%% Example of a file:
-%% ```
-%% <?xml version='1.0' encoding='UTF-8'?>
-%% <server-data xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'>
-%% <host jid='localhost'>
-%% <user name='juliet' password='s3crEt'>
-%% <query xmlns='jabber:iq:roster'>
-%% <item jid='romeo@montague.net'
-%% name='Romeo'
-%% subscription='both'>
-%% <group>Friends</group>
-%% </item>
-%% </query>
-%% </user>
-%% </host>
-%% </server-data>
-%% '''
-spec export_host(binary(), binary()) -> any().
-
export_host(Dir, Host) ->
export_hosts([Host], Dir).
-%% @spec User = String with the user name
-%% Domain = String with a domain name
-%% El = Sub XML element with vCard tags values
-%% @ret ok | {error, not_found}
-%% @doc Read vcards from the XML and send it to the server
-%%
-%% Example:
-%% ```
-%% <?xml version='1.0' encoding='UTF-8'?>
-%% <server-data xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'>
-%% <host jid='localhost'>
-%% <user name='admin' password='s3crEt'>
-%% <vCard xmlns='vcard-temp'>
-%% <FN>Admin</FN>
-%% </vCard>
-%% </user>
-%% </host>
-%% </server-data>
-%% '''
%%%===================================================================
%%% Internal functions
%%%===================================================================
@@ -194,11 +135,6 @@ export_hosts(Hosts, Dir) ->
{error, Reason}
end.
-%% @spec User = String with the user name
-%% Domain = String with a domain name
-%% El = Sub XML element with offline messages values
-%% @ret ok | {error, not_found}
-%% @doc Read off-line message from the XML and send it to the server
export_host(Dir, FnH, Host) ->
DFn = make_host_basefilename(Dir, FnH),
case file:open(DFn, [raw, write]) of
@@ -223,11 +159,6 @@ export_host(Dir, FnH, Host) ->
{error, Reason}
end.
-%% @spec User = String with the user name
-%% Domain = String with a domain name
-%% El = Sub XML element with private storage values
-%% @ret ok | {error, not_found}
-%% @doc Private storage parsing
export_users([{User, _S}|Users], Server, Fd) ->
case export_user(User, Server, Fd) of
ok ->
@@ -238,8 +169,6 @@ export_users([{User, _S}|Users], Server, Fd) ->
export_users([], _Server, _Fd) ->
ok.
-%%%==================================
-%%%% Utilities
export_user(User, Server, Fd) ->
Password = ejabberd_auth:get_password_s(User, Server),
LServer = jid:nameprep(Server),
@@ -257,7 +186,7 @@ export_user(User, Server, Fd) ->
get_privacy(User, Server) ++
get_roster(User, Server) ++
get_private(User, Server),
- print(Fd, xml:element_to_binary(
+ print(Fd, fxml:element_to_binary(
#xmlel{name = <<"user">>,
attrs = [{<<"name">>, User},
{<<"password">>, Pass}],
@@ -289,7 +218,6 @@ get_vcard(User, Server) ->
[]
end.
-%%%==================================
get_offline(User, Server) ->
case mod_offline:get_offline_els(User, Server) of
[] ->
@@ -306,7 +234,6 @@ get_offline(User, Server) ->
[#xmlel{name = <<"offline-messages">>, children = NewEls}]
end.
-%%%% Export hosts
get_privacy(User, Server) ->
case mod_privacy:get_user_lists(User, Server) of
{ok, #privacy{default = Default,
@@ -333,7 +260,6 @@ get_privacy(User, Server) ->
[]
end.
-%% @spec (Dir::string(), Hosts::[string()]) -> ok
get_roster(User, Server) ->
JID = jid:make(User, Server, <<>>),
case mod_roster:get_roster(User, Server) of
@@ -387,17 +313,17 @@ get_private(User, Server) ->
process(#state{xml_stream_state = XMLStreamState, fd = Fd} = State) ->
case file:read(Fd, ?CHUNK_SIZE) of
{ok, Data} ->
- NewXMLStreamState = xml_stream:parse(XMLStreamState, Data),
+ NewXMLStreamState = fxml_stream:parse(XMLStreamState, Data),
case process_els(State#state{xml_stream_state =
NewXMLStreamState}) of
{ok, NewState} ->
process(NewState);
Err ->
- xml_stream:close(NewXMLStreamState),
+ fxml_stream:close(NewXMLStreamState),
Err
end;
eof ->
- xml_stream:close(XMLStreamState),
+ fxml_stream:close(XMLStreamState),
ok
end.
@@ -415,7 +341,7 @@ process_els(State) ->
end.
process_el({xmlstreamstart, <<"server-data">>, Attrs}, State) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_PIEFXIS ->
{ok, State};
?NS_PIE ->
@@ -430,7 +356,7 @@ process_el({xmlstreamcdata, _}, State) ->
process_el({xmlstreamelement, #xmlel{name = <<"xi:include">>,
attrs = Attrs}},
#state{dir = Dir, user = <<"">>} = State) ->
- FileName = xml:get_attr_s(<<"href">>, Attrs),
+ FileName = fxml:get_attr_s(<<"href">>, Attrs),
case import_file(filename:join([Dir, FileName]), State) of
ok ->
{ok, State};
@@ -443,7 +369,7 @@ process_el({xmlstreamstart, <<"host">>, Attrs}, State) ->
process_el({xmlstreamelement, #xmlel{name = <<"host">>,
attrs = Attrs,
children = Els}}, State) ->
- JIDS = xml:get_attr_s(<<"jid">>, Attrs),
+ JIDS = fxml:get_attr_s(<<"jid">>, Attrs),
case jid:from_string(JIDS) of
#jid{lserver = S} ->
case lists:member(S, ?MYHOSTS) of
@@ -486,8 +412,8 @@ process_users([], State) ->
process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els},
#state{server = LServer} = State) ->
- Name = xml:get_attr_s(<<"name">>, Attrs),
- Password = xml:get_attr_s(<<"password">>, Attrs),
+ Name = fxml:get_attr_s(<<"name">>, Attrs),
+ Password = fxml:get_attr_s(<<"password">>, Attrs),
PasswordFormat = ejabberd_config:get_option({auth_password_format, LServer}, fun(X) -> X end, plain),
Pass = case PasswordFormat of
scram ->
@@ -525,7 +451,7 @@ process_user_els([], State) ->
process_user_el(#xmlel{name = Name, attrs = Attrs, children = Els} = El,
State) ->
- case {Name, xml:get_attr_s(<<"xmlns">>, Attrs)} of
+ case {Name, fxml:get_attr_s(<<"xmlns">>, Attrs)} of
{<<"query">>, ?NS_ROSTER} ->
process_roster(El, State);
{<<"query">>, ?NS_PRIVACY} ->
@@ -576,15 +502,13 @@ process_roster(El, State = #state{user = U, server = S}) ->
stop("Failed to write roster: ~p", [Err])
end.
-%%%==================================
-%%%% Export server
process_privacy(El, State = #state{user = U, server = S}) ->
JID = jid:make(U, S, <<"">>),
case mod_privacy:process_iq_set(
[], JID, JID, #iq{type = set, sub_el = El}) of
{error, Error} = Err ->
#xmlel{children = Els} = El,
- Name = case xml:remove_cdata(Els) of
+ Name = case fxml:remove_cdata(Els) of
[#xmlel{name = N}] -> N;
_ -> undefined
end,
@@ -603,7 +527,6 @@ process_privacy(El, State = #state{user = U, server = S}) ->
{ok, State}
end.
-%% @spec (Dir::string()) -> ok
process_private(El, State = #state{user = U, server = S}) ->
JID = jid:make(U, S, <<"">>),
case mod_private:process_sm_iq(
@@ -614,8 +537,6 @@ process_private(El, State = #state{user = U, server = S}) ->
stop("Failed to write private: ~p", [Err])
end.
-%%%==================================
-%%%% Export host
process_vcard(El, State = #state{user = U, server = S}) ->
JID = jid:make(U, S, <<"">>),
case mod_vcard:process_sm_iq(
@@ -626,9 +547,8 @@ process_vcard(El, State = #state{user = U, server = S}) ->
stop("Failed to write vcard: ~p", [Err])
end.
-%% @spec (Dir::string(), Host::string()) -> ok
process_offline_msg(El, State = #state{user = U, server = S}) ->
- FromS = xml:get_attr_s(<<"from">>, El#xmlel.attrs),
+ FromS = fxml:get_attr_s(<<"from">>, El#xmlel.attrs),
case jid:from_string(FromS) of
#jid{} = From ->
To = jid:make(U, S, <<>>),
@@ -643,9 +563,8 @@ process_offline_msg(El, State = #state{user = U, server = S}) ->
stop("Invalid 'from' = ~s", [FromS])
end.
-%% @spec (Dir::string(), Fn::string(), Host::string()) -> ok
process_presence(El, #state{user = U, server = S} = State) ->
- FromS = xml:get_attr_s(<<"from">>, El#xmlel.attrs),
+ FromS = fxml:get_attr_s(<<"from">>, El#xmlel.attrs),
case jid:from_string(FromS) of
#jid{} = From ->
To = jid:make(U, S, <<>>),
diff --git a/src/ejabberd_rdbms.erl b/src/ejabberd_rdbms.erl
index ae405db0..5224b035 100644
--- a/src/ejabberd_rdbms.erl
+++ b/src/ejabberd_rdbms.erl
@@ -35,10 +35,10 @@
-include("logger.hrl").
start() ->
- file:delete(ejabberd_odbc:freetds_config()),
- file:delete(ejabberd_odbc:odbc_config()),
- file:delete(ejabberd_odbc:odbcinst_config()),
- case lists:any(fun(H) -> needs_odbc(H) /= false end,
+ file:delete(ejabberd_sql:freetds_config()),
+ file:delete(ejabberd_sql:odbc_config()),
+ file:delete(ejabberd_sql:odbcinst_config()),
+ case lists:any(fun(H) -> needs_sql(H) /= false end,
?MYHOSTS) of
true ->
start_hosts();
@@ -49,34 +49,34 @@ start() ->
%% Start relationnal DB module on the nodes where it is needed
start_hosts() ->
lists:foreach(fun (Host) ->
- case needs_odbc(Host) of
- {true, App} -> start_odbc(Host, App);
+ case needs_sql(Host) of
+ {true, App} -> start_sql(Host, App);
false -> ok
end
end,
?MYHOSTS).
-%% Start the ODBC module on the given host
-start_odbc(Host, App) ->
+%% Start the SQL module on the given host
+start_sql(Host, App) ->
ejabberd:start_app(App),
Supervisor_name = gen_mod:get_module_proc(Host,
- ejabberd_odbc_sup),
+ ejabberd_sql_sup),
ChildSpec = {Supervisor_name,
- {ejabberd_odbc_sup, start_link, [Host]}, transient,
- infinity, supervisor, [ejabberd_odbc_sup]},
+ {ejabberd_sql_sup, start_link, [Host]}, transient,
+ infinity, supervisor, [ejabberd_sql_sup]},
case supervisor:start_child(ejabberd_sup, ChildSpec) of
{ok, _PID} -> ok;
_Error ->
?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying."
"..~n",
[Supervisor_name, _Error]),
- start_odbc(Host, App)
+ start_sql(Host, App)
end.
-%% Returns {true, App} if we have configured odbc for the given host
-needs_odbc(Host) ->
+%% Returns {true, App} if we have configured sql for the given host
+needs_sql(Host) ->
LHost = jid:nameprep(Host),
- case ejabberd_config:get_option({odbc_type, LHost},
+ case ejabberd_config:get_option({sql_type, LHost},
fun(mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
@@ -91,11 +91,11 @@ needs_odbc(Host) ->
undefined -> false
end.
-opt_type(odbc_type) ->
+opt_type(sql_type) ->
fun (mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
(mssql) -> mssql;
(odbc) -> odbc
end;
-opt_type(_) -> [odbc_type].
+opt_type(_) -> [sql_type].
diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl
index 78585529..0a33e30e 100644
--- a/src/ejabberd_receiver.erl
+++ b/src/ejabberd_receiver.erl
@@ -48,15 +48,16 @@
-include("logger.hrl").
-record(state,
- {socket :: inet:socket() | p1_tls:tls_socket() | ezlib:zlib_socket(),
- sock_mod = gen_tcp :: gen_tcp | p1_tls | ezlib,
+ {socket :: inet:socket() | fast_tls:tls_socket() | ezlib:zlib_socket(),
+ sock_mod = gen_tcp :: gen_tcp | fast_tls | ezlib,
shaper_state = none :: shaper:shaper(),
c2s_pid :: pid(),
max_stanza_size = infinity :: non_neg_integer() | infinity,
- xml_stream_state :: xml_stream:xml_stream_state(),
+ xml_stream_state :: fxml_stream:xml_stream_state(),
timeout = infinity:: timeout()}).
--define(HIBERNATE_TIMEOUT, 90000).
+-define(HIBERNATE_TIMEOUT, ejabberd_config:get_option(receiver_hibernate, fun(X) when is_integer(X); X == hibernate-> X end, 90000)).
+
-spec start_link(inet:socket(), atom(), shaper:shaper(),
non_neg_integer() | infinity) -> ignore |
@@ -89,7 +90,7 @@ change_shaper(Pid, Shaper) ->
reset_stream(Pid) -> do_call(Pid, reset_stream).
--spec starttls(pid(), p1_tls:tls_socket()) -> ok.
+-spec starttls(pid(), fast_tls:tls_socket()) -> ok.
starttls(Pid, TLSSocket) ->
do_call(Pid, {starttls, TLSSocket}).
@@ -129,8 +130,8 @@ init([Socket, SockMod, Shaper, MaxStanzaSize]) ->
handle_call({starttls, TLSSocket}, _From, State) ->
State1 = reset_parser(State),
NewState = State1#state{socket = TLSSocket,
- sock_mod = p1_tls},
- case p1_tls:recv_data(TLSSocket, <<"">>) of
+ sock_mod = fast_tls},
+ case fast_tls:recv_data(TLSSocket, <<"">>) of
{ok, TLSData} ->
{reply, ok,
process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
@@ -140,6 +141,7 @@ handle_call({starttls, TLSSocket}, _From, State) ->
handle_call({compress, Data}, _From,
#state{socket = Socket, sock_mod = SockMod} =
State) ->
+ ejabberd:start_app(ezlib),
{ok, ZlibSocket} = ezlib:enable_zlib(SockMod,
Socket),
if Data /= undefined -> do_send(State, Data);
@@ -160,7 +162,7 @@ handle_call(reset_stream, _From, State) ->
Reply = ok,
{reply, Reply, NewState, ?HIBERNATE_TIMEOUT};
handle_call({become_controller, C2SPid}, _From, State) ->
- XMLStreamState = xml_stream:new(C2SPid, State#state.max_stanza_size),
+ XMLStreamState = fxml_stream:new(C2SPid, State#state.max_stanza_size),
NewState = State#state{c2s_pid = C2SPid,
xml_stream_state = XMLStreamState},
activate_socket(NewState),
@@ -182,8 +184,8 @@ handle_info({Tag, _TCPSocket, Data},
when (Tag == tcp) or (Tag == ssl) or
(Tag == ejabberd_xml) ->
case SockMod of
- p1_tls ->
- case p1_tls:recv_data(Socket, Data) of
+ fast_tls ->
+ case fast_tls:recv_data(Socket, Data) of
{ok, TLSData} ->
{noreply, process_data(TLSData, State),
?HIBERNATE_TIMEOUT};
@@ -284,7 +286,7 @@ process_data(Data,
undefined ->
XMLStreamState;
_ ->
- xml_stream:parse(XMLStreamState, Data)
+ fxml_stream:parse(XMLStreamState, Data)
end,
{NewShaperState, Pause} = shaper:update(ShaperState, byte_size(Data)),
if
@@ -309,7 +311,7 @@ element_wrapper(Element) -> Element.
close_stream(undefined) -> ok;
close_stream(XMLStreamState) ->
- xml_stream:close(XMLStreamState).
+ fxml_stream:close(XMLStreamState).
reset_parser(#state{xml_stream_state = undefined} = State) ->
State;
@@ -317,14 +319,14 @@ reset_parser(#state{c2s_pid = C2SPid,
max_stanza_size = MaxStanzaSize,
xml_stream_state = XMLStreamState}
= State) ->
- NewStreamState = try xml_stream:reset(XMLStreamState)
+ NewStreamState = try fxml_stream:reset(XMLStreamState)
catch error:_ ->
close_stream(XMLStreamState),
case C2SPid of
undefined ->
undefined;
_ ->
- xml_stream:new(C2SPid, MaxStanzaSize)
+ fxml_stream:new(C2SPid, MaxStanzaSize)
end
end,
State#state{xml_stream_state = NewStreamState}.
diff --git a/src/ejabberd_riak.erl b/src/ejabberd_riak.erl
index 22da9d2e..575810ac 100644
--- a/src/ejabberd_riak.erl
+++ b/src/ejabberd_riak.erl
@@ -28,7 +28,7 @@
-behaviour(gen_server).
%% API
--export([start_link/4, get_proc/1, make_bucket/1, put/2, put/3,
+-export([start_link/5, get_proc/1, make_bucket/1, put/2, put/3,
get/2, get/3, get_by_index/4, delete/1, delete/2,
count_by_index/3, get_by_index_range/5,
get_keys/1, get_keys_by_index/3, is_connected/0,
@@ -68,12 +68,20 @@
%%% API
%%%===================================================================
%% @private
-start_link(Num, Server, Port, _StartInterval) ->
- gen_server:start_link({local, get_proc(Num)}, ?MODULE, [Server, Port], []).
+start_link(Num, Server, Port, _StartInterval, Options) ->
+ gen_server:start_link({local, get_proc(Num)}, ?MODULE, [Server, Port, Options], []).
%% @private
is_connected() ->
- catch riakc_pb_socket:is_connected(get_random_pid()).
+ lists:all(
+ fun({_Id, Pid, _Type, _Modules}) when is_pid(Pid) ->
+ case catch riakc_pb_socket:is_connected(get_riak_pid(Pid)) of
+ true -> true;
+ _ -> false
+ end;
+ (_) ->
+ false
+ end, supervisor:which_children(ejabberd_riak_sup)).
%% @private
get_proc(I) ->
@@ -429,10 +437,8 @@ map_key(Obj, _, _) ->
%%% gen_server API
%%%===================================================================
%% @private
-init([Server, Port]) ->
- case riakc_pb_socket:start(
- Server, Port,
- [auto_reconnect]) of
+init([Server, Port, Options]) ->
+ case riakc_pb_socket:start(Server, Port, Options) of
{ok, Pid} ->
erlang:monitor(process, Pid),
{ok, #state{pid = Pid}};
@@ -517,6 +523,9 @@ make_invalid_object(Val) ->
get_random_pid() ->
PoolPid = ejabberd_riak_sup:get_random_pid(),
+ get_riak_pid(PoolPid).
+
+get_riak_pid(PoolPid) ->
case catch gen_server:call(PoolPid, get_pid) of
{ok, Pid} ->
Pid;
diff --git a/src/ejabberd_riak_sup.erl b/src/ejabberd_riak_sup.erl
index 319d1707..af811441 100644
--- a/src/ejabberd_riak_sup.erl
+++ b/src/ejabberd_riak_sup.erl
@@ -103,12 +103,27 @@ init([]) ->
StartInterval = get_start_interval(),
Server = get_riak_server(),
Port = get_riak_port(),
+ CACertFile = get_riak_cacertfile(),
+ Username = get_riak_username(),
+ Password = get_riak_password(),
+ Options = lists:filter(
+ fun(X) -> X /= nil end,
+ [auto_reconnect,
+ {keepalive, true},
+ if CACertFile /= nil -> {cacertfile ,CACertFile};
+ true -> nil
+ end,
+ if (Username /= nil) and (Password /= nil) ->
+ {credentials, Username, Password};
+ true -> nil
+ end
+ ]),
{ok, {{one_for_one, PoolSize*10, 1},
lists:map(
fun(I) ->
{ejabberd_riak:get_proc(I),
{ejabberd_riak, start_link,
- [I, Server, Port, StartInterval*1000]},
+ [I, Server, Port, StartInterval*1000, Options]},
transient, 2000, worker, [?MODULE]}
end, lists:seq(1, PoolSize))}}.
@@ -131,6 +146,27 @@ get_riak_server() ->
binary_to_list(iolist_to_binary(S))
end, ?DEFAULT_RIAK_HOST).
+get_riak_cacertfile() ->
+ ejabberd_config:get_option(
+ riak_cacertfile,
+ fun(S) ->
+ binary_to_list(iolist_to_binary(S))
+ end, nil).
+
+get_riak_username() ->
+ ejabberd_config:get_option(
+ riak_username,
+ fun(S) ->
+ binary_to_list(iolist_to_binary(S))
+ end, nil).
+
+get_riak_password() ->
+ ejabberd_config:get_option(
+ riak_password,
+ fun(S) ->
+ binary_to_list(iolist_to_binary(S))
+ end, nil).
+
get_riak_port() ->
ejabberd_config:get_option(
riak_port,
@@ -162,6 +198,9 @@ opt_type(riak_port) -> fun (_) -> true end;
opt_type(riak_server) -> fun (_) -> true end;
opt_type(riak_start_interval) ->
fun (N) when is_integer(N), N >= 1 -> N end;
+opt_type(riak_cacertfile) -> fun iolist_to_binary/1;
+opt_type(riak_username) -> fun iolist_to_binary/1;
+opt_type(riak_password) -> fun iolist_to_binary/1;
opt_type(_) ->
[modules, riak_pool_size, riak_port, riak_server,
- riak_start_interval].
+ riak_start_interval, riak_cacertfile, riak_username, riak_password].
diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl
index 5ca1262e..e29d6acf 100644
--- a/src/ejabberd_router.erl
+++ b/src/ejabberd_router.erl
@@ -36,7 +36,9 @@
route_error/4,
register_route/1,
register_route/2,
+ register_route/3,
register_routes/1,
+ host_of_route/1,
unregister_route/1,
unregister_routes/1,
dirty_get_all_routes/0,
@@ -55,7 +57,7 @@
-type local_hint() :: undefined | integer() | {apply, atom(), atom()}.
--record(route, {domain, pid, local_hint}).
+-record(route, {domain, server_host, pid, local_hint}).
-record(state, {}).
@@ -86,7 +88,7 @@ route(From, To, Packet) ->
route_error(From, To, ErrPacket, OrigPacket) ->
#xmlel{attrs = Attrs} = OrigPacket,
- case <<"error">> == xml:get_attr_s(<<"type">>, Attrs) of
+ case <<"error">> == fxml:get_attr_s(<<"type">>, Attrs) of
false -> route(From, To, ErrPacket);
true -> ok
end.
@@ -94,19 +96,29 @@ route_error(From, To, ErrPacket, OrigPacket) ->
-spec register_route(binary()) -> term().
register_route(Domain) ->
- register_route(Domain, undefined).
+ ?WARNING_MSG("~s:register_route/1 is deprected, "
+ "use ~s:register_route/2 instead",
+ [?MODULE, ?MODULE]),
+ register_route(Domain, ?MYNAME).
--spec register_route(binary(), local_hint()) -> term().
+-spec register_route(binary(), binary()) -> term().
-register_route(Domain, LocalHint) ->
- case jid:nameprep(Domain) of
- error -> erlang:error({invalid_domain, Domain});
- LDomain ->
+register_route(Domain, ServerHost) ->
+ register_route(Domain, ServerHost, undefined).
+
+-spec register_route(binary(), binary(), local_hint()) -> term().
+
+register_route(Domain, ServerHost, LocalHint) ->
+ case {jid:nameprep(Domain), jid:nameprep(ServerHost)} of
+ {error, _} -> erlang:error({invalid_domain, Domain});
+ {_, error} -> erlang:error({invalid_domain, ServerHost});
+ {LDomain, LServerHost} ->
Pid = self(),
case get_component_number(LDomain) of
undefined ->
F = fun () ->
mnesia:write(#route{domain = LDomain, pid = Pid,
+ server_host = LServerHost,
local_hint = LocalHint})
end,
mnesia:transaction(F);
@@ -115,46 +127,42 @@ register_route(Domain, LocalHint) ->
case mnesia:wread({route, LDomain}) of
[] ->
mnesia:write(#route{domain = LDomain,
+ server_host = LServerHost,
pid = Pid,
local_hint = 1}),
- lists:foreach(fun (I) ->
- mnesia:write(#route{domain
- =
- LDomain,
- pid
- =
- undefined,
- local_hint
- =
- I})
- end,
- lists:seq(2, N));
+ lists:foreach(
+ fun (I) ->
+ mnesia:write(
+ #route{domain = LDomain,
+ pid = undefined,
+ server_host = LServerHost,
+ local_hint = I})
+ end,
+ lists:seq(2, N));
Rs ->
- lists:any(fun (#route{pid = undefined,
- local_hint = I} =
- R) ->
- mnesia:write(#route{domain =
- LDomain,
- pid =
- Pid,
- local_hint
- =
- I}),
- mnesia:delete_object(R),
- true;
- (_) -> false
- end,
- Rs)
+ lists:any(
+ fun (#route{pid = undefined,
+ local_hint = I} = R) ->
+ mnesia:write(
+ #route{domain = LDomain,
+ pid = Pid,
+ server_host = LServerHost,
+ local_hint = I}),
+ mnesia:delete_object(R),
+ true;
+ (_) -> false
+ end,
+ Rs)
end
end,
mnesia:transaction(F)
end
end.
--spec register_routes([binary()]) -> ok.
+-spec register_routes([{binary(), binary()}]) -> ok.
register_routes(Domains) ->
- lists:foreach(fun (Domain) -> register_route(Domain)
+ lists:foreach(fun ({Domain, ServerHost}) -> register_route(Domain, ServerHost)
end,
Domains).
@@ -183,7 +191,9 @@ unregister_route(Domain) ->
of
[R] ->
I = R#route.local_hint,
+ ServerHost = R#route.server_host,
mnesia:write(#route{domain = LDomain,
+ server_host = ServerHost,
pid = undefined,
local_hint = I}),
mnesia:delete_object(R);
@@ -211,6 +221,20 @@ dirty_get_all_routes() ->
dirty_get_all_domains() ->
lists:usort(mnesia:dirty_all_keys(route)).
+-spec host_of_route(binary()) -> binary().
+
+host_of_route(Domain) ->
+ case jid:nameprep(Domain) of
+ error ->
+ erlang:error({invalid_domain, Domain});
+ LDomain ->
+ case mnesia:dirty_read(route, LDomain) of
+ [#route{server_host = ServerHost}|_] ->
+ ServerHost;
+ [] ->
+ erlang:error({unregistered_route, Domain})
+ end
+ end.
%%====================================================================
%% gen_server callbacks
@@ -283,8 +307,11 @@ handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) ->
if is_integer(E#route.local_hint) ->
LDomain = E#route.domain,
I = E#route.local_hint,
+ ServerHost = E#route.server_host,
mnesia:write(#route{domain =
LDomain,
+ server_host =
+ ServerHost,
pid =
undefined,
local_hint =
@@ -394,12 +421,10 @@ get_component_number(LDomain) ->
undefined).
update_tables() ->
- case catch mnesia:table_info(route, attributes) of
- [domain, node, pid] -> mnesia:delete_table(route);
- [domain, pid] -> mnesia:delete_table(route);
- [domain, pid, local_hint] -> ok;
- [domain, pid, local_hint|_] -> mnesia:delete_table(route);
- {'EXIT', _} -> ok
+ try
+ mnesia:transform_table(route, ignore, record_info(fields, route))
+ catch exit:{aborted, {no_exists, _}} ->
+ ok
end,
case lists:member(local_route,
mnesia:system_info(tables))
diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl
index 2f026b32..be1ee465 100644
--- a/src/ejabberd_s2s.erl
+++ b/src/ejabberd_s2s.erl
@@ -214,7 +214,7 @@ check_peer_certificate(SockMod, Sock, Peer) ->
end
end;
VerifyRes ->
- {error, p1_tls:get_cert_verify_string(VerifyRes, Cert)}
+ {error, fast_tls:get_cert_verify_string(VerifyRes, Cert)}
end;
{error, _Reason} ->
{error, <<"Cannot get peer certificate">>};
@@ -308,12 +308,14 @@ do_route(From, To, Packet) ->
#xmlel{name = Name, attrs = NewAttrs, children = Els}),
ok;
{aborted, _Reason} ->
- case xml:get_tag_attr_s(<<"type">>, Packet) of
+ case fxml:get_tag_attr_s(<<"type">>, Packet) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"No s2s connection found">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end,
false
diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl
index 29dd5e9d..d8d0a400 100644
--- a/src/ejabberd_s2s_in.erl
+++ b/src/ejabberd_s2s_in.erl
@@ -85,16 +85,16 @@
-define(STREAM_TRAILER, <<"</stream:stream>">>).
-define(INVALID_NAMESPACE_ERR,
- xml:element_to_binary(?SERR_INVALID_NAMESPACE)).
+ fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
-define(HOST_UNKNOWN_ERR,
- xml:element_to_binary(?SERR_HOST_UNKNOWN)).
+ fxml:element_to_binary(?SERR_HOST_UNKNOWN)).
-define(INVALID_FROM_ERR,
- xml:element_to_binary(?SERR_INVALID_FROM)).
+ fxml:element_to_binary(?SERR_INVALID_FROM)).
-define(INVALID_XML_ERR,
- xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
+ fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
start(SockData, Opts) ->
supervisor:start_child(ejabberd_s2s_in_sup,
@@ -188,10 +188,10 @@ init([{SockMod, Socket}, Opts]) ->
wait_for_stream({xmlstreamstart, _Name, Attrs},
StateData) ->
- case {xml:get_attr_s(<<"xmlns">>, Attrs),
- xml:get_attr_s(<<"xmlns:db">>, Attrs),
- xml:get_attr_s(<<"to">>, Attrs),
- xml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs),
+ fxml:get_attr_s(<<"xmlns:db">>, Attrs),
+ fxml:get_attr_s(<<"to">>, Attrs),
+ fxml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
of
{<<"jabber:server">>, _, Server, true}
when StateData#state.tls and
@@ -199,7 +199,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
send_text(StateData,
?STREAM_HEADER(<<" version='1.0'">>)),
Auth = if StateData#state.tls_enabled ->
- case jid:nameprep(xml:get_attr_s(<<"from">>, Attrs)) of
+ case jid:nameprep(fxml:get_attr_s(<<"from">>, Attrs)) of
From when From /= <<"">>, From /= error ->
{Result, Message} =
ejabberd_s2s:check_peer_certificate(StateData#state.sockmod,
@@ -234,7 +234,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)",
[StateData#state.server, RemoteServer, CertError]),
send_text(StateData,
- <<(xml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
+ <<(fxml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
CertError)))/binary,
(?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
@@ -306,7 +306,7 @@ wait_for_feature_request({xmlstreamelement, El},
TLSEnabled = StateData#state.tls_enabled,
SockMod =
(StateData#state.sockmod):get_sockmod(StateData#state.socket),
- case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
{?NS_TLS, <<"starttls">>}
when TLS == true, TLSEnabled == false,
SockMod == gen_tcp ->
@@ -325,13 +325,13 @@ wait_for_feature_request({xmlstreamelement, El},
{s2s_tls_compression, StateData#state.server},
fun(true) -> true;
(false) -> false
- end, true) of
+ end, false) of
true -> lists:delete(compression_none, TLSOpts1);
false -> [compression_none | TLSOpts1]
end,
TLSSocket = (StateData#state.sockmod):starttls(Socket,
TLSOpts,
- xml:element_to_binary(#xmlel{name
+ fxml:element_to_binary(#xmlel{name
=
<<"proceed">>,
attrs
@@ -345,7 +345,7 @@ wait_for_feature_request({xmlstreamelement, El},
StateData#state{socket = TLSSocket, streamid = new_id(),
tls_enabled = true, tls_options = TLSOpts}};
{?NS_SASL, <<"auth">>} when TLSEnabled ->
- Mech = xml:get_attr_s(<<"mechanism">>, Attrs),
+ Mech = fxml:get_attr_s(<<"mechanism">>, Attrs),
case Mech of
<<"EXTERNAL">> when StateData#state.auth_domain /= <<"">> ->
AuthDomain = StateData#state.auth_domain,
@@ -447,9 +447,9 @@ stream_established({xmlstreamelement, El}, StateData) ->
_ ->
NewEl = jlib:remove_attr(<<"xmlns">>, El),
#xmlel{name = Name, attrs = Attrs} = NewEl,
- From_s = xml:get_attr_s(<<"from">>, Attrs),
+ From_s = fxml:get_attr_s(<<"from">>, Attrs),
From = jid:from_string(From_s),
- To_s = xml:get_attr_s(<<"to">>, Attrs),
+ To_s = fxml:get_attr_s(<<"to">>, Attrs),
To = jid:from_string(To_s),
if (To /= error) and (From /= error) ->
LFrom = From#jid.lserver,
@@ -626,7 +626,7 @@ send_text(StateData, Text) ->
Text).
send_element(StateData, El) ->
- send_text(StateData, xml:element_to_binary(El)).
+ send_text(StateData, fxml:element_to_binary(El)).
change_shaper(StateData, Host, JID) ->
Shaper = acl:match_rule(Host, StateData#state.shaper,
@@ -643,15 +643,15 @@ cancel_timer(Timer) ->
is_key_packet(#xmlel{name = Name, attrs = Attrs,
children = Els})
when Name == <<"db:result">> ->
- {key, xml:get_attr_s(<<"to">>, Attrs),
- xml:get_attr_s(<<"from">>, Attrs),
- xml:get_attr_s(<<"id">>, Attrs), xml:get_cdata(Els)};
+ {key, fxml:get_attr_s(<<"to">>, Attrs),
+ fxml:get_attr_s(<<"from">>, Attrs),
+ fxml:get_attr_s(<<"id">>, Attrs), fxml:get_cdata(Els)};
is_key_packet(#xmlel{name = Name, attrs = Attrs,
children = Els})
when Name == <<"db:verify">> ->
- {verify, xml:get_attr_s(<<"to">>, Attrs),
- xml:get_attr_s(<<"from">>, Attrs),
- xml:get_attr_s(<<"id">>, Attrs), xml:get_cdata(Els)};
+ {verify, fxml:get_attr_s(<<"to">>, Attrs),
+ fxml:get_attr_s(<<"from">>, Attrs),
+ fxml:get_attr_s(<<"id">>, Attrs), fxml:get_cdata(Els)};
is_key_packet(_) -> false.
fsm_limit_opts(Opts) ->
diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl
index 69618573..a30f2f43 100644
--- a/src/ejabberd_s2s_out.erl
+++ b/src/ejabberd_s2s_out.erl
@@ -105,13 +105,13 @@
-define(STREAM_TRAILER, <<"</stream:stream>">>).
-define(INVALID_NAMESPACE_ERR,
- xml:element_to_binary(?SERR_INVALID_NAMESPACE)).
+ fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
-define(HOST_UNKNOWN_ERR,
- xml:element_to_binary(?SERR_HOST_UNKNOWN)).
+ fxml:element_to_binary(?SERR_HOST_UNKNOWN)).
-define(INVALID_XML_ERR,
- xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
+ fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
-define(SOCKET_DEFAULT_RESULT, {error, badarg}).
@@ -192,7 +192,7 @@ init([From, Server, Type]) ->
{s2s_tls_compression, From},
fun(true) -> true;
(false) -> false
- end, true) of
+ end, false) of
false -> [compression_none | TLSOpts4];
true -> TLSOpts4
end,
@@ -323,15 +323,15 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
true ->
{no_verify, <<"Not verified">>, StateData}
end,
- RemoteStreamID = xml:get_attr_s(<<"id">>, Attrs),
+ RemoteStreamID = fxml:get_attr_s(<<"id">>, Attrs),
NewStateData = StateData0#state{remote_streamid = RemoteStreamID},
- case {xml:get_attr_s(<<"xmlns">>, Attrs),
- xml:get_attr_s(<<"xmlns:db">>, Attrs),
- xml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs),
+ fxml:get_attr_s(<<"xmlns:db">>, Attrs),
+ fxml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
of
_ when CertCheckRes == error ->
send_text(NewStateData,
- <<(xml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
+ <<(fxml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
CertCheckMsg)))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (~s)",
@@ -495,7 +495,7 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
STLSReq} =
Acc) ->
case
- xml:get_attr_s(<<"xmlns">>,
+ fxml:get_attr_s(<<"xmlns">>,
Attrs1)
of
?NS_SASL ->
@@ -508,7 +508,7 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
=
Els2}) ->
case
- xml:get_cdata(Els2)
+ fxml:get_cdata(Els2)
of
<<"EXTERNAL">> ->
true;
@@ -533,13 +533,13 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
_STLSReq} =
Acc) ->
case
- xml:get_attr_s(<<"xmlns">>,
+ fxml:get_attr_s(<<"xmlns">>,
Attrs1)
of
?NS_TLS ->
Req =
case
- xml:get_subtag(El1,
+ fxml:get_subtag(El1,
<<"required">>)
of
#xmlel{} ->
@@ -610,7 +610,7 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
end;
_ ->
send_text(StateData,
- <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
"format)",
@@ -637,7 +637,7 @@ wait_for_auth_result({xmlstreamelement, El},
StateData) ->
case El of
#xmlel{name = <<"success">>, attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_SASL ->
?DEBUG("auth: ~p",
[{StateData#state.myname, StateData#state.server}]),
@@ -653,7 +653,7 @@ wait_for_auth_result({xmlstreamelement, El},
?FSMTIMEOUT};
_ ->
send_text(StateData,
- <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
"format)",
@@ -661,7 +661,7 @@ wait_for_auth_result({xmlstreamelement, El},
{stop, normal, StateData}
end;
#xmlel{name = <<"failure">>, attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_SASL ->
?DEBUG("restarted: ~p",
[{StateData#state.myname, StateData#state.server}]),
@@ -670,7 +670,7 @@ wait_for_auth_result({xmlstreamelement, El},
StateData#state{socket = undefined}, ?FSMTIMEOUT};
_ ->
send_text(StateData,
- <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
"format)",
@@ -679,7 +679,7 @@ wait_for_auth_result({xmlstreamelement, El},
end;
_ ->
send_text(StateData,
- <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
"format)",
@@ -707,7 +707,7 @@ wait_for_starttls_proceed({xmlstreamelement, El},
StateData) ->
case El of
#xmlel{name = <<"proceed">>, attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_TLS ->
?DEBUG("starttls: ~p",
[{StateData#state.myname, StateData#state.server}]),
@@ -737,7 +737,7 @@ wait_for_starttls_proceed({xmlstreamelement, El},
?FSMTIMEOUT};
_ ->
send_text(StateData,
- <<(xml:element_to_binary(?SERR_BAD_FORMAT))/binary,
+ <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary,
(?STREAM_TRAILER)/binary>>),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad "
"format)",
@@ -985,7 +985,7 @@ send_text(StateData, Text) ->
ejabberd_socket:send(StateData#state.socket, Text).
send_element(StateData, El) ->
- send_text(StateData, xml:element_to_binary(El)).
+ send_text(StateData, fxml:element_to_binary(El)).
send_queue(StateData, Q) ->
case queue:out(Q) of
@@ -997,14 +997,14 @@ send_queue(StateData, Q) ->
%% Bounce a single message (xmlelement)
bounce_element(El, Error) ->
#xmlel{attrs = Attrs} = El,
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
Err = jlib:make_error_reply(El, Error),
- From = jid:from_string(xml:get_tag_attr_s(<<"from">>,
+ From = jid:from_string(fxml:get_tag_attr_s(<<"from">>,
El)),
- To = jid:from_string(xml:get_tag_attr_s(<<"to">>,
+ To = jid:from_string(fxml:get_tag_attr_s(<<"to">>,
El)),
ejabberd_router:route(To, From, Err)
end.
@@ -1070,16 +1070,16 @@ send_db_request(StateData) ->
is_verify_res(#xmlel{name = Name, attrs = Attrs})
when Name == <<"db:result">> ->
- {result, xml:get_attr_s(<<"to">>, Attrs),
- xml:get_attr_s(<<"from">>, Attrs),
- xml:get_attr_s(<<"id">>, Attrs),
- xml:get_attr_s(<<"type">>, Attrs)};
+ {result, fxml:get_attr_s(<<"to">>, Attrs),
+ fxml:get_attr_s(<<"from">>, Attrs),
+ fxml:get_attr_s(<<"id">>, Attrs),
+ fxml:get_attr_s(<<"type">>, Attrs)};
is_verify_res(#xmlel{name = Name, attrs = Attrs})
when Name == <<"db:verify">> ->
- {verify, xml:get_attr_s(<<"to">>, Attrs),
- xml:get_attr_s(<<"from">>, Attrs),
- xml:get_attr_s(<<"id">>, Attrs),
- xml:get_attr_s(<<"type">>, Attrs)};
+ {verify, fxml:get_attr_s(<<"to">>, Attrs),
+ fxml:get_attr_s(<<"from">>, Attrs),
+ fxml:get_attr_s(<<"id">>, Attrs),
+ fxml:get_attr_s(<<"type">>, Attrs)};
is_verify_res(_) -> false.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl
index a9b771b8..465fb587 100644
--- a/src/ejabberd_service.erl
+++ b/src/ejabberd_service.erl
@@ -91,10 +91,10 @@
"m:error></stream:stream>">>).
-define(INVALID_XML_ERR,
- xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
+ fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
-define(INVALID_NS_ERR,
- xml:element_to_binary(?SERR_INVALID_NAMESPACE)).
+ fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
%%%----------------------------------------------------------------------
%%% API
@@ -166,9 +166,9 @@ init([{SockMod, Socket}, Opts]) ->
wait_for_stream({xmlstreamstart, _Name, Attrs},
StateData) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
<<"jabber:component:accept">> ->
- To = xml:get_attr_s(<<"to">>, Attrs),
+ To = fxml:get_attr_s(<<"to">>, Attrs),
Host = jid:nameprep(To),
if Host == error ->
Header = io_lib:format(?STREAM_HEADER,
@@ -180,7 +180,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
{stop, normal, StateData};
true ->
Header = io_lib:format(?STREAM_HEADER,
- [StateData#state.streamid, xml:crypt(To)]),
+ [StateData#state.streamid, fxml:crypt(To)]),
send_text(StateData, Header),
HostOpts = case dict:is_key(Host, StateData#state.host_opts) of
true ->
@@ -212,7 +212,7 @@ wait_for_stream(closed, StateData) ->
wait_for_handshake({xmlstreamelement, El}, StateData) ->
#xmlel{name = Name, children = Els} = El,
- case {Name, xml:get_cdata(Els)} of
+ case {Name, fxml:get_cdata(Els)} of
{<<"handshake">>, Digest} ->
case dict:find(StateData#state.host, StateData#state.host_opts) of
{ok, Password} ->
@@ -222,7 +222,7 @@ wait_for_handshake({xmlstreamelement, El}, StateData) ->
send_text(StateData, <<"<handshake/>">>),
lists:foreach(
fun (H) ->
- ejabberd_router:register_route(H),
+ ejabberd_router:register_route(H, ?MYNAME),
?INFO_MSG("Route registered for service ~p~n",
[H])
end, dict:fetch_keys(StateData#state.host_opts)),
@@ -250,7 +250,7 @@ wait_for_handshake(closed, StateData) ->
stream_established({xmlstreamelement, El}, StateData) ->
NewEl = jlib:remove_attr(<<"xmlns">>, El),
#xmlel{name = Name, attrs = Attrs} = NewEl,
- From = xml:get_attr_s(<<"from">>, Attrs),
+ From = fxml:get_attr_s(<<"from">>, Attrs),
FromJID = case StateData#state.check_from of
%% If the admin does not want to check the from field
%% when accept packets from any address.
@@ -269,7 +269,7 @@ stream_established({xmlstreamelement, El}, StateData) ->
_ -> error
end
end,
- To = xml:get_attr_s(<<"to">>, Attrs),
+ To = fxml:get_attr_s(<<"to">>, Attrs),
ToJID = case To of
<<"">> -> error;
_ -> jid:from_string(To)
@@ -280,7 +280,9 @@ stream_established({xmlstreamelement, El}, StateData) ->
and (FromJID /= error) ->
ejabberd_router:route(FromJID, ToJID, NewEl);
true ->
- Err = jlib:make_error_reply(NewEl, ?ERR_BAD_REQUEST),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, El),
+ Txt = <<"Incorrect stanza name or from/to JID">>,
+ Err = jlib:make_error_reply(NewEl, ?ERRT_BAD_REQUEST(Lang, Txt)),
send_element(StateData, Err),
error
end,
@@ -356,11 +358,13 @@ handle_info({route, From, To, Packet}, StateName,
Attrs2 =
jlib:replace_from_to_attrs(jid:to_string(From),
jid:to_string(To), Attrs),
- Text = xml:element_to_binary(#xmlel{name = Name,
+ Text = fxml:element_to_binary(#xmlel{name = Name,
attrs = Attrs2, children = Els}),
send_text(StateData, Text);
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"Denied by ACL">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
ejabberd_router:route_error(To, From, Err, Packet)
end,
{next_state, StateName, StateData};
@@ -402,7 +406,7 @@ send_text(StateData, Text) ->
Text).
send_element(StateData, El) ->
- send_text(StateData, xml:element_to_binary(El)).
+ send_text(StateData, fxml:element_to_binary(El)).
new_id() -> randoms:get_string().
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index 3045a417..b7fc3950 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -35,6 +35,7 @@
-export([start/0,
start_link/0,
route/3,
+ process_iq/3,
open_session/5,
open_session/6,
close_session/4,
@@ -50,6 +51,7 @@
dirty_get_my_sessions_list/0,
get_vh_session_list/1,
get_vh_session_number/1,
+ get_vh_by_backend/1,
register_iq_handler/4,
register_iq_handler/5,
unregister_iq_handler/2,
@@ -64,7 +66,8 @@
get_max_user_sessions/2,
get_all_pids/0,
is_existing_resource/3,
- get_commands_spec/0
+ get_commands_spec/0,
+ make_sid/0
]).
-export([init/1, handle_call/3, handle_cast/2,
@@ -133,10 +136,10 @@ open_session(SID, User, Server, Resource, Info) ->
-spec close_session(sid(), binary(), binary(), binary()) -> ok.
close_session(SID, User, Server, Resource) ->
- Mod = get_sm_backend(),
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
+ Mod = get_sm_backend(LServer),
Info = case Mod:delete_session(LUser, LServer, LResource, SID) of
{ok, #session{info = I}} -> I;
{error, notfound} -> []
@@ -157,8 +160,10 @@ check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
-spec bounce_offline_message(jid(), jid(), xmlel()) -> stop.
bounce_offline_message(From, To, Packet) ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"User session not found">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err),
stop.
@@ -172,14 +177,14 @@ disconnect_removed_user(User, Server) ->
get_user_resources(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(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(),
+ Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
[{S#session.priority, element(3, S#session.usr)}
|| S <- clean_session_list(Ss), is_integer(S#session.priority)].
@@ -190,7 +195,7 @@ get_user_ip(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[] ->
undefined;
@@ -205,7 +210,7 @@ get_user_info(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[] ->
offline;
@@ -255,7 +260,7 @@ get_session_pid(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[#session{sid = {_, Pid}}] -> Pid;
_ -> none
@@ -264,33 +269,40 @@ get_session_pid(User, Server, Resource) ->
-spec dirty_get_sessions_list() -> [ljid()].
dirty_get_sessions_list() ->
- Mod = get_sm_backend(),
- [S#session.usr || S <- Mod:get_sessions()].
+ lists:flatmap(
+ fun(Mod) ->
+ [S#session.usr || S <- Mod:get_sessions()]
+ end, get_sm_backends()).
-spec dirty_get_my_sessions_list() -> [#session{}].
dirty_get_my_sessions_list() ->
- Mod = get_sm_backend(),
- [S || S <- Mod:get_sessions(), node(element(2, S#session.sid)) == node()].
+ lists:flatmap(
+ fun(Mod) ->
+ [S || S <- Mod:get_sessions(),
+ node(element(2, S#session.sid)) == node()]
+ end, get_sm_backends()).
-spec get_vh_session_list(binary()) -> [ljid()].
get_vh_session_list(Server) ->
LServer = jid:nameprep(Server),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
[S#session.usr || S <- Mod:get_sessions(LServer)].
-spec get_all_pids() -> [pid()].
get_all_pids() ->
- Mod = get_sm_backend(),
- [element(2, S#session.sid) || S <- Mod:get_sessions()].
+ lists:flatmap(
+ fun(Mod) ->
+ [element(2, S#session.sid) || S <- Mod:get_sessions()]
+ end, get_sm_backends()).
-spec get_vh_session_number(binary()) -> non_neg_integer().
get_vh_session_number(Server) ->
LServer = jid:nameprep(Server),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
length(Mod:get_sessions(LServer)).
register_iq_handler(Host, XMLNS, Module, Fun) ->
@@ -312,8 +324,7 @@ unregister_iq_handler(Host, XMLNS) ->
%%====================================================================
init([]) ->
- Mod = get_sm_backend(),
- Mod:init(),
+ lists:foreach(fun(Mod) -> Mod:init() end, get_sm_backends()),
ets:new(sm_iqtable, [named_table]),
lists:foreach(
fun(Host) ->
@@ -380,7 +391,7 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
LResource = jid:resourceprep(Resource),
US = {LUser, LServer},
USR = {LUser, LServer, LResource},
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
Mod:set_session(#session{sid = SID, usr = USR, us = US,
priority = Priority, info = Info}).
@@ -397,7 +408,7 @@ do_route(From, To, {broadcast, _} = Packet) ->
get_user_resources(To#jid.user, To#jid.server));
_ ->
{U, S, R} = jid:tolower(To),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(S),
case Mod:get_sessions(U, S, R) of
[] ->
?DEBUG("packet dropped~n", []);
@@ -415,14 +426,15 @@ do_route(From, To, #xmlel{} = Packet) ->
#jid{user = User, server = Server,
luser = LUser, lserver = LServer, lresource = LResource} = To,
#xmlel{name = Name, attrs = Attrs} = Packet,
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case LResource of
<<"">> ->
case Name of
<<"presence">> ->
- {Pass, _Subsc} = case xml:get_attr_s(<<"type">>, Attrs)
+ {Pass, _Subsc} = case fxml:get_attr_s(<<"type">>, Attrs)
of
<<"subscribe">> ->
- Reason = xml:get_path_s(Packet,
+ Reason = fxml:get_path_s(Packet,
[{elem,
<<"status">>},
cdata]),
@@ -483,13 +495,14 @@ do_route(From, To, #xmlel{} = Packet) ->
true -> ok
end;
<<"message">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"chat">> -> route_message(From, To, Packet, chat);
<<"headline">> -> route_message(From, To, Packet, headline);
<<"error">> -> ok;
<<"groupchat">> ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
+ ErrTxt = <<"Incorrect message type">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
ejabberd_router:route(To, From, Err);
_ ->
route_message(From, To, Packet, normal)
@@ -498,28 +511,32 @@ do_route(From, To, #xmlel{} = Packet) ->
_ -> ok
end;
_ ->
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[] ->
case Name of
<<"message">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"chat">> -> route_message(From, To, Packet, chat);
<<"normal">> -> route_message(From, To, Packet, normal);
<<"">> -> route_message(From, To, Packet, normal);
<<"error">> -> ok;
_ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
+ ErrTxt = <<"Incorrect message type">>,
+ Err = jlib:make_error_reply(
+ Packet,
+ ?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
ejabberd_router:route(To, From, Err)
end;
<<"iq">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
+ ErrTxt = <<"User session not found">>,
+ Err = jlib:make_error_reply(
+ Packet,
+ ?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
ejabberd_router:route(To, From, Err)
end;
_ -> ?DEBUG("packet dropped~n", [])
@@ -565,7 +582,7 @@ route_message(From, To, Packet, Type) ->
lists:foreach(fun ({P, R}) when P == Priority;
(P >= 0) and (Type == headline) ->
LResource = jid:resourceprep(R),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer,
LResource) of
[] ->
@@ -647,11 +664,11 @@ get_resource_sessions(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
[S#session.sid || S <- Mod:get_sessions(LUser, LServer, LResource)].
check_max_sessions(LUser, LServer) ->
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
SIDs = [S#session.sid || S <- Mod:get_sessions(LUser, LServer)],
MaxSessions = get_max_user_sessions(LUser, LServer),
if length(SIDs) =< MaxSessions -> ok;
@@ -676,7 +693,7 @@ get_max_user_sessions(LUser, Host) ->
process_iq(From, To, Packet) ->
IQ = jlib:iq_query_info(Packet),
case IQ of
- #iq{xmlns = XMLNS} ->
+ #iq{xmlns = XMLNS, lang = Lang} ->
Host = To#jid.lserver,
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
[{_, Module, Function}] ->
@@ -689,8 +706,10 @@ process_iq(From, To, Packet) ->
gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, IQ);
[] ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
+ Txt = <<"No module is handling this query">>,
+ Err = jlib:make_error_reply(
+ Packet,
+ ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end;
reply -> ok;
@@ -703,24 +722,38 @@ process_iq(From, To, Packet) ->
-spec force_update_presence({binary(), binary()}) -> any().
force_update_presence({LUser, LServer}) ->
- Mod = get_sm_backend(),
+ Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
lists:foreach(fun (#session{sid = {_, Pid}}) ->
Pid ! {force_update_presence, LUser, LServer}
end,
Ss).
--spec get_sm_backend() -> module().
+-spec get_sm_backend(binary()) -> module().
-get_sm_backend() ->
- DBType = ejabberd_config:get_option(sm_db_type,
+get_sm_backend(Host) ->
+ DBType = ejabberd_config:get_option({sm_db_type, Host},
fun(mnesia) -> mnesia;
(internal) -> mnesia;
- (odbc) -> odbc;
+ (odbc) -> sql;
+ (sql) -> sql;
(redis) -> redis
end, mnesia),
list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)).
+-spec get_sm_backends() -> [module()].
+
+get_sm_backends() ->
+ lists:usort([get_sm_backend(Host) || Host <- ?MYHOSTS]).
+
+-spec get_vh_by_backend(module()) -> [binary()].
+
+get_vh_by_backend(Mod) ->
+ lists:filter(
+ fun(Host) ->
+ get_sm_backend(Host) == Mod
+ end, ?MYHOSTS).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% ejabberd commands
@@ -775,10 +808,14 @@ kick_user(User, Server) ->
end, Resources),
length(Resources).
+make_sid() ->
+ {p1_time_compat:unique_timestamp(), self()}.
+
opt_type(sm_db_type) ->
fun (mnesia) -> mnesia;
(internal) -> mnesia;
- (odbc) -> odbc;
+ (sql) -> sql;
+ (odbc) -> sql;
(redis) -> redis
end;
opt_type(_) -> [sm_db_type].
diff --git a/src/ejabberd_sm_redis.erl b/src/ejabberd_sm_redis.erl
index 637e1364..bf9e0eff 100644
--- a/src/ejabberd_sm_redis.erl
+++ b/src/ejabberd_sm_redis.erl
@@ -107,7 +107,7 @@ get_sessions() ->
lists:flatmap(
fun(LServer) ->
get_sessions(LServer)
- end, ?MYHOSTS).
+ end, ejabberd_sm:get_vh_by_backend(?MODULE)).
-spec get_sessions(binary()) -> [#session{}].
get_sessions(LServer) ->
@@ -204,7 +204,7 @@ clean_table() ->
?ERROR_MSG("failed to clean redis table for "
"server ~s: ~p", [LServer, Err])
end
- end, ?MYHOSTS).
+ end, ejabberd_sm:get_vh_by_backend(?MODULE)).
opt_type(redis_connect_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
diff --git a/src/ejabberd_sm_odbc.erl b/src/ejabberd_sm_sql.erl
index debeafe9..3d4e224d 100644
--- a/src/ejabberd_sm_odbc.erl
+++ b/src/ejabberd_sm_sql.erl
@@ -6,7 +6,7 @@
%%% @end
%%% Created : 9 Mar 2015 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
--module(ejabberd_sm_odbc).
+-module(ejabberd_sm_sql).
-behaviour(ejabberd_sm).
@@ -29,11 +29,11 @@
%%%===================================================================
-spec init() -> ok | {error, any()}.
init() ->
- Node = ejabberd_odbc:escape(jlib:atom_to_binary(node())),
+ Node = ejabberd_sql:escape(jlib:atom_to_binary(node())),
?INFO_MSG("Cleaning SQL SM table...", []),
lists:foldl(
fun(Host, ok) ->
- case ejabberd_odbc:sql_query(
+ case ejabberd_sql:sql_query(
Host, [<<"delete from sm where node='">>, Node, <<"'">>]) of
{updated, _} ->
ok;
@@ -43,18 +43,18 @@ init() ->
end;
(_, Err) ->
Err
- end, ok, ?MYHOSTS).
+ end, ok, ejabberd_sm:get_vh_by_backend(?MODULE)).
set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
priority = Priority, info = Info}) ->
- Username = ejabberd_odbc:escape(U),
- Resource = ejabberd_odbc:escape(R),
- InfoS = ejabberd_odbc:encode_term(Info),
+ Username = ejabberd_sql:escape(U),
+ Resource = ejabberd_sql:escape(R),
+ InfoS = ejabberd_sql:encode_term(Info),
PrioS = enc_priority(Priority),
TS = now_to_timestamp(Now),
PidS = list_to_binary(erlang:pid_to_list(Pid)),
- Node = ejabberd_odbc:escape(jlib:atom_to_binary(node(Pid))),
- case odbc_queries:update(
+ Node = ejabberd_sql:escape(jlib:atom_to_binary(node(Pid))),
+ case sql_queries:update(
LServer,
<<"sm">>,
[<<"usec">>, <<"pid">>, <<"node">>, <<"username">>,
@@ -70,12 +70,12 @@ set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
delete_session(_LUser, LServer, _LResource, {Now, Pid}) ->
TS = now_to_timestamp(Now),
PidS = list_to_binary(erlang:pid_to_list(Pid)),
- case ejabberd_odbc:sql_query(
+ case ejabberd_sql:sql_query(
LServer,
[<<"select usec, pid, username, resource, priority, info ">>,
<<"from sm where usec='">>, TS, <<"' and pid='">>,PidS, <<"'">>]) of
{selected, _, [Row]} ->
- ejabberd_odbc:sql_query(
+ ejabberd_sql:sql_query(
LServer, [<<"delete from sm where usec='">>,
TS, <<"' and pid='">>, PidS, <<"'">>]),
{ok, row_to_session(LServer, Row)};
@@ -90,10 +90,10 @@ get_sessions() ->
lists:flatmap(
fun(LServer) ->
get_sessions(LServer)
- end, ?MYHOSTS).
+ end, ejabberd_sm:get_vh_by_backend(?MODULE)).
get_sessions(LServer) ->
- case ejabberd_odbc:sql_query(
+ case ejabberd_sql:sql_query(
LServer, [<<"select usec, pid, username, ">>,
<<"resource, priority, info from sm">>]) of
{selected, _, Rows} ->
@@ -104,8 +104,8 @@ get_sessions(LServer) ->
end.
get_sessions(LUser, LServer) ->
- Username = ejabberd_odbc:escape(LUser),
- case ejabberd_odbc:sql_query(
+ Username = ejabberd_sql:escape(LUser),
+ case ejabberd_sql:sql_query(
LServer, [<<"select usec, pid, username, ">>,
<<"resource, priority, info from sm where ">>,
<<"username='">>, Username, <<"'">>]) of
@@ -117,9 +117,9 @@ get_sessions(LUser, LServer) ->
end.
get_sessions(LUser, LServer, LResource) ->
- Username = ejabberd_odbc:escape(LUser),
- Resource = ejabberd_odbc:escape(LResource),
- case ejabberd_odbc:sql_query(
+ Username = ejabberd_sql:escape(LUser),
+ Resource = ejabberd_sql:escape(LResource),
+ case ejabberd_sql:sql_query(
LServer, [<<"select usec, pid, username, ">>,
<<"resource, priority, info from sm where ">>,
<<"username='">>, Username, <<"' and resource='">>,
@@ -162,7 +162,7 @@ row_to_session(LServer, [USec, PidS, User, Resource, PrioS, InfoS]) ->
Now = timestamp_to_now(USec),
Pid = erlang:list_to_pid(binary_to_list(PidS)),
Priority = dec_priority(PrioS),
- Info = ejabberd_odbc:decode_term(InfoS),
+ Info = ejabberd_sql:decode_term(InfoS),
#session{sid = {Now, Pid}, us = {User, LServer},
usr = {User, LServer, Resource},
priority = Priority,
diff --git a/src/ejabberd_socket.erl b/src/ejabberd_socket.erl
index 16ffd192..887b4a0f 100644
--- a/src/ejabberd_socket.erl
+++ b/src/ejabberd_socket.erl
@@ -52,10 +52,10 @@
-type sockmod() :: ejabberd_http_bind |
ejabberd_http_ws |
- gen_tcp | p1_tls | ezlib.
+ gen_tcp | fast_tls | ezlib.
-type receiver() :: pid () | atom().
-type socket() :: pid() | inet:socket() |
- p1_tls:tls_socket() |
+ fast_tls:tls_socket() |
ezlib:zlib_socket() |
ejabberd_http_bind:bind_socket().
@@ -145,15 +145,15 @@ connect(Addr, Port, Opts, Timeout) ->
end.
starttls(SocketData, TLSOpts) ->
- {ok, TLSSocket} = p1_tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
ejabberd_receiver:starttls(SocketData#socket_state.receiver, TLSSocket),
- SocketData#socket_state{socket = TLSSocket, sockmod = p1_tls}.
+ SocketData#socket_state{socket = TLSSocket, sockmod = fast_tls}.
starttls(SocketData, TLSOpts, Data) ->
- {ok, TLSSocket} = p1_tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
+ {ok, TLSSocket} = fast_tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
ejabberd_receiver:starttls(SocketData#socket_state.receiver, TLSSocket),
send(SocketData, Data),
- SocketData#socket_state{socket = TLSSocket, sockmod = p1_tls}.
+ SocketData#socket_state{socket = TLSSocket, sockmod = fast_tls}.
compress(SocketData) -> compress(SocketData, undefined).
@@ -216,10 +216,10 @@ get_sockmod(SocketData) ->
SocketData#socket_state.sockmod.
get_peer_certificate(SocketData) ->
- p1_tls:get_peer_certificate(SocketData#socket_state.socket).
+ fast_tls:get_peer_certificate(SocketData#socket_state.socket).
get_verify_result(SocketData) ->
- p1_tls:get_verify_result(SocketData#socket_state.socket).
+ fast_tls:get_verify_result(SocketData#socket_state.socket).
close(SocketData) ->
ejabberd_receiver:close(SocketData#socket_state.receiver).
diff --git a/src/ejabberd_odbc.erl b/src/ejabberd_sql.erl
index a15c66b5..4bee08f7 100644
--- a/src/ejabberd_odbc.erl
+++ b/src/ejabberd_sql.erl
@@ -23,7 +23,7 @@
%%%
%%%----------------------------------------------------------------------
--module(ejabberd_odbc).
+-module(ejabberd_sql).
-behaviour(ejabberd_config).
@@ -41,6 +41,7 @@
sql_bloc/2,
escape/1,
escape_like/1,
+ escape_like_arg/1,
to_bool/1,
sqlite_db/1,
sqlite_file/1,
@@ -63,18 +64,20 @@
-include("ejabberd.hrl").
-include("logger.hrl").
+-include("ejabberd_sql_pt.hrl").
-record(state,
{db_ref = self() :: pid(),
db_type = odbc :: pgsql | mysql | sqlite | odbc | mssql,
+ db_version = undefined :: undefined | non_neg_integer(),
start_interval = 0 :: non_neg_integer(),
host = <<"">> :: binary(),
max_pending_requests_len :: non_neg_integer(),
pending_requests = {0, queue:new()} :: {non_neg_integer(), ?TQUEUE}}).
--define(STATE_KEY, ejabberd_odbc_state).
+-define(STATE_KEY, ejabberd_sql_state).
--define(NESTING_KEY, ejabberd_odbc_nesting_level).
+-define(NESTING_KEY, ejabberd_sql_nesting_level).
-define(TOP_LEVEL_TXN, 0).
@@ -92,6 +95,8 @@
-define(KEEPALIVE_QUERY, [<<"SELECT 1;">>]).
+-define(PREPARE_KEY, ejabberd_sql_prepare).
+
%%-define(DBGFSM, true).
-ifdef(DBGFSM).
@@ -108,19 +113,21 @@
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
- (?GEN_FSM):start(ejabberd_odbc, [Host],
+ (?GEN_FSM):start(ejabberd_sql, [Host],
fsm_limit_opts() ++ (?FSMOPTS)).
start_link(Host, StartInterval) ->
- (?GEN_FSM):start_link(ejabberd_odbc,
+ (?GEN_FSM):start_link(ejabberd_sql,
[Host, StartInterval],
fsm_limit_opts() ++ (?FSMOPTS)).
--type sql_query() :: [sql_query() | binary()].
+-type sql_query() :: [sql_query() | binary()] | #sql_query{} |
+ fun(() -> any()) | fun((atom(), _) -> any()).
-type sql_query_result() :: {updated, non_neg_integer()} |
{error, binary()} |
{selected, [binary()],
- [[binary()]]}.
+ [[binary()]]} |
+ {selected, [any()]}.
-spec sql_query(binary(), sql_query()) -> sql_query_result().
@@ -150,7 +157,7 @@ sql_bloc(Host, F) -> sql_call(Host, {sql_bloc, F}).
sql_call(Host, Msg) ->
case get(?STATE_KEY) of
undefined ->
- case ejabberd_odbc_sup:get_random_pid(Host) of
+ case ejabberd_sql_sup:get_random_pid(Host) of
none -> {error, <<"Unknown Host">>};
Pid ->
(?GEN_FSM):sync_send_event(Pid,{sql_cmd, Msg,
@@ -183,7 +190,7 @@ sql_query_t(Query) ->
%% Escape character that will confuse an SQL engine
escape(S) ->
- << <<(odbc_queries:escape(Char))/binary>> || <<Char>> <= S >>.
+ << <<(sql_queries:escape(Char))/binary>> || <<Char>> <= S >>.
%% Escape character that will confuse an SQL engine
%% Percent and underscore only need to be escaped for pattern matching like
@@ -192,7 +199,14 @@ escape_like(S) when is_binary(S) ->
<< <<(escape_like(C))/binary>> || <<C>> <= S >>;
escape_like($%) -> <<"\\%">>;
escape_like($_) -> <<"\\_">>;
-escape_like(C) when is_integer(C), C >= 0, C =< 255 -> odbc_queries:escape(C).
+escape_like(C) when is_integer(C), C >= 0, C =< 255 -> sql_queries:escape(C).
+
+escape_like_arg(S) when is_binary(S) ->
+ << <<(escape_like_arg(C))/binary>> || <<C>> <= S >>;
+escape_like_arg($%) -> <<"\\%">>;
+escape_like_arg($_) -> <<"\\_">>;
+escape_like_arg($\\) -> <<"\\\\">>;
+escape_like_arg(C) when is_integer(C), C >= 0, C =< 255 -> <<C>>.
to_bool(<<"t">>) -> true;
to_bool(<<"true">>) -> true;
@@ -218,7 +232,7 @@ sqlite_db(Host) ->
-spec sqlite_file(binary()) -> string().
sqlite_file(Host) ->
- case ejabberd_config:get_option({odbc_database, Host},
+ case ejabberd_config:get_option({sql_database, Host},
fun iolist_to_binary/1) of
undefined ->
{ok, Cwd} = file:get_cwd(),
@@ -233,7 +247,7 @@ sqlite_file(Host) ->
%%%----------------------------------------------------------------------
init([Host, StartInterval]) ->
case ejabberd_config:get_option(
- {odbc_keepalive_interval, Host},
+ {sql_keepalive_interval, Host},
fun(I) when is_integer(I), I>0 -> I end) of
undefined ->
ok;
@@ -243,7 +257,7 @@ init([Host, StartInterval]) ->
end,
[DBType | _] = db_opts(Host),
(?GEN_FSM):send_event(self(), connect),
- ejabberd_odbc_sup:add_pid(Host, self()),
+ ejabberd_sql_sup:add_pid(Host, self()),
{ok, connecting,
#state{db_type = DBType, host = Host,
max_pending_requests_len = max_fsm_queue(),
@@ -255,19 +269,21 @@ connecting(connect, #state{host = Host} = State) ->
[mysql | Args] -> apply(fun mysql_connect/5, Args);
[pgsql | Args] -> apply(fun pgsql_connect/5, Args);
[sqlite | Args] -> apply(fun sqlite_connect/1, Args);
+ [mssql | Args] -> apply(fun odbc_connect/1, Args);
[odbc | Args] -> apply(fun odbc_connect/1, Args)
end,
{_, PendingRequests} = State#state.pending_requests,
case ConnectRes of
- {ok, Ref} ->
- erlang:monitor(process, Ref),
- lists:foreach(fun (Req) ->
- (?GEN_FSM):send_event(self(), Req)
- end,
- queue:to_list(PendingRequests)),
- {next_state, session_established,
- State#state{db_ref = Ref,
- pending_requests = {0, queue:new()}}};
+ {ok, Ref} ->
+ erlang:monitor(process, Ref),
+ lists:foreach(fun (Req) ->
+ (?GEN_FSM):send_event(self(), Req)
+ end,
+ queue:to_list(PendingRequests)),
+ State1 = State#state{db_ref = Ref,
+ pending_requests = {0, queue:new()}},
+ 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",
@@ -355,7 +371,7 @@ handle_info(Info, StateName, State) ->
{next_state, StateName, State}.
terminate(_Reason, _StateName, State) ->
- ejabberd_odbc_sup:remove_pid(State#state.host, self()),
+ ejabberd_sql_sup:remove_pid(State#state.host, self()),
case State#state.db_type of
mysql -> catch p1_mysql_conn:stop(State#state.db_ref);
sqlite -> catch sqlite3:close(sqlite_db(State#state.host));
@@ -469,12 +485,82 @@ execute_bloc(F) ->
Res -> {atomic, Res}
end.
+execute_fun(F) when is_function(F, 0) ->
+ F();
+execute_fun(F) when is_function(F, 2) ->
+ State = get(?STATE_KEY),
+ F(State#state.db_type, State#state.db_version).
+
+sql_query_internal([{_, _} | _] = Queries) ->
+ State = get(?STATE_KEY),
+ case select_sql_query(Queries, State) of
+ undefined ->
+ {error, <<"no matching query for the current DBMS found">>};
+ Query ->
+ sql_query_internal(Query)
+ end;
+sql_query_internal(#sql_query{} = Query) ->
+ State = get(?STATE_KEY),
+ Res =
+ try
+ case State#state.db_type of
+ odbc ->
+ generic_sql_query(Query);
+ mssql ->
+ generic_sql_query(Query);
+ pgsql ->
+ Key = {?PREPARE_KEY, Query#sql_query.hash},
+ case get(Key) of
+ undefined ->
+ case pgsql_prepare(Query, State) of
+ {ok, _, _, _} ->
+ put(Key, prepared);
+ {error, Error} ->
+ ?ERROR_MSG("PREPARE failed for SQL query "
+ "at ~p: ~p",
+ [Query#sql_query.loc, Error]),
+ put(Key, ignore)
+ end;
+ _ ->
+ ok
+ end,
+ case get(Key) of
+ prepared ->
+ pgsql_execute_sql_query(Query, State);
+ _ ->
+ generic_sql_query(Query)
+ end;
+ mysql ->
+ generic_sql_query(Query);
+ sqlite ->
+ generic_sql_query(Query)
+ end
+ catch
+ Class:Reason ->
+ ST = erlang:get_stacktrace(),
+ ?ERROR_MSG("Internal error while processing SQL query: ~p",
+ [{Class, Reason, ST}]),
+ {error, <<"internal error">>}
+ end,
+ case Res of
+ {error, <<"No SQL-driver information available.">>} ->
+ {updated, 0};
+ _Else -> Res
+ end;
+sql_query_internal(F) when is_function(F) ->
+ case catch execute_fun(F) of
+ {'EXIT', Reason} -> {error, Reason};
+ Res -> Res
+ end;
sql_query_internal(Query) ->
State = get(?STATE_KEY),
?DEBUG("SQL: \"~s\"", [Query]),
Res = case State#state.db_type of
odbc ->
- to_odbc(odbc:sql_query(State#state.db_ref, Query,
+ to_odbc(odbc:sql_query(State#state.db_ref, [Query],
+ (?TRANSACTION_TIMEOUT) - 1000));
+ mssql ->
+ to_odbc(odbc:sql_query(State#state.db_ref, [Query],
(?TRANSACTION_TIMEOUT) - 1000));
pgsql ->
pgsql_to_odbc(pgsql:squery(State#state.db_ref, Query));
@@ -495,6 +581,93 @@ sql_query_internal(Query) ->
_Else -> Res
end.
+select_sql_query(Queries, State) ->
+ select_sql_query(
+ Queries, State#state.db_type, State#state.db_version, undefined).
+
+select_sql_query([], _Type, _Version, undefined) ->
+ undefined;
+select_sql_query([], _Type, _Version, Query) ->
+ Query;
+select_sql_query([{any, Query} | _], _Type, _Version, _) ->
+ Query;
+select_sql_query([{Type, Query} | _], Type, _Version, _) ->
+ Query;
+select_sql_query([{{Type, _Version1}, Query1} | Rest], Type, undefined, _) ->
+ select_sql_query(Rest, Type, undefined, Query1);
+select_sql_query([{{Type, Version1}, Query1} | Rest], Type, Version, Query) ->
+ if
+ Version >= Version1 ->
+ Query1;
+ true ->
+ select_sql_query(Rest, Type, Version, Query)
+ end;
+select_sql_query([{_, _} | Rest], Type, Version, Query) ->
+ select_sql_query(Rest, Type, Version, Query).
+
+generic_sql_query(SQLQuery) ->
+ sql_query_format_res(
+ sql_query_internal(generic_sql_query_format(SQLQuery)),
+ SQLQuery).
+
+generic_sql_query_format(SQLQuery) ->
+ Args = (SQLQuery#sql_query.args)(generic_escape()),
+ (SQLQuery#sql_query.format_query)(Args).
+
+generic_escape() ->
+ #sql_escape{string = fun(X) -> <<"'", (escape(X))/binary, "'">> end,
+ integer = fun(X) -> integer_to_binary(X) end,
+ boolean = fun(true) -> <<"1">>;
+ (false) -> <<"0">>
+ end
+ }.
+
+pgsql_prepare(SQLQuery, State) ->
+ Escape = #sql_escape{_ = fun(X) -> X end},
+ N = length((SQLQuery#sql_query.args)(Escape)),
+ Args = [<<$$, (integer_to_binary(I))/binary>> || I <- lists:seq(1, N)],
+ Query = (SQLQuery#sql_query.format_query)(Args),
+ pgsql:prepare(State#state.db_ref, SQLQuery#sql_query.hash, Query).
+
+pgsql_execute_escape() ->
+ #sql_escape{string = fun(X) -> X end,
+ integer = fun(X) -> [integer_to_binary(X)] end,
+ boolean = fun(true) -> "1";
+ (false) -> "0"
+ end
+ }.
+
+pgsql_execute_sql_query(SQLQuery, State) ->
+ Args = (SQLQuery#sql_query.args)(pgsql_execute_escape()),
+ ExecuteRes =
+ pgsql:execute(State#state.db_ref, SQLQuery#sql_query.hash, Args),
+% {T, ExecuteRes} =
+% timer:tc(pgsql, execute, [State#state.db_ref, SQLQuery#sql_query.hash, Args]),
+% io:format("T ~s ~p~n", [SQLQuery#sql_query.hash, T]),
+ Res = pgsql_execute_to_odbc(ExecuteRes),
+ sql_query_format_res(Res, SQLQuery).
+
+
+sql_query_format_res({selected, _, Rows}, SQLQuery) ->
+ Res =
+ lists:flatmap(
+ fun(Row) ->
+ try
+ [(SQLQuery#sql_query.format_res)(Row)]
+ catch
+ Class:Reason ->
+ ST = erlang:get_stacktrace(),
+ ?ERROR_MSG("Error while processing "
+ "SQL query result: ~p~n"
+ "row: ~p",
+ [{Class, Reason, ST}, Row]),
+ []
+ end
+ end, Rows),
+ {selected, Res};
+sql_query_format_res(Res, _SQLQuery) ->
+ Res.
+
%% Generate the OTP callback return tuple depending on the driver result.
abort_on_driver_error({error, <<"query timed out">>} =
Reply,
@@ -606,6 +779,18 @@ pgsql_item_to_odbc(<<"UPDATE ", N/binary>>) ->
pgsql_item_to_odbc({error, Error}) -> {error, Error};
pgsql_item_to_odbc(_) -> {updated, undefined}.
+pgsql_execute_to_odbc({ok, {<<"SELECT", _/binary>>, Rows}}) ->
+ {selected, [], [[Field || {_, Field} <- Row] || Row <- Rows]};
+pgsql_execute_to_odbc({ok, {'INSERT', N}}) ->
+ {updated, N};
+pgsql_execute_to_odbc({ok, {'DELETE', N}}) ->
+ {updated, N};
+pgsql_execute_to_odbc({ok, {'UPDATE', N}}) ->
+ {updated, N};
+pgsql_execute_to_odbc({error, Error}) -> {error, Error};
+pgsql_execute_to_odbc(_) -> {updated, undefined}.
+
+
%% == Native MySQL code
%% part of init/1
@@ -658,6 +843,24 @@ to_odbc({error, Reason}) when is_list(Reason) ->
to_odbc(Res) ->
Res.
+get_db_version(#state{db_type = pgsql} = State) ->
+ case pgsql:squery(State#state.db_ref,
+ <<"select current_setting('server_version_num')">>) of
+ {ok, [{_, _, [[SVersion]]}]} ->
+ case catch binary_to_integer(SVersion) of
+ Version when is_integer(Version) ->
+ State#state{db_version = Version};
+ Error ->
+ ?WARNING_MSG("error getting pgsql version: ~p", [Error]),
+ State
+ end;
+ Res ->
+ ?WARNING_MSG("error getting pgsql version: ~p", [Res]),
+ State
+ end;
+get_db_version(State) ->
+ State.
+
log(Level, Format, Args) ->
case Level of
debug -> ?DEBUG(Format, Args);
@@ -666,14 +869,14 @@ log(Level, Format, Args) ->
end.
db_opts(Host) ->
- Type = ejabberd_config:get_option({odbc_type, Host},
+ Type = ejabberd_config:get_option({sql_type, Host},
fun(mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
(mssql) -> mssql;
(odbc) -> odbc
end, odbc),
- Server = ejabberd_config:get_option({odbc_server, Host},
+ Server = ejabberd_config:get_option({sql_server, Host},
fun iolist_to_binary/1,
<<"localhost">>),
case Type of
@@ -683,41 +886,40 @@ db_opts(Host) ->
[sqlite, Host];
_ ->
Port = ejabberd_config:get_option(
- {odbc_port, Host},
+ {sql_port, Host},
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
case Type of
mssql -> ?MSSQL_PORT;
mysql -> ?MYSQL_PORT;
pgsql -> ?PGSQL_PORT
end),
- DB = ejabberd_config:get_option({odbc_database, Host},
+ DB = ejabberd_config:get_option({sql_database, Host},
fun iolist_to_binary/1,
<<"ejabberd">>),
- User = ejabberd_config:get_option({odbc_username, Host},
+ User = ejabberd_config:get_option({sql_username, Host},
fun iolist_to_binary/1,
<<"ejabberd">>),
- Pass = ejabberd_config:get_option({odbc_password, Host},
+ Pass = ejabberd_config:get_option({sql_password, Host},
fun iolist_to_binary/1,
<<"">>),
case Type of
mssql ->
- Username = get_mssql_user(Server, User),
- [odbc, <<"DSN=", Host/binary, ";UID=", Username/binary,
- ";PWD=", Pass/binary>>];
+ [mssql, <<"DSN=", Host/binary, ";UID=", User/binary,
+ ";PWD=", Pass/binary>>];
_ ->
[Type, Server, Port, DB, User, Pass]
end
end.
init_mssql(Host) ->
- Server = ejabberd_config:get_option({odbc_server, Host},
+ Server = ejabberd_config:get_option({sql_server, Host},
fun iolist_to_binary/1,
<<"localhost">>),
Port = ejabberd_config:get_option(
- {odbc_port, Host},
+ {sql_port, Host},
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
?MSSQL_PORT),
- DB = ejabberd_config:get_option({odbc_database, Host},
+ DB = ejabberd_config:get_option({sql_database, Host},
fun iolist_to_binary/1,
<<"ejabberd">>),
FreeTDS = io_lib:fwrite("[~s]~n"
@@ -762,21 +964,6 @@ init_mssql(Host) ->
Err
end.
-get_mssql_user(Server, User) ->
- HostName = case inet_parse:address(binary_to_list(Server)) of
- {ok, _} ->
- Server;
- {error, _} ->
- hd(str:tokens(Server, <<".">>))
- end,
- UserName = case str:chr(User, $@) of
- 0 ->
- <<User/binary, $@, HostName/binary>>;
- _ ->
- User
- end,
- UserName.
-
tmp_dir() ->
filename:join(["/tmp", "ejabberd"]).
@@ -800,30 +987,39 @@ fsm_limit_opts() ->
_ -> []
end.
+check_error({error, Why} = Err, #sql_query{} = Query) ->
+ ?ERROR_MSG("SQL query '~s' at ~p failed: ~p",
+ [Query#sql_query.hash, Query#sql_query.loc, Why]),
+ Err;
check_error({error, Why} = Err, Query) ->
- ?ERROR_MSG("SQL query '~s' failed: ~p", [Query, 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 ~p failed: ~p", [Query, Why])
+ end,
Err;
check_error(Result, _Query) ->
Result.
opt_type(max_fsm_queue) ->
fun (N) when is_integer(N), N > 0 -> N end;
-opt_type(odbc_database) -> fun iolist_to_binary/1;
-opt_type(odbc_keepalive_interval) ->
+opt_type(sql_database) -> fun iolist_to_binary/1;
+opt_type(sql_keepalive_interval) ->
fun (I) when is_integer(I), I > 0 -> I end;
-opt_type(odbc_password) -> fun iolist_to_binary/1;
-opt_type(odbc_port) ->
+opt_type(sql_password) -> fun iolist_to_binary/1;
+opt_type(sql_port) ->
fun (P) when is_integer(P), P > 0, P < 65536 -> P end;
-opt_type(odbc_server) -> fun iolist_to_binary/1;
-opt_type(odbc_type) ->
+opt_type(sql_server) -> fun iolist_to_binary/1;
+opt_type(sql_type) ->
fun (mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
(mssql) -> mssql;
(odbc) -> odbc
end;
-opt_type(odbc_username) -> fun iolist_to_binary/1;
+opt_type(sql_username) -> fun iolist_to_binary/1;
opt_type(_) ->
- [max_fsm_queue, odbc_database, odbc_keepalive_interval,
- odbc_password, odbc_port, odbc_server, odbc_type,
- odbc_username].
+ [max_fsm_queue, sql_database, sql_keepalive_interval,
+ sql_password, sql_port, sql_server, sql_type,
+ sql_username].
diff --git a/src/ejabberd_sql_pt.erl b/src/ejabberd_sql_pt.erl
new file mode 100644
index 00000000..47e4a07c
--- /dev/null
+++ b/src/ejabberd_sql_pt.erl
@@ -0,0 +1,544 @@
+%%%-------------------------------------------------------------------
+%%% File : ejabberd_sql_pt.erl
+%%% Author : Alexey Shchepin <alexey@process-one.net>
+%%% Description : Parse transform for SQL queries
+%%%
+%%% Created : 20 Jan 2016 by Alexey Shchepin <alexey@process-one.net>
+%%%-------------------------------------------------------------------
+-module(ejabberd_sql_pt).
+
+%% API
+-export([parse_transform/2]).
+
+-export([parse/2]).
+
+-include("ejabberd_sql_pt.hrl").
+
+-record(state, {loc,
+ 'query' = [],
+ params = [],
+ param_pos = 0,
+ args = [],
+ res = [],
+ res_vars = [],
+ res_pos = 0}).
+
+-define(QUERY_RECORD, "sql_query").
+
+-define(ESCAPE_RECORD, "sql_escape").
+-define(ESCAPE_VAR, "__SQLEscape").
+
+-define(MOD, sql__module_).
+
+%%====================================================================
+%% API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function:
+%% Description:
+%%--------------------------------------------------------------------
+parse_transform(AST, _Options) ->
+ %io:format("PT: ~p~nOpts: ~p~n", [AST, Options]),
+ NewAST = top_transform(AST),
+ %io:format("NewPT: ~p~n", [NewAST]),
+ NewAST.
+
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+
+transform(Form) ->
+ case erl_syntax:type(Form) of
+ application ->
+ case erl_syntax_lib:analyze_application(Form) of
+ {?SQL_MARK, 1} ->
+ case erl_syntax:application_arguments(Form) of
+ [Arg] ->
+ case erl_syntax:type(Arg) of
+ string ->
+ S = erl_syntax:string_value(Arg),
+ Pos = erl_syntax:get_pos(Arg),
+ ParseRes = parse(S, Pos),
+ set_pos(make_sql_query(ParseRes), Pos);
+ _ ->
+ throw({error, erl_syntax:get_pos(Form),
+ "?SQL argument must be "
+ "a constant string"})
+ end;
+ _ ->
+ throw({error, erl_syntax:get_pos(Form),
+ "wrong number of ?SQL args"})
+ end;
+ {?SQL_UPSERT_MARK, 2} ->
+ case erl_syntax:application_arguments(Form) of
+ [TableArg, FieldsArg] ->
+ case {erl_syntax:type(TableArg),
+ erl_syntax:is_proper_list(FieldsArg)}of
+ {string, true} ->
+ Table = erl_syntax:string_value(TableArg),
+ ParseRes =
+ parse_upsert(
+ erl_syntax:list_elements(FieldsArg)),
+ Pos = erl_syntax:get_pos(Form),
+ set_pos(
+ make_sql_upsert(Table, ParseRes, Pos),
+ Pos);
+ _ ->
+ throw({error, erl_syntax:get_pos(Form),
+ "?SQL_UPSERT arguments must be "
+ "a constant string and a list"})
+ end;
+ _ ->
+ throw({error, erl_syntax:get_pos(Form),
+ "wrong number of ?SQL_UPSERT args"})
+ end;
+ _ ->
+ Form
+ end;
+ attribute ->
+ case erl_syntax:atom_value(erl_syntax:attribute_name(Form)) of
+ module ->
+ case erl_syntax:attribute_arguments(Form) of
+ [M | _] ->
+ Module = erl_syntax:atom_value(M),
+ %io:format("module ~p~n", [Module]),
+ put(?MOD, Module),
+ Form;
+ _ ->
+ Form
+ end;
+ _ ->
+ Form
+ end;
+ _ ->
+ Form
+ end.
+
+top_transform(Forms) when is_list(Forms) ->
+ lists:map(
+ fun(Form) ->
+ try
+ Form2 = erl_syntax_lib:map(
+ fun(Node) ->
+ %io:format("asd ~p~n", [Node]),
+ transform(Node)
+ end, Form),
+ Form3 = erl_syntax:revert(Form2),
+ Form3
+ catch
+ throw:{error, Line, Error} ->
+ {error, {Line, erl_parse, Error}}
+ end
+ end, Forms).
+
+parse(S, Loc) ->
+ parse1(S, [], #state{loc = Loc}).
+
+parse(S, ParamPos, Loc) ->
+ parse1(S, [], #state{loc = Loc, param_pos = ParamPos}).
+
+parse1([], Acc, State) ->
+ State1 = append_string(lists:reverse(Acc), State),
+ State1#state{'query' = lists:reverse(State1#state.'query'),
+ params = lists:reverse(State1#state.params),
+ args = lists:reverse(State1#state.args),
+ res = lists:reverse(State1#state.res),
+ res_vars = lists:reverse(State1#state.res_vars)
+ };
+parse1([$@, $( | S], Acc, State) ->
+ State1 = append_string(lists:reverse(Acc), State),
+ {Name, Type, S1, State2} = parse_name(S, State1),
+ Var = "__V" ++ integer_to_list(State2#state.res_pos),
+ EVar = erl_syntax:variable(Var),
+ Convert =
+ case Type of
+ integer ->
+ erl_syntax:application(
+ erl_syntax:atom(binary_to_integer),
+ [EVar]);
+ string ->
+ EVar;
+ boolean ->
+ erl_syntax:application(
+ erl_syntax:atom(ejabberd_sql),
+ erl_syntax:atom(to_bool),
+ [EVar])
+ end,
+ State3 = append_string(Name, State2),
+ State4 = State3#state{res_pos = State3#state.res_pos + 1,
+ res = [Convert | State3#state.res],
+ res_vars = [EVar | State3#state.res_vars]},
+ parse1(S1, [], State4);
+parse1([$%, $( | S], Acc, State) ->
+ State1 = append_string(lists:reverse(Acc), State),
+ {Name, Type, S1, State2} = parse_name(S, State1),
+ Var = State2#state.param_pos,
+ Convert =
+ erl_syntax:application(
+ erl_syntax:record_access(
+ erl_syntax:variable(?ESCAPE_VAR),
+ erl_syntax:atom(?ESCAPE_RECORD),
+ erl_syntax:atom(Type)),
+ [erl_syntax:variable(Name)]),
+ State3 = State2,
+ State4 =
+ State3#state{'query' = [{var, Var} | State3#state.'query'],
+ args = [Convert | State3#state.args],
+ params = [Var | State3#state.params],
+ param_pos = State3#state.param_pos + 1},
+ parse1(S1, [], State4);
+parse1([C | S], Acc, State) ->
+ parse1(S, [C | Acc], State).
+
+append_string([], State) ->
+ State;
+append_string(S, State) ->
+ State#state{query = [{str, S} | State#state.query]}.
+
+parse_name(S, State) ->
+ parse_name(S, [], 0, State).
+
+parse_name([], _Acc, _Depth, State) ->
+ throw({error, State#state.loc,
+ "expected ')', found end of string"});
+parse_name([$), T | S], Acc, 0, State) ->
+ Type =
+ case T of
+ $d -> integer;
+ $s -> string;
+ $b -> boolean;
+ _ ->
+ throw({error, State#state.loc,
+ ["unknown type specifier '", T, "'"]})
+ end,
+ {lists:reverse(Acc), Type, S, State};
+parse_name([$)], _Acc, 0, State) ->
+ throw({error, State#state.loc,
+ "expected type specifier, found end of string"});
+parse_name([$( = C | S], Acc, Depth, State) ->
+ parse_name(S, [C | Acc], Depth + 1, State);
+parse_name([$) = C | S], Acc, Depth, State) ->
+ parse_name(S, [C | Acc], Depth - 1, State);
+parse_name([C | S], Acc, Depth, State) ->
+ parse_name(S, [C | Acc], Depth, State).
+
+
+make_var(V) ->
+ Var = "__V" ++ integer_to_list(V),
+ erl_syntax:variable(Var).
+
+
+make_sql_query(State) ->
+ Hash = erlang:phash2(State#state{loc = undefined}),
+ SHash = <<"Q", (integer_to_binary(Hash))/binary>>,
+ Query = pack_query(State#state.'query'),
+ EQuery =
+ lists:map(
+ fun({str, S}) ->
+ erl_syntax:binary(
+ [erl_syntax:binary_field(
+ erl_syntax:string(S))]);
+ ({var, V}) -> make_var(V)
+ end, Query),
+ erl_syntax:record_expr(
+ erl_syntax:atom(?QUERY_RECORD),
+ [erl_syntax:record_field(
+ erl_syntax:atom(hash),
+ %erl_syntax:abstract(SHash)
+ erl_syntax:binary(
+ [erl_syntax:binary_field(
+ erl_syntax:string(binary_to_list(SHash)))])),
+ erl_syntax:record_field(
+ erl_syntax:atom(args),
+ erl_syntax:fun_expr(
+ [erl_syntax:clause(
+ [erl_syntax:variable(?ESCAPE_VAR)],
+ none,
+ [erl_syntax:list(State#state.args)]
+ )])),
+ erl_syntax:record_field(
+ erl_syntax:atom(format_query),
+ erl_syntax:fun_expr(
+ [erl_syntax:clause(
+ [erl_syntax:list(lists:map(fun make_var/1, State#state.params))],
+ none,
+ [erl_syntax:list(EQuery)]
+ )])),
+ erl_syntax:record_field(
+ erl_syntax:atom(format_res),
+ erl_syntax:fun_expr(
+ [erl_syntax:clause(
+ [erl_syntax:list(State#state.res_vars)],
+ none,
+ [erl_syntax:tuple(State#state.res)]
+ )])),
+ erl_syntax:record_field(
+ erl_syntax:atom(loc),
+ erl_syntax:abstract({get(?MOD), State#state.loc}))
+ ]).
+
+pack_query([]) ->
+ [];
+pack_query([{str, S1}, {str, S2} | Rest]) ->
+ pack_query([{str, S1 ++ S2} | Rest]);
+pack_query([X | Rest]) ->
+ [X | pack_query(Rest)].
+
+
+parse_upsert(Fields) ->
+ {Fs, _} =
+ lists:foldr(
+ fun(F, {Acc, Param}) ->
+ case erl_syntax:type(F) of
+ string ->
+ V = erl_syntax:string_value(F),
+ {_, _, State} = Res =
+ parse_upsert_field(
+ V, Param, erl_syntax:get_pos(F)),
+ {[Res | Acc], State#state.param_pos};
+ _ ->
+ throw({error, erl_syntax:get_pos(F),
+ "?SQL_UPSERT field must be "
+ "a constant string"})
+ end
+ end, {[], 0}, Fields),
+ %io:format("asd ~p~n", [{Fields, Fs}]),
+ Fs.
+
+parse_upsert_field([$! | S], ParamPos, Loc) ->
+ {Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc),
+ {Name, true, ParseState};
+parse_upsert_field(S, ParamPos, Loc) ->
+ {Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc),
+ {Name, false, ParseState}.
+
+parse_upsert_field1([], _Acc, _ParamPos, Loc) ->
+ throw({error, Loc,
+ "?SQL_UPSERT fields must have the "
+ "following form: \"[!]name=value\""});
+parse_upsert_field1([$= | S], Acc, ParamPos, Loc) ->
+ {lists:reverse(Acc), parse(S, ParamPos, Loc)};
+parse_upsert_field1([C | S], Acc, ParamPos, Loc) ->
+ parse_upsert_field1(S, [C | Acc], ParamPos, Loc).
+
+
+make_sql_upsert(Table, ParseRes, Pos) ->
+ check_upsert(ParseRes, Pos),
+ erl_syntax:fun_expr(
+ [erl_syntax:clause(
+ [erl_syntax:atom(pgsql), erl_syntax:variable("__Version")],
+ [erl_syntax:infix_expr(
+ erl_syntax:variable("__Version"),
+ erl_syntax:operator('>='),
+ erl_syntax:integer(90100))],
+ [make_sql_upsert_pgsql901(Table, ParseRes),
+ erl_syntax:atom(ok)]),
+ erl_syntax:clause(
+ [erl_syntax:underscore(), erl_syntax:underscore()],
+ none,
+ [make_sql_upsert_generic(Table, ParseRes)])
+ ]).
+
+make_sql_upsert_generic(Table, ParseRes) ->
+ Update = make_sql_query(make_sql_upsert_update(Table, ParseRes)),
+ Insert = make_sql_query(make_sql_upsert_insert(Table, ParseRes)),
+ InsertBranch =
+ erl_syntax:case_expr(
+ erl_syntax:application(
+ erl_syntax:atom(ejabberd_sql),
+ erl_syntax:atom(sql_query_t),
+ [Insert]),
+ [erl_syntax:clause(
+ [erl_syntax:abstract({updated, 1})],
+ none,
+ [erl_syntax:atom(ok)]),
+ erl_syntax:clause(
+ [erl_syntax:variable("__UpdateRes")],
+ none,
+ [erl_syntax:variable("__UpdateRes")])]),
+ erl_syntax:case_expr(
+ erl_syntax:application(
+ erl_syntax:atom(ejabberd_sql),
+ erl_syntax:atom(sql_query_t),
+ [Update]),
+ [erl_syntax:clause(
+ [erl_syntax:abstract({updated, 1})],
+ none,
+ [erl_syntax:atom(ok)]),
+ erl_syntax:clause(
+ [erl_syntax:underscore()],
+ none,
+ [InsertBranch])]).
+
+make_sql_upsert_update(Table, ParseRes) ->
+ WPairs =
+ lists:flatmap(
+ fun({_Field, false, _ST}) ->
+ [];
+ ({Field, true, ST}) ->
+ [ST#state{
+ 'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'
+ }]
+ end, ParseRes),
+ Where = join_states(WPairs, " AND "),
+ SPairs =
+ lists:flatmap(
+ fun({_Field, true, _ST}) ->
+ [];
+ ({Field, false, ST}) ->
+ [ST#state{
+ 'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'
+ }]
+ end, ParseRes),
+ Set = join_states(SPairs, ", "),
+ State =
+ concat_states(
+ [#state{'query' = [{str, "UPDATE "}, {str, Table}, {str, " SET "}]},
+ Set,
+ #state{'query' = [{str, " WHERE "}]},
+ Where
+ ]),
+ State.
+
+make_sql_upsert_insert(Table, ParseRes) ->
+ Vals =
+ lists:map(
+ fun({_Field, _, ST}) ->
+ ST
+ end, ParseRes),
+ Fields =
+ lists:map(
+ fun({Field, _, _ST}) ->
+ #state{'query' = [{str, Field}]}
+ end, ParseRes),
+ State =
+ concat_states(
+ [#state{'query' = [{str, "INSERT INTO "}, {str, Table}, {str, "("}]},
+ join_states(Fields, ", "),
+ #state{'query' = [{str, ") VALUES ("}]},
+ join_states(Vals, ", "),
+ #state{'query' = [{str, ")"}]}
+ ]),
+ State.
+
+make_sql_upsert_pgsql901(Table, ParseRes) ->
+ Update = make_sql_upsert_update(Table, ParseRes),
+ Vals =
+ lists:map(
+ fun({_Field, _, ST}) ->
+ ST
+ end, ParseRes),
+ Fields =
+ lists:map(
+ fun({Field, _, _ST}) ->
+ #state{'query' = [{str, Field}]}
+ end, ParseRes),
+ Insert =
+ concat_states(
+ [#state{'query' = [{str, "INSERT INTO "}, {str, Table}, {str, "("}]},
+ join_states(Fields, ", "),
+ #state{'query' = [{str, ") SELECT "}]},
+ join_states(Vals, ", "),
+ #state{'query' = [{str, " WHERE NOT EXISTS (SELECT * FROM upsert)"}]}
+ ]),
+ State =
+ concat_states(
+ [#state{'query' = [{str, "WITH upsert AS ("}]},
+ Update,
+ #state{'query' = [{str, " RETURNING *) "}]},
+ Insert
+ ]),
+ Upsert = make_sql_query(State),
+ erl_syntax:application(
+ erl_syntax:atom(ejabberd_sql),
+ erl_syntax:atom(sql_query_t),
+ [Upsert]).
+
+
+check_upsert(ParseRes, Pos) ->
+ Set =
+ lists:filter(
+ fun({_Field, Match, _ST}) ->
+ not Match
+ end, ParseRes),
+ case Set of
+ [] ->
+ throw({error, Pos,
+ "No ?SQL_UPSERT fields to set, use INSERT instead"});
+ _ ->
+ ok
+ end,
+ ok.
+
+
+concat_states(States) ->
+ lists:foldr(
+ fun(ST11, ST2) ->
+ ST1 = resolve_vars(ST11, ST2),
+ ST1#state{
+ 'query' = ST1#state.'query' ++ ST2#state.'query',
+ params = ST1#state.params ++ ST2#state.params,
+ args = ST1#state.args ++ ST2#state.args,
+ res = ST1#state.res ++ ST2#state.res,
+ res_vars = ST1#state.res_vars ++ ST2#state.res_vars,
+ loc = case ST1#state.loc of
+ undefined -> ST2#state.loc;
+ _ -> ST1#state.loc
+ end
+ }
+ end, #state{}, States).
+
+resolve_vars(ST1, ST2) ->
+ Max = lists:max([0 | ST1#state.params ++ ST2#state.params]),
+ {Map, _} =
+ lists:foldl(
+ fun(Var, {Acc, New}) ->
+ case lists:member(Var, ST2#state.params) of
+ true ->
+ {dict:store(Var, New, Acc), New + 1};
+ false ->
+ {Acc, New}
+ end
+ end, {dict:new(), Max + 1}, ST1#state.params),
+ NewParams =
+ lists:map(
+ fun(Var) ->
+ case dict:find(Var, Map) of
+ {ok, New} ->
+ New;
+ error ->
+ Var
+ end
+ end, ST1#state.params),
+ NewQuery =
+ lists:map(
+ fun({var, Var}) ->
+ case dict:find(Var, Map) of
+ {ok, New} ->
+ {var, New};
+ error ->
+ {var, Var}
+ end;
+ (S) -> S
+ end, ST1#state.'query'),
+ ST1#state{params = NewParams, 'query' = NewQuery}.
+
+
+join_states([], _Sep) ->
+ #state{};
+join_states([H | T], Sep) ->
+ J = [[H] | [[#state{'query' = [{str, Sep}]}, X] || X <- T]],
+ concat_states(lists:append(J)).
+
+
+set_pos(Tree, Pos) ->
+ erl_syntax_lib:map(
+ fun(Node) ->
+ case erl_syntax:get_pos(Node) of
+ 0 -> erl_syntax:set_pos(Node, Pos);
+ _ -> Node
+ end
+ end, Tree).
diff --git a/src/ejabberd_odbc_sup.erl b/src/ejabberd_sql_sup.erl
index 65fa3c1c..68241455 100644
--- a/src/ejabberd_odbc_sup.erl
+++ b/src/ejabberd_sql_sup.erl
@@ -1,7 +1,7 @@
%%%----------------------------------------------------------------------
-%%% File : ejabberd_odbc_sup.erl
+%%% File : ejabberd_sql_sup.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
-%%% Purpose : ODBC connections supervisor
+%%% Purpose : SQL connections supervisor
%%% Created : 22 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
@@ -23,7 +23,7 @@
%%%
%%%----------------------------------------------------------------------
--module(ejabberd_odbc_sup).
+-module(ejabberd_sql_sup).
-behaviour(ejabberd_config).
@@ -42,7 +42,7 @@
-define(DEFAULT_POOL_SIZE, 10).
--define(DEFAULT_ODBC_START_INTERVAL, 30).
+-define(DEFAULT_SQL_START_INTERVAL, 30).
-define(CONNECT_TIMEOUT, 500).
@@ -62,14 +62,14 @@ start_link(Host) ->
init([Host]) ->
PoolSize = ejabberd_config:get_option(
- {odbc_pool_size, Host},
+ {sql_pool_size, Host},
fun(I) when is_integer(I), I>0 -> I end,
?DEFAULT_POOL_SIZE),
StartInterval = ejabberd_config:get_option(
- {odbc_start_interval, Host},
+ {sql_start_interval, Host},
fun(I) when is_integer(I), I>0 -> I end,
- ?DEFAULT_ODBC_START_INTERVAL),
- Type = ejabberd_config:get_option({odbc_type, Host},
+ ?DEFAULT_SQL_START_INTERVAL),
+ Type = ejabberd_config:get_option({sql_type, Host},
fun(mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
@@ -80,7 +80,7 @@ init([Host]) ->
sqlite ->
check_sqlite_db(Host);
mssql ->
- ejabberd_odbc:init_mssql(Host);
+ ejabberd_sql:init_mssql(Host);
_ ->
ok
end,
@@ -89,7 +89,7 @@ init([Host]) ->
{{one_for_one, PoolSize * 10, 1},
lists:map(fun (I) ->
{I,
- {ejabberd_odbc, start_link,
+ {ejabberd_sql, start_link,
[Host, StartInterval * 1000]},
transient, 2000, worker, [?MODULE]}
end,
@@ -121,12 +121,12 @@ transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
transform_options({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) ->
- [{odbc_type, Type},
- {odbc_server, Server},
- {odbc_port, Port},
- {odbc_database, DB},
- {odbc_username, User},
- {odbc_password, Pass}|Opts];
+ [{sql_type, Type},
+ {sql_server, Server},
+ {sql_port, Port},
+ {sql_database, DB},
+ {sql_username, User},
+ {sql_password, Pass}|Opts];
transform_options({odbc_server, {mysql, Server, DB, User, Pass}}, Opts) ->
transform_options({odbc_server, {mysql, Server, ?MYSQL_PORT, DB, User, Pass}}, Opts);
transform_options({odbc_server, {pgsql, Server, DB, User, Pass}}, Opts) ->
@@ -137,8 +137,8 @@ transform_options(Opt, Opts) ->
[Opt|Opts].
check_sqlite_db(Host) ->
- DB = ejabberd_odbc:sqlite_db(Host),
- File = ejabberd_odbc:sqlite_file(Host),
+ DB = ejabberd_sql:sqlite_db(Host),
+ File = ejabberd_sql:sqlite_file(Host),
Ret = case filelib:ensure_dir(File) of
ok ->
case sqlite3:open(DB, [{file, File}]) of
@@ -211,11 +211,11 @@ read_lines(Fd, File, Acc) ->
[]
end.
-opt_type(odbc_pool_size) ->
+opt_type(sql_pool_size) ->
fun (I) when is_integer(I), I > 0 -> I end;
-opt_type(odbc_start_interval) ->
+opt_type(sql_start_interval) ->
fun (I) when is_integer(I), I > 0 -> I end;
-opt_type(odbc_type) ->
+opt_type(sql_type) ->
fun (mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
@@ -223,4 +223,4 @@ opt_type(odbc_type) ->
(odbc) -> odbc
end;
opt_type(_) ->
- [odbc_pool_size, odbc_start_interval, odbc_type].
+ [sql_pool_size, sql_start_interval, sql_type].
diff --git a/src/ejabberd_stun.erl b/src/ejabberd_stun.erl
index f7ae19cd..3c91117d 100644
--- a/src/ejabberd_stun.erl
+++ b/src/ejabberd_stun.erl
@@ -38,11 +38,11 @@
%%% API
%%%===================================================================
tcp_init(Socket, Opts) ->
- ejabberd:start_app(p1_stun),
+ ejabberd:start_app(stun),
stun:tcp_init(Socket, prepare_turn_opts(Opts)).
udp_init(Socket, Opts) ->
- ejabberd:start_app(p1_stun),
+ ejabberd:start_app(stun),
stun:udp_init(Socket, prepare_turn_opts(Opts)).
udp_recv(Socket, Addr, Port, Packet, Opts) ->
diff --git a/src/ejabberd_system_monitor.erl b/src/ejabberd_system_monitor.erl
index 9a0213c1..3f6c0566 100644
--- a/src/ejabberd_system_monitor.erl
+++ b/src/ejabberd_system_monitor.erl
@@ -71,7 +71,7 @@ process_command(From, To, Packet) ->
jid:tolower(jid:remove_resource(From)),
case lists:member(LFrom, get_admin_jids()) of
true ->
- Body = xml:get_path_s(Packet,
+ Body = fxml:get_path_s(Packet,
[{elem, <<"body">>}, cdata]),
spawn(fun () ->
process_flag(priority, high),
@@ -186,18 +186,24 @@ process_large_heap(Pid, Info) ->
"much memory:~n~p~n~s",
[node(), Pid, Info, DetailedInfo])),
From = jid:make(<<"">>, Host, <<"watchdog">>),
+ Hint = [#xmlel{name = <<"no-permanent-store">>,
+ attrs = [{<<"xmlns">>, ?NS_HINTS}]}],
lists:foreach(fun (JID) ->
- send_message(From, jid:make(JID), Body)
+ send_message(From, jid:make(JID), Body, Hint)
end, JIDs).
send_message(From, To, Body) ->
+ send_message(From, To, Body, []).
+
+send_message(From, To, Body, ExtraEls) ->
ejabberd_router:route(From, To,
#xmlel{name = <<"message">>,
attrs = [{<<"type">>, <<"chat">>}],
children =
[#xmlel{name = <<"body">>, attrs = [],
children =
- [{xmlcdata, Body}]}]}).
+ [{xmlcdata, Body}]}
+ | ExtraEls]}).
get_admin_jids() ->
ejabberd_config:get_option(
diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl
index d711f2fc..3281f643 100644
--- a/src/ejabberd_web_admin.erl
+++ b/src/ejabberd_web_admin.erl
@@ -264,7 +264,7 @@ get_auth_admin(Auth, HostHTTP, RPath, Method) ->
get_auth_account(HostOfRule, AccessRule, User, Server,
Pass) ->
- case ejabberd_auth:check_password(User, Server, Pass) of
+ case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
true ->
case is_acl_match(HostOfRule, AccessRule,
jid:make(User, Server, <<"">>))
@@ -383,6 +383,9 @@ css(Host) ->
" background: #f9f9f9;\n"
" font-family: sans-serif;\n"
"}\n"
+ "body {\n"
+ " min-width: 990px;\n"
+ "}\n"
"a {\n"
" text-decoration: none;\n"
" color: #3eaffa;\n"
@@ -461,13 +464,15 @@ css(Host) ->
" font-size: 0.75em;\n"
" text-align: center;\n"
"}\n"
+ "#navigation {\n"
+ " display: inline-block;\n"
+ " vertical-align: top;\n"
+ " width: 30%;\n"
+ "}\n"
"#navigation ul {\n"
- " position: absolute;\n"
- " top: 75px;\n"
- " left: 0;\n"
" padding: 0;\n"
" margin: 0;\n"
- " width: 17em;\n"
+ " width: 90%;\n"
" background: #fff;\n"
"}\n"
"#navigation ul li {\n"
@@ -512,6 +517,9 @@ css(Host) ->
" background: #3eaffa;\n"
" color: #fff;\n"
"}\n"
+ "thead tr td a {\n"
+ " color: #fff;\n"
+ "}\n"
"td.copy {\n"
" text-align: center;\n"
"}\n"
@@ -592,8 +600,10 @@ css(Host) ->
" list-style-type: none;\n"
"}\n"
"#content {\n"
- " padding-left: 19em;\n"
+ " display: inline-block;\n"
+ " vertical-align: top;\n"
" padding-top: 25px;\n"
+ " width: 70%;\n"
"}\n"
"div.guidelink,\n"
"p[dir=ltr] {\n"
@@ -735,7 +745,7 @@ process_admin(Host,
[{{acl, '$1', '$2'}}]}])),
{NumLines, ACLsP} = term_to_paragraph(ACLs, 80),
make_xhtml((?H1GL((?T(<<"Access Control Lists">>)),
- <<"acl-definition">>, <<"ACL Definition">>))
+ <<"acldefinition">>, <<"ACL Definition">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -771,7 +781,7 @@ process_admin(Host,
[{{acl, {'$1', Host}, '$2'}, [],
[{{acl, '$1', '$2'}}]}])),
make_xhtml((?H1GL((?T(<<"Access Control Lists">>)),
- <<"acl-definition">>, <<"ACL Definition">>))
+ <<"acldefinition">>, <<"ACL Definition">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -837,7 +847,7 @@ process_admin(Host,
[{{access, '$1', '$2'}}]}]),
{NumLines, AccessP} = term_to_paragraph(lists:keysort(2,Access), 80),
make_xhtml((?H1GL((?T(<<"Access Rules">>)),
- <<"access-rights">>, <<"Access Rights">>))
+ <<"accessrights">>, <<"Access Rights">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -870,7 +880,7 @@ process_admin(Host,
[{{access, {'$1', Host}, '$2'}, [],
[{{access, '$1', '$2'}}]}]),
make_xhtml((?H1GL((?T(<<"Access Rules">>)),
- <<"access-rights">>, <<"Access Rights">>))
+ <<"accessrights">>, <<"Access Rights">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -929,7 +939,7 @@ process_admin(global,
lang = Lang}) ->
Res = list_vhosts(Lang, AJID),
make_xhtml((?H1GL((?T(<<"Virtual Hosts">>)),
- <<"virtual-hosting">>, <<"Virtual Hosting">>))
+ <<"virtualhosting">>, <<"Virtual Hosting">>))
++ Res,
global, Lang, AJID);
process_admin(Host,
@@ -1510,8 +1520,7 @@ get_offlinemsg_length(ModOffline, User, Server) ->
case ModOffline of
none -> <<"disabled">>;
_ ->
- pretty_string_int(ModOffline:get_queue_length(User,
- Server))
+ pretty_string_int(ModOffline:count_offline_messages(User,Server))
end.
get_offlinemsg_module(Server) ->
@@ -2114,7 +2123,7 @@ get_node(global, Node, [<<"ports">>], Query, Lang) ->
[]])),
H1String = <<(?T(<<"Listened Ports at ">>))/binary,
(iolist_to_binary(atom_to_list(Node)))/binary>>,
- (?H1GL(H1String, <<"listening-ports">>, <<"Listening Ports">>))
+ (?H1GL(H1String, <<"listeningports">>, <<"Listening Ports">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -2142,7 +2151,7 @@ get_node(Host, Node, [<<"modules">>], Query, Lang)
NewModules = lists:sort(ejabberd_cluster:call(Node, gen_mod,
loaded_modules_with_opts, [Host])),
H1String = list_to_binary(io_lib:format(?T(<<"Modules at ~p">>), [Node])),
- (?H1GL(H1String, <<"modules-overview">>,
+ (?H1GL(H1String, <<"modulesoverview">>,
<<"Modules Overview">>))
++
case Res of
@@ -2405,7 +2414,7 @@ node_backup_parse_query(Node, Query) ->
lists:keysearch(<<Action/binary,
"host">>,
1, Query),
- ejabberd_cluster:call(Node, ejd2odbc,
+ ejabberd_cluster:call(Node, ejd2sql,
export, [Host, Path]);
<<"import_file">> ->
ejabberd_cluster:call(Node, ejabberd_admin,
@@ -2722,14 +2731,14 @@ pretty_print_xml(#xmlel{name = Name, attrs = Attrs,
[{Attr, Val} | RestAttrs] ->
AttrPrefix = [Prefix,
str:copies(<<" ">>, byte_size(Name) + 2)],
- [$\s, Attr, $=, $', xml:crypt(Val) | [$',
+ [$\s, Attr, $=, $', fxml:crypt(Val) | [$',
lists:map(fun ({Attr1,
Val1}) ->
[$\n,
AttrPrefix,
Attr1, $=,
$',
- xml:crypt(Val1),
+ fxml:crypt(Val1),
$']
end,
RestAttrs)]]
@@ -2741,7 +2750,7 @@ pretty_print_xml(#xmlel{name = Name, attrs = Attrs,
end,
Els),
if OnlyCData ->
- [$>, xml:get_cdata(Els), $<, $/, Name, $>, $\n];
+ [$>, fxml:get_cdata(Els), $<, $/, Name, $>, $\n];
true ->
[$>, $\n,
lists:map(fun (E) ->
diff --git a/src/ejabberd_websocket.erl b/src/ejabberd_websocket.erl
index d9a1bafc..0cdd9bac 100644
--- a/src/ejabberd_websocket.erl
+++ b/src/ejabberd_websocket.erl
@@ -373,10 +373,10 @@ process_frame(#frame_info{unprocessed =
process_frame(FrameInfo#frame_info{unprocessed = <<>>},
<<UnprocessedPre/binary, Data/binary>>).
-handle_data(tcp, FrameInfo, Data, Socket, WsHandleLoopPid, p1_tls) ->
- case p1_tls:recv_data(Socket, Data) of
+handle_data(tcp, FrameInfo, Data, Socket, WsHandleLoopPid, fast_tls) ->
+ case fast_tls:recv_data(Socket, Data) of
{ok, NewData} ->
- handle_data_int(FrameInfo, NewData, Socket, WsHandleLoopPid, p1_tls);
+ handle_data_int(FrameInfo, NewData, Socket, WsHandleLoopPid, fast_tls);
{error, Error} ->
{error, Error}
end;
diff --git a/src/ejabberd_xmlrpc.erl b/src/ejabberd_xmlrpc.erl
index 6b25adc4..c7e72d66 100644
--- a/src/ejabberd_xmlrpc.erl
+++ b/src/ejabberd_xmlrpc.erl
@@ -228,13 +228,13 @@ process(_, #request{method = 'POST', data = Data, opts = Opts}) ->
end,
GetAuth = true,
State = #state{access_commands = AccessCommands, get_auth = GetAuth},
- case xml_stream:parse_element(Data) of
+ case fxml_stream:parse_element(Data) of
{error, _} ->
{400, [],
#xmlel{name = <<"h1">>, attrs = [],
children = [{xmlcdata, <<"Malformed XML">>}]}};
El ->
- case p1_xmlrpc:decode(El) of
+ case fxmlrpc:decode(El) of
{error, _} = Err ->
?ERROR_MSG("XML-RPC request ~s failed with reason: ~p",
[Data, Err]),
@@ -244,7 +244,7 @@ process(_, #request{method = 'POST', data = Data, opts = Opts}) ->
{ok, RPC} ->
?DEBUG("got XML-RPC request: ~p", [RPC]),
{false, Result} = handler(State, RPC),
- XML = xml:element_to_binary(p1_xmlrpc:encode(Result)),
+ XML = fxml:element_to_binary(fxmlrpc:encode(Result)),
{200, [{<<"Content-Type">>, <<"text/xml">>}],
<<"<?xml version=\"1.0\"?>", XML/binary>>}
end
@@ -491,7 +491,7 @@ format_result(Atom, {Name, atom}) ->
[{Name, iolist_to_binary(atom_to_list(Atom))}]};
format_result(Int, {Name, integer}) ->
{struct, [{Name, Int}]};
-format_result(String, {Name, string}) when is_list(String) ->
+format_result([A|_]=String, {Name, string}) when is_list(String) and is_integer(A) ->
{struct, [{Name, lists:flatten(String)}]};
format_result(Binary, {Name, string}) when is_binary(Binary) ->
{struct, [{Name, binary_to_list(Binary)}]};
diff --git a/src/ejd2odbc.erl b/src/ejd2sql.erl
index 4cb937ef..aa74286e 100644
--- a/src/ejd2odbc.erl
+++ b/src/ejd2sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : ejd2odbc.erl
+%%% File : ejd2sql.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Export some mnesia tables to SQL DB
%%% Created : 22 Aug 2005 by Alexey Shchepin <alexey@process-one.net>
@@ -23,14 +23,14 @@
%%%
%%%----------------------------------------------------------------------
--module(ejd2odbc).
+-module(ejd2sql).
-author('alexey@process-one.net').
-include("logger.hrl").
-export([export/2, export/3, import_file/2, import/2,
- import/3]).
+ import/3, delete/1]).
-define(MAX_RECORDS_PER_TRANSACTION, 100).
@@ -43,7 +43,7 @@
%%% A table can be converted from Mnesia to an ODBC database by calling
%%% one of the API function with the following parameters:
%%% - Server is the server domain you want to convert
-%%% - Output can be either odbc to export to the configured relational
+%%% - Output can be either sql to export to the configured relational
%%% database or "Filename" to export to text file.
modules() ->
@@ -80,6 +80,20 @@ export(Server, Output, Module) ->
end, Module:export(Server)),
close_output(Output, IO).
+delete(Server) ->
+ Modules = modules(),
+ lists:foreach(
+ fun(Module) ->
+ delete(Server, Module)
+ end, Modules).
+
+delete(Server, Module) ->
+ LServer = jid:nameprep(iolist_to_binary(Server)),
+ lists:foreach(
+ fun({Table, ConvertFun}) ->
+ delete(LServer, Table, ConvertFun)
+ end, Module:export(Server)).
+
import_file(Server, FileName) when is_binary(FileName) ->
import(Server, binary_to_list(FileName));
import_file(Server, FileName) ->
@@ -154,17 +168,36 @@ export(LServer, Table, IO, ConvertFun) ->
output(_LServer, _Table, _IO, []) ->
ok;
-output(LServer, _Table, odbc, SQLs) ->
- ejabberd_odbc:sql_transaction(LServer, SQLs);
+output(LServer, _Table, sql, SQLs) ->
+ ejabberd_sql:sql_transaction(LServer, SQLs);
output(_LServer, Table, Fd, SQLs) ->
file:write(Fd, ["-- \n-- Mnesia table: ", atom_to_list(Table),
"\n--\n", SQLs]).
+delete(LServer, Table, ConvertFun) ->
+ F = fun () ->
+ mnesia:write_lock_table(Table),
+ {_N, SQLs} =
+ mnesia:foldl(
+ fun(R, {N, SQLs} = Acc) ->
+ case ConvertFun(LServer, R) of
+ [] ->
+ Acc;
+ _SQL ->
+ mnesia:delete_object(R),
+ Acc
+ end
+ end,
+ {0, []}, Table),
+ delete(LServer, Table, SQLs)
+ end,
+ mnesia:transaction(F).
+
import(LServer, SelectQuery, IO, ConvertFun, Opts) ->
F = case proplists:get_bool(fast, Opts) of
true ->
fun() ->
- case ejabberd_odbc:sql_query_t(SelectQuery) of
+ case ejabberd_sql:sql_query_t(SelectQuery) of
{selected, _, Rows} ->
lists:foldl(fun process_sql_row/2,
{IO, ConvertFun, undefined}, Rows);
@@ -174,16 +207,16 @@ import(LServer, SelectQuery, IO, ConvertFun, Opts) ->
end;
false ->
fun() ->
- ejabberd_odbc:sql_query_t(
+ ejabberd_sql:sql_query_t(
[iolist_to_binary(
[<<"declare c cursor for ">>, SelectQuery])]),
fetch(IO, ConvertFun, undefined)
end
end,
- ejabberd_odbc:sql_transaction(LServer, F).
+ ejabberd_sql:sql_transaction(LServer, F).
fetch(IO, ConvertFun, PrevRow) ->
- case ejabberd_odbc:sql_query_t([<<"fetch c;">>]) of
+ case ejabberd_sql:sql_query_t([<<"fetch c;">>]) of
{selected, _, [Row]} ->
process_sql_row(Row, {IO, ConvertFun, PrevRow}),
fetch(IO, ConvertFun, Row);
diff --git a/src/elixir_logger_backend.erl b/src/elixir_logger_backend.erl
new file mode 100644
index 00000000..c055853f
--- /dev/null
+++ b/src/elixir_logger_backend.erl
@@ -0,0 +1,122 @@
+%%%-------------------------------------------------------------------
+%%% @author Mickael Remond <mremond@process-one.net>
+%%% @doc
+%%% This module bridges lager logs to Elixir Logger.
+%%% @end
+%%% Created : 9 March 2016 by Mickael Remond <mremond@process-one.net>
+%%%
+%%% ejabberd, Copyright (C) 2002-2016 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(elixir_logger_backend).
+
+-behaviour(gen_event).
+
+-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-record(state, {level = debug}).
+
+init(Opts) ->
+ Level = proplists:get_value(level, Opts, debug),
+ State = #state{level = Level},
+ {ok, State}.
+
+%% @private
+handle_event({log, LagerMsg}, State) ->
+ #{mode := Mode, truncate := Truncate, level := MinLevel, utc_log := UTCLog} = 'Elixir.Logger.Config':'__data__'(),
+ MsgLevel = severity_to_level(lager_msg:severity(LagerMsg)),
+ case {lager_util:is_loggable(LagerMsg, lager_util:level_to_num(State#state.level), ?MODULE),
+ 'Elixir.Logger':compare_levels(MsgLevel, MinLevel)} of
+ {_, lt}->
+ {ok, State};
+ {true, _} ->
+ Metadata = normalize_pid(lager_msg:metadata(LagerMsg)),
+ Message = 'Elixir.Logger.Utils':truncate(lager_msg:message(LagerMsg), Truncate),
+ Timestamp = timestamp(lager_msg:timestamp(LagerMsg), UTCLog),
+ GroupLeader = case proplists:get_value(pid, Metadata, self()) of
+ Pid when is_pid(Pid) ->
+ erlang:process_info(self(), group_leader);
+ _ -> {group_leader, self()}
+ end,
+ notify(Mode, {MsgLevel, GroupLeader, {'Elixir.Logger', Message, Timestamp, Metadata}}),
+ {ok, State};
+ _ ->
+ {ok, State}
+ end;
+handle_event(_Msg, State) ->
+ {ok, State}.
+
+%% @private
+%% TODO Handle loglevels
+handle_call(get_loglevel, State) ->
+ {ok, lager_util:config_to_mask(State#state.level), State};
+handle_call({set_loglevel, Config}, State) ->
+ {ok, ok, State#state{level = Config}}.
+
+%% @private
+handle_info(_Msg, State) ->
+ {ok, State}.
+
+%% @private
+terminate(_Reason, _State) ->
+ ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+notify(sync, Msg) ->
+ gen_event:sync_notify('Elixir.Logger', Msg);
+notify(async, Msg) ->
+ gen_event:notify('Elixir.Logger', Msg).
+
+normalize_pid(Metadata) ->
+ case proplists:get_value(pid, Metadata) of
+ Pid when is_pid(Pid) -> Metadata;
+ Pid when is_list(Pid) ->
+ M1 = proplists:delete(pid, Metadata),
+ case catch erlang:list_to_pid(Pid) of
+ {'EXIT', _} ->
+ M1;
+ PidAsPid ->
+ [{pid, PidAsPid}|M1]
+ end;
+ _ ->
+ proplists:delete(pid, Metadata)
+ end.
+
+%% Return timestamp with milliseconds
+timestamp(Time, UTCLog) ->
+ {_, _, Micro} = p1_time_compat:timestamp(),
+ {Date, {Hours, Minutes, Seconds}} =
+ case UTCLog of
+ true -> calendar:now_to_universal_time(Time);
+ false -> calendar:now_to_local_time(Time)
+ end,
+ {Date, {Hours, Minutes, Seconds, Micro div 1000}}.
+
+
+severity_to_level(debug) -> debug;
+severity_to_level(info) -> info;
+severity_to_level(notice) -> info;
+severity_to_level(warning) -> warn;
+severity_to_level(error) -> error;
+severity_to_level(critical) -> error;
+severity_to_level(alert) -> error;
+severity_to_level(emergency) -> error.
diff --git a/src/ext_mod.erl b/src/ext_mod.erl
index 46ece873..91526e71 100644
--- a/src/ext_mod.erl
+++ b/src/ext_mod.erl
@@ -509,12 +509,11 @@ compile(_Module, _Spec, DestDir) ->
filelib:ensure_dir(filename:join(Ebin, ".")),
EjabBin = filename:dirname(code:which(ejabberd)),
EjabInc = filename:join(filename:dirname(EjabBin), "include"),
- XmlHrl = filename:join(EjabInc, "xml.hrl"),
- Logger = [{d, 'P1LOGGER'} || code:is_loaded(lager)==false],
+ XmlHrl = filename:join(EjabInc, "fxml.hrl"),
ExtLib = [{d, 'NO_EXT_LIB'} || filelib:is_file(XmlHrl)],
Options = [{outdir, Ebin}, {i, "include"}, {i, EjabInc},
verbose, report_errors, report_warnings]
- ++ Logger ++ ExtLib,
+ ++ ExtLib,
[file:copy(App, Ebin) || App <- filelib:wildcard("src/*.app")],
Result = [case compile:file(File, Options) of
{ok, _} -> ok;
@@ -589,10 +588,10 @@ rebar_dep({App, _, {git, Url, Ref}}) ->
%% -- YAML spec parser
consult(File) ->
- case p1_yaml:decode_from_file(File, [plain_as_atom]) of
+ case fast_yaml:decode_from_file(File, [plain_as_atom]) of
{ok, []} -> {ok, []};
{ok, [Doc|_]} -> {ok, [format(Spec) || Spec <- Doc]};
- {error, Err} -> {error, p1_yaml:format_error(Err)}
+ {error, Err} -> {error, fast_yaml:format_error(Err)}
end.
format({Key, Val}) when is_binary(Val) ->
diff --git a/src/gen_mod.erl b/src/gen_mod.erl
index c45642d4..26e662dc 100644
--- a/src/gen_mod.erl
+++ b/src/gen_mod.erl
@@ -35,7 +35,8 @@
get_module_opt/4, get_module_opt/5, get_module_opt_host/3,
loaded_modules/1, loaded_modules_with_opts/1,
get_hosts/2, get_module_proc/2, is_loaded/2,
- start_modules/1, default_db/1, v_db/1, opt_type/1]).
+ start_modules/0, start_modules/1, stop_modules/0, stop_modules/1,
+ default_db/1, v_db/1, opt_type/1, db_mod/2, db_mod/3]).
%%-export([behaviour_info/1]).
@@ -47,7 +48,7 @@
opts = [] :: opts() | '_' | '$2'}).
-type opts() :: [{atom(), any()}].
--type db_type() :: odbc | mnesia | riak.
+-type db_type() :: sql | mnesia | riak.
-callback start(binary(), opts()) -> any().
-callback stop(binary()) -> any().
@@ -64,23 +65,38 @@ start() ->
{keypos, #ejabberd_module.module_host}]),
ok.
+-spec start_modules() -> any().
+
+%% Start all the modules in all the hosts
+start_modules() ->
+ lists:foreach(
+ fun(Host) ->
+ start_modules(Host)
+ end, ?MYHOSTS).
+
+get_modules_options(Host) ->
+ ejabberd_config:get_option(
+ {modules, Host},
+ fun(Mods) ->
+ lists:map(
+ fun({M, A}) when is_atom(M), is_list(A) ->
+ {M, A}
+ end, Mods)
+ end, []).
+
-spec start_modules(binary()) -> any().
start_modules(Host) ->
- Modules = ejabberd_config:get_option(
- {modules, Host},
- fun(L) when is_list(L) -> L end, []),
+ Modules = get_modules_options(Host),
lists:foreach(
- fun({Module, Opts}) ->
- start_module(Host, Module, Opts)
- end, Modules).
+ fun({Module, Opts}) ->
+ start_module(Host, Module, Opts)
+ end, Modules).
-spec start_module(binary(), atom()) -> any().
start_module(Host, Module) ->
- Modules = ejabberd_config:get_option(
- {modules, Host},
- fun(L) when is_list(L) -> L end, []),
+ Modules = get_modules_options(Host),
case lists:keyfind(Module, 1, Modules) of
{_, Opts} ->
start_module(Host, Module, Opts);
@@ -121,6 +137,23 @@ is_app_running(AppName) ->
lists:keymember(AppName, 1,
application:which_applications(Timeout)).
+-spec stop_modules() -> any().
+
+stop_modules() ->
+ lists:foreach(
+ fun(Host) ->
+ stop_modules(Host)
+ end, ?MYHOSTS).
+
+-spec stop_modules(binary()) -> any().
+
+stop_modules(Host) ->
+ Modules = get_modules_options(Host),
+ lists:foreach(
+ fun({Module, _Args}) ->
+ gen_mod:stop_module_keep_config(Host, Module)
+ end, Modules).
+
-spec stop_module(binary(), atom()) -> error | {aborted, any()} | {atomic, any()}.
stop_module(Host, Module) ->
@@ -264,7 +297,8 @@ validate_opts(Module, Opts) ->
-spec v_db(db_type() | internal) -> db_type().
-v_db(odbc) -> odbc;
+v_db(odbc) -> sql;
+v_db(sql) -> sql;
v_db(internal) -> mnesia;
v_db(mnesia) -> mnesia;
v_db(riak) -> riak.
@@ -286,6 +320,20 @@ db_type(Host, Opts) when is_list(Opts) ->
default_db(Host) ->
ejabberd_config:get_option({default_db, Host}, fun v_db/1, mnesia).
+-spec db_mod(binary() | global | db_type(), module()) -> module().
+
+db_mod(odbc, Module) -> list_to_atom(atom_to_list(Module) ++ "_sql");
+db_mod(sql, Module) -> list_to_atom(atom_to_list(Module) ++ "_sql");
+db_mod(mnesia, Module) -> list_to_atom(atom_to_list(Module) ++ "_mnesia");
+db_mod(riak, Module) -> list_to_atom(atom_to_list(Module) ++ "_riak");
+db_mod(Host, Module) when is_binary(Host) orelse Host == global ->
+ db_mod(db_type(Host, Module), Module).
+
+-spec db_mod(binary() | global, opts(), module()) -> module().
+
+db_mod(Host, Opts, Module) when is_list(Opts) ->
+ db_mod(db_type(Host, Opts), Module).
+
-spec loaded_modules(binary()) -> [atom()].
loaded_modules(Host) ->
diff --git a/src/jd2ejd.erl b/src/jd2ejd.erl
index cda8d11c..099387c9 100644
--- a/src/jd2ejd.erl
+++ b/src/jd2ejd.erl
@@ -48,7 +48,7 @@ import_file(File) ->
true ->
case file:read_file(File) of
{ok, Text} ->
- case xml_stream:parse_element(Text) of
+ case fxml_stream:parse_element(Text) of
El when is_record(El, xmlel) ->
case catch process_xdb(User, Server, El) of
{'EXIT', Reason} ->
@@ -113,16 +113,16 @@ process_xdb(User, Server,
xdb_data(_User, _Server, {xmlcdata, _CData}) -> ok;
xdb_data(User, Server, #xmlel{attrs = Attrs} = El) ->
From = jid:make(User, Server, <<"">>),
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_AUTH ->
- Password = xml:get_tag_cdata(El),
+ Password = fxml:get_tag_cdata(El),
ejabberd_auth:set_password(User, Server, Password),
ok;
?NS_ROSTER ->
catch mod_roster:set_items(User, Server, El), ok;
?NS_LAST ->
- TimeStamp = xml:get_attr_s(<<"last">>, Attrs),
- Status = xml:get_tag_cdata(El),
+ TimeStamp = fxml:get_attr_s(<<"last">>, Attrs),
+ Status = fxml:get_tag_cdata(El),
catch mod_last:store_last_info(User, Server,
jlib:binary_to_integer(TimeStamp),
Status),
@@ -136,7 +136,7 @@ xdb_data(User, Server, #xmlel{attrs = Attrs} = El) ->
<<"jabber:x:offline">> ->
process_offline(Server, From, El), ok;
XMLNS ->
- case xml:get_attr_s(<<"j_private_flag">>, Attrs) of
+ case fxml:get_attr_s(<<"j_private_flag">>, Attrs) of
<<"1">> ->
catch mod_private:process_sm_iq(From,
jid:make(<<"">>, Server,
@@ -160,7 +160,7 @@ xdb_data(User, Server, #xmlel{attrs = Attrs} = El) ->
process_offline(Server, To, #xmlel{children = Els}) ->
LServer = jid:nameprep(Server),
lists:foreach(fun (#xmlel{attrs = Attrs} = El) ->
- FromS = xml:get_attr_s(<<"from">>, Attrs),
+ FromS = fxml:get_attr_s(<<"from">>, Attrs),
From = case FromS of
<<"">> ->
jid:make(<<"">>, Server, <<"">>);
diff --git a/src/jid.erl b/src/jid.erl
index cc387ecb..7bdd652a 100644
--- a/src/jid.erl
+++ b/src/jid.erl
@@ -87,9 +87,13 @@ split(#jid{user = U, server = S, resource = R}) ->
split(_) ->
error.
--spec from_string(binary()) -> jid() | error.
-
-from_string(S) ->
+-spec from_string([binary()|string()]) -> jid() | error.
+from_string(S) when is_list(S) ->
+ %% We do not accept list because we want to enforce good practice of
+ %% using binaries for string. However, we do not let it crash to avoid
+ %% losing associated ets table.
+ {error, need_jid_as_binary};
+from_string(S) when is_binary(S) ->
SplitPattern = ets:lookup_element(jlib, string_to_jid_pattern, 2),
Size = size(S),
End = Size-1,
diff --git a/src/jlib.erl b/src/jlib.erl
index 61aaf690..8eaebbc8 100644
--- a/src/jlib.erl
+++ b/src/jlib.erl
@@ -92,8 +92,8 @@ make_result_iq_reply(#xmlel{name = Name, attrs = Attrs,
-spec make_result_iq_reply_attrs([attr()]) -> [attr()].
make_result_iq_reply_attrs(Attrs) ->
- To = xml:get_attr(<<"to">>, Attrs),
- From = xml:get_attr(<<"from">>, Attrs),
+ To = fxml:get_attr(<<"to">>, Attrs),
+ From = fxml:get_attr(<<"from">>, Attrs),
Attrs1 = lists:keydelete(<<"to">>, 1, Attrs),
Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1),
Attrs3 = case To of
@@ -133,8 +133,8 @@ make_error_reply(#xmlel{name = Name, attrs = Attrs,
-spec make_error_reply_attrs([attr()]) -> [attr()].
make_error_reply_attrs(Attrs) ->
- To = xml:get_attr(<<"to">>, Attrs),
- From = xml:get_attr(<<"from">>, Attrs),
+ To = fxml:get_attr(<<"to">>, Attrs),
+ From = fxml:get_attr(<<"from">>, Attrs),
Attrs1 = lists:keydelete(<<"to">>, 1, Attrs),
Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1),
Attrs3 = case To of
@@ -159,7 +159,7 @@ make_error_element(Code, Desc) ->
make_correct_from_to_attrs(From, To, Attrs) ->
Attrs1 = lists:keydelete(<<"from">>, 1, Attrs),
- Attrs2 = case xml:get_attr(<<"to">>, Attrs) of
+ Attrs2 = case fxml:get_attr(<<"to">>, Attrs) of
{value, _} -> Attrs1;
_ -> [{<<"to">>, To} | Attrs1]
end,
@@ -299,8 +299,8 @@ jid_replace_resource(JID, Resource) ->
-spec get_iq_namespace(xmlel()) -> binary().
get_iq_namespace(#xmlel{name = <<"iq">>, children = Els}) ->
- case xml:remove_cdata(Els) of
- [#xmlel{attrs = Attrs}] -> xml:get_attr_s(<<"xmlns">>, Attrs);
+ case fxml:remove_cdata(Els) of
+ [#xmlel{attrs = Attrs}] -> fxml:get_attr_s(<<"xmlns">>, Attrs);
_ -> <<"">>
end;
get_iq_namespace(_) -> <<"">>.
@@ -326,9 +326,9 @@ iq_query_or_response_info(El) ->
iq_info_internal(El, any).
iq_info_internal(#xmlel{name = <<"iq">>, attrs = Attrs, children = Els}, Filter) ->
- ID = xml:get_attr_s(<<"id">>, Attrs),
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
- {Type, Class} = case xml:get_attr_s(<<"type">>, Attrs) of
+ ID = fxml:get_attr_s(<<"id">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+ {Type, Class} = case fxml:get_attr_s(<<"type">>, Attrs) of
<<"set">> -> {set, request};
<<"get">> -> {get, request};
<<"result">> -> {result, reply};
@@ -336,15 +336,15 @@ iq_info_internal(#xmlel{name = <<"iq">>, attrs = Attrs, children = Els}, Filter)
_ -> {invalid, invalid}
end,
if Type == invalid -> invalid; Class == request; Filter == any ->
- FilteredEls = xml:remove_cdata(Els),
+ FilteredEls = fxml:remove_cdata(Els),
{XMLNS, SubEl} = case {Class, FilteredEls} of
{request, [#xmlel{attrs = Attrs2}]} ->
- {xml:get_attr_s(<<"xmlns">>, Attrs2), hd(FilteredEls)};
+ {fxml:get_attr_s(<<"xmlns">>, Attrs2), hd(FilteredEls)};
{reply, _} ->
NonErrorEls = [El || #xmlel{name = SubName} = El <- FilteredEls,
SubName /= <<"error">>],
{case NonErrorEls of
- [NonErrorEl] -> xml:get_tag_attr_s(<<"xmlns">>, NonErrorEl);
+ [NonErrorEl] -> fxml:get_tag_attr_s(<<"xmlns">>, NonErrorEl);
_ -> <<"">>
end,
FilteredEls};
@@ -399,7 +399,7 @@ iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) ->
).
parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"submit">> ->
lists:reverse(parse_xdata_fields(Els, []));
<<"form">> -> %% This is a workaround to accept Psi's wrong forms
@@ -418,7 +418,7 @@ parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
parse_xdata_fields([], Res) -> Res;
parse_xdata_fields([#xmlel{name = <<"field">>, attrs = Attrs, children = SubEls}
| Els], Res) ->
- case xml:get_attr_s(<<"var">>, Attrs) of
+ case fxml:get_attr_s(<<"var">>, Attrs) of
<<>> ->
parse_xdata_fields(Els, Res);
Var ->
@@ -437,7 +437,7 @@ parse_xdata_fields([_ | Els], Res) ->
parse_xdata_values([], Res) -> Res;
parse_xdata_values([#xmlel{name = <<"value">>, children = SubEls} | Els], Res) ->
- Val = xml:get_cdata(SubEls),
+ Val = fxml:get_cdata(SubEls),
parse_xdata_values(Els, [Val | Res]);
parse_xdata_values([_ | Els], Res) ->
parse_xdata_values(Els, Res).
@@ -446,7 +446,7 @@ parse_xdata_values([_ | Els], Res) ->
rsm_decode(#iq{sub_el = SubEl}) -> rsm_decode(SubEl);
rsm_decode(#xmlel{} = SubEl) ->
- case xml:get_subtag(SubEl, <<"set">>) of
+ case fxml:get_subtag(SubEl, <<"set">>) of
false -> none;
#xmlel{name = <<"set">>, children = SubEls} ->
lists:foldl(fun rsm_parse_element/2, #rsm_in{}, SubEls)
@@ -455,26 +455,26 @@ rsm_decode(#xmlel{} = SubEl) ->
rsm_parse_element(#xmlel{name = <<"max">>, attrs = []} =
Elem,
RsmIn) ->
- CountStr = xml:get_tag_cdata(Elem),
+ CountStr = fxml:get_tag_cdata(Elem),
{Count, _} = str:to_integer(CountStr),
RsmIn#rsm_in{max = Count};
rsm_parse_element(#xmlel{name = <<"before">>,
attrs = []} =
Elem,
RsmIn) ->
- UID = xml:get_tag_cdata(Elem),
+ UID = fxml:get_tag_cdata(Elem),
RsmIn#rsm_in{direction = before, id = UID};
rsm_parse_element(#xmlel{name = <<"after">>,
attrs = []} =
Elem,
RsmIn) ->
- UID = xml:get_tag_cdata(Elem),
+ UID = fxml:get_tag_cdata(Elem),
RsmIn#rsm_in{direction = aft, id = UID};
rsm_parse_element(#xmlel{name = <<"index">>,
attrs = []} =
Elem,
RsmIn) ->
- IndexStr = xml:get_tag_cdata(Elem),
+ IndexStr = fxml:get_tag_cdata(Elem),
{Index, _} = str:to_integer(IndexStr),
RsmIn#rsm_in{index = Index};
rsm_parse_element(_, RsmIn) -> RsmIn.
@@ -535,7 +535,7 @@ is_standalone_chat_state(#xmlel{name = <<"message">>} = El) ->
<<"paused">>],
Stripped =
lists:foldl(fun(ChatState, AccEl) ->
- xml:remove_subtags(AccEl, ChatState,
+ fxml:remove_subtags(AccEl, ChatState,
{<<"xmlns">>, ?NS_CHATSTATES})
end, El, ChatStates),
case Stripped of
@@ -558,15 +558,15 @@ add_delay_info(El, From, Time) ->
binary()) -> xmlel().
add_delay_info(El, From, Time, Desc) ->
- case xml:get_subtag_with_xmlns(El, <<"delay">>, ?NS_DELAY) of
+ case fxml:get_subtag_with_xmlns(El, <<"delay">>, ?NS_DELAY) of
false ->
%% Add new tag
DelayTag = create_delay_tag(Time, From, Desc),
- xml:append_subtags(El, [DelayTag]);
+ fxml:append_subtags(El, [DelayTag]);
DelayTag ->
%% Update existing tag
NewDelayTag =
- case {xml:get_tag_cdata(DelayTag), Desc} of
+ case {fxml:get_tag_cdata(DelayTag), Desc} of
{<<"">>, <<"">>} ->
DelayTag;
{OldDesc, <<"">>} ->
@@ -582,8 +582,8 @@ add_delay_info(El, From, Time, Desc) ->
DelayTag#xmlel{children = [{xmlcdata, OldDesc}]}
end
end,
- NewEl = xml:remove_subtags(El, <<"delay">>, {<<"xmlns">>, ?NS_DELAY}),
- xml:append_subtags(NewEl, [NewDelayTag])
+ NewEl = fxml:remove_subtags(El, <<"delay">>, {<<"xmlns">>, ?NS_DELAY}),
+ fxml:append_subtags(NewEl, [NewDelayTag])
end.
-spec create_delay_tag(erlang:timestamp(), jid() | ljid() | binary(), binary())
diff --git a/src/mod_adhoc.erl b/src/mod_adhoc.erl
index 9947c84a..9e0682f7 100644
--- a/src/mod_adhoc.erl
+++ b/src/mod_adhoc.erl
@@ -233,7 +233,7 @@ process_sm_iq(From, To, IQ) ->
process_adhoc_request(From, To, IQ, adhoc_sm_commands).
process_adhoc_request(From, To,
- #iq{sub_el = SubEl} = IQ, Hook) ->
+ #iq{sub_el = SubEl, lang = Lang} = IQ, Hook) ->
?DEBUG("About to parse ~p...", [IQ]),
case adhoc:parse_request(IQ) of
{error, Error} ->
@@ -245,8 +245,9 @@ process_adhoc_request(From, To,
of
ignore -> ignore;
empty ->
+ Txt = <<"No hook has processed this command">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
+ sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]};
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]};
Command -> IQ#iq{type = result, sub_el = [Command]}
@@ -277,7 +278,9 @@ ping_command(_Acc, _From, _To,
[{<<"info">>,
translate:translate(Lang,
<<"Pong">>)}]});
- true -> {error, ?ERR_BAD_REQUEST}
+ true ->
+ Txt = <<"Incorrect value of 'action' attribute">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end;
ping_command(Acc, _From, _To, _Request) -> Acc.
diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl
index dae775e4..b12a8000 100644
--- a/src/mod_admin_extra.erl
+++ b/src/mod_admin_extra.erl
@@ -31,7 +31,7 @@
-include("logger.hrl").
-export([start/2, stop/1, compile/1, get_cookie/0,
- remove_node/1, set_password/3,
+ remove_node/1, set_password/3, check_password/3,
check_password_hash/4, delete_old_users/1,
delete_old_users_vhost/2, ban_account/3,
num_active_users/2, num_resources/2, resource_num/3,
@@ -101,89 +101,156 @@ get_commands_spec() ->
desc = "Recompile and reload Erlang source code file",
module = ?MODULE, function = compile,
args = [{file, string}],
- result = {res, rescode}},
+ args_example = ["/home/me/srcs/ejabberd/mod_example.erl"],
+ args_desc = ["Filename of erlang source file to compile"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = get_cookie, tags = [erlang],
desc = "Get the Erlang cookie of this node",
module = ?MODULE, function = get_cookie,
args = [],
- result = {cookie, string}},
+ result = {cookie, string},
+ result_example = "MWTAVMODFELNLSMYXPPD",
+ result_desc = "Erlang cookie used for authentication by ejabberd"},
#ejabberd_commands{name = remove_node, tags = [erlang],
desc = "Remove an ejabberd node from Mnesia clustering config",
module = ?MODULE, function = remove_node,
args = [{node, string}],
- result = {res, rescode}},
-
+ args_example = ["ejabberd@server2"],
+ args_desc = ["Name of erlang node to remove"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = num_active_users, tags = [accounts, stats],
desc = "Get number of users active in the last days",
policy = admin,
module = ?MODULE, function = num_active_users,
args = [{host, binary}, {days, integer}],
- result = {users, integer}},
+ args_example = [<<"myserver.com">>, 3],
+ args_desc = ["Name of host to check", "Number of days to calculate sum"],
+ result = {users, integer},
+ result_example = 123,
+ result_desc = "Number of users active on given server in last n days"},
#ejabberd_commands{name = delete_old_users, tags = [accounts, purge],
desc = "Delete users that didn't log in last days, or that never logged",
module = ?MODULE, function = delete_old_users,
args = [{days, integer}],
- result = {res, restuple}},
+ args_example = [30],
+ args_desc = ["Last login age in days of accounts that should be removed"],
+ result = {res, restuple},
+ result_example = {ok, <<"Deleted 2 users: [\"oldman@myserver.com\", \"test@myserver.com\"]">>},
+ result_desc = "Result tuple"},
#ejabberd_commands{name = delete_old_users_vhost, tags = [accounts, purge],
desc = "Delete users that didn't log in last days in vhost, or that never logged",
module = ?MODULE, function = delete_old_users_vhost,
args = [{host, binary}, {days, integer}],
- result = {res, restuple}},
-
+ args_example = [<<"myserver.com">>, 30],
+ args_desc = ["Server name",
+ "Last login age in days of accounts that should be removed"],
+ result = {res, restuple},
+ result_example = {ok, <<"Deleted 2 users: [\"oldman@myserver.com\", \"test@myserver.com\"]">>},
+ result_desc = "Result tuple"},
#ejabberd_commands{name = check_account, tags = [accounts],
desc = "Check if an account exists or not",
module = ejabberd_auth, function = is_user_exists,
args = [{user, binary}, {host, binary}],
- result = {res, rescode}},
+ args_example = [<<"peter">>, <<"myserver.com">>],
+ args_desc = ["User name to check", "Server to check"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = check_password, tags = [accounts],
desc = "Check if a password is correct",
- module = ejabberd_auth, function = check_password,
+ module = ?MODULE, function = check_password,
args = [{user, binary}, {host, binary}, {password, binary}],
- result = {res, rescode}},
+ args_example = [<<"peter">>, <<"myserver.com">>, <<"secret">>],
+ args_desc = ["User name to check", "Server to check", "Password to check"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = check_password_hash, tags = [accounts],
desc = "Check if the password hash is correct",
longdesc = "Allowed hash methods: md5, sha.",
module = ?MODULE, function = check_password_hash,
- args = [{user, binary}, {host, binary}, {passwordhash, string}, {hashmethod, string}],
- result = {res, rescode}},
+ args = [{user, binary}, {host, binary}, {passwordhash, string},
+ {hashmethod, string}],
+ args_example = [<<"peter">>, <<"myserver.com">>,
+ <<"5ebe2294ecd0e0f08eab7690d2a6ee69">>, <<"md5">>],
+ args_desc = ["User name to check", "Server to check",
+ "Password's hash value", "Name of hash method"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = change_password, tags = [accounts],
desc = "Change the password of an account",
module = ?MODULE, function = set_password,
args = [{user, binary}, {host, binary}, {newpass, binary}],
- result = {res, rescode}},
+ args_example = [<<"peter">>, <<"myserver.com">>, <<"blank">>],
+ args_desc = ["User name", "Server name",
+ "New password for user"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = ban_account, tags = [accounts],
desc = "Ban an account: kick sessions and set random password",
module = ?MODULE, function = ban_account,
args = [{user, binary}, {host, binary}, {reason, binary}],
- result = {res, rescode}},
-
+ args_example = [<<"attacker">>, <<"myserver.com">>, <<"Spaming other users">>],
+ args_desc = ["User name to ban", "Server name",
+ "Reason for banning user"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = num_resources, tags = [session],
desc = "Get the number of resources of a user",
module = ?MODULE, function = num_resources,
args = [{user, binary}, {host, binary}],
- result = {resources, integer}},
+ args_example = [<<"peter">>, <<"myserver.com">>],
+ args_desc = ["User name", "Server name"],
+ result = {resources, integer},
+ result_example = 5,
+ result_desc = "Number of active resources for a user"},
#ejabberd_commands{name = resource_num, tags = [session],
desc = "Resource string of a session number",
module = ?MODULE, function = resource_num,
args = [{user, binary}, {host, binary}, {num, integer}],
- result = {resource, string}},
+ args_example = [<<"peter">>, <<"myserver.com">>, 2],
+ args_desc = ["User name", "Server name", "ID of resource to return"],
+ result = {resource, string},
+ result_example = <<"Psi">>,
+ result_desc = "Name of user resource"},
#ejabberd_commands{name = kick_session, tags = [session],
desc = "Kick a user session",
module = ?MODULE, function = kick_session,
args = [{user, binary}, {host, binary}, {resource, binary}, {reason, binary}],
- result = {res, rescode}},
+ args_example = [<<"peter">>, <<"myserver.com">>, <<"Psi">>,
+ <<"Stuck connection">>],
+ args_desc = ["User name", "Server name", "User's resource",
+ "Reason for closing session"],
+ result = {res, rescode},
+ result_example = ok,
+ result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = status_num_host, tags = [session, stats],
desc = "Number of logged users with this status in host",
policy = admin,
module = ?MODULE, function = status_num,
args = [{host, binary}, {status, binary}],
- result = {users, integer}},
+ args_example = [<<"myserver.com">>, <<"dnd">>],
+ args_desc = ["Server name", "Status type to check"],
+ result = {users, integer},
+ result_example = 23,
+ result_desc = "Number of connected sessions with given status type"},
#ejabberd_commands{name = status_num, tags = [session, stats],
desc = "Number of logged users with this status",
policy = admin,
module = ?MODULE, function = status_num,
args = [{status, binary}],
- result = {users, integer}},
+ args_example = [<<"dnd">>],
+ args_desc = ["Status type to check"],
+ result = {users, integer},
+ result_example = 23,
+ result_desc = "Number of connected sessions with given status type"},
#ejabberd_commands{name = status_list_host, tags = [session],
desc = "List of users logged in host with their statuses",
module = ?MODULE, function = status_list,
@@ -464,7 +531,7 @@ get_commands_spec() ->
tags = [offline],
desc = "Get the number of unread offline messages",
policy = user,
- module = mod_offline, function = get_queue_length,
+ module = mod_offline, function = count_offline_messages,
args = [],
result = {res, integer}},
#ejabberd_commands{name = send_message, tags = [stanza],
@@ -523,36 +590,38 @@ remove_node(Node) ->
%%%
set_password(User, Host, Password) ->
- case ejabberd_auth:set_password(User, Host, Password) of
- ok ->
- ok;
- _ ->
- error
- end.
+ Fun = fun () -> ejabberd_auth:set_password(User, Host, Password) end,
+ user_action(User, Host, Fun, ok).
+
+check_password(User, Host, Password) ->
+ ejabberd_auth:check_password(User, <<>>, Host, Password).
%% Copied some code from ejabberd_commands.erl
check_password_hash(User, Host, PasswordHash, HashMethod) ->
AccountPass = ejabberd_auth:get_password_s(User, Host),
AccountPassHash = case {AccountPass, HashMethod} of
{A, _} when is_tuple(A) -> scrammed;
- {_, "md5"} -> get_md5(AccountPass);
- {_, "sha"} -> get_sha(AccountPass);
- _ -> undefined
+ {_, <<"md5">>} -> get_md5(AccountPass);
+ {_, <<"sha">>} -> get_sha(AccountPass);
+ {_, Method} ->
+ ?ERROR_MSG("check_password_hash called "
+ "with hash method: ~p", [Method]),
+ undefined
end,
case AccountPassHash of
scrammed ->
- ?ERROR_MSG("Passwords are scrammed, and check_password_hash can not work.", []),
+ ?ERROR_MSG("Passwords are scrammed, and check_password_hash cannot work.", []),
throw(passwords_scrammed_command_cannot_work);
- undefined -> error;
+ undefined -> throw(unkown_hash_method);
PasswordHash -> ok;
- _ -> error
+ _ -> false
end.
get_md5(AccountPass) ->
- lists:flatten([io_lib:format("~.16B", [X])
- || X <- binary_to_list(erlang:md5(AccountPass))]).
+ iolist_to_binary([io_lib:format("~2.16.0B", [X])
+ || X <- binary_to_list(erlang:md5(AccountPass))]).
get_sha(AccountPass) ->
- lists:flatten([io_lib:format("~.16B", [X])
- || X <- binary_to_list(p1_sha:sha1(AccountPass))]).
+ iolist_to_binary([io_lib:format("~2.16.0B", [X])
+ || X <- binary_to_list(p1_sha:sha1(AccountPass))]).
num_active_users(Host, Days) ->
list_last_activity(Host, true, Days).
@@ -681,21 +750,7 @@ kick_sessions(User, Server, Reason) ->
fun(Resource) ->
kick_this_session(User, Server, Resource, Reason)
end,
- get_resources(User, Server)).
-
-get_resources(User, Server) ->
- lists:map(
- fun(Session) ->
- element(3, Session#session.usr)
- end,
- get_sessions(User, Server)).
-
-get_sessions(User, Server) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
- Sessions = mnesia:dirty_index_read(session, {LUser, LServer}, #session.us),
- true = is_list(Sessions),
- Sessions.
+ ejabberd_sm:get_user_resources(User, Server)).
set_random_password(User, Server, Reason) ->
NewPass = build_random_password(Reason),
@@ -729,7 +784,8 @@ resource_num(User, Host, Num) ->
true ->
lists:nth(Num, Resources);
false ->
- lists:flatten(io_lib:format("Error: Wrong resource number: ~p", [Num]))
+ throw({bad_argument,
+ lists:flatten(io_lib:format("Wrong resource number: ~p", [Num]))})
end.
kick_session(User, Server, Resource, ReasonText) ->
@@ -794,7 +850,8 @@ connected_users_info() ->
PI when is_integer(PI) -> PI;
_ -> nil
end,
- {[U, $@, S, $/, R], atom_to_list(Conn), IPS, Port, PriorityI, NodeS, Uptime}
+ {binary_to_list(<<U/binary, $@, S/binary, $/, R/binary>>),
+ atom_to_list(Conn), IPS, Port, PriorityI, NodeS, Uptime}
end,
USRIs).
@@ -912,7 +969,7 @@ get_vcard_content(User, Server, Data) ->
[_|_] ->
case get_vcard(Data, A1) of
[false] -> throw(error_no_value_found_in_vcard);
- ElemList -> ?DEBUG("ELS ~p", [ElemList]), [xml:get_tag_cdata(Elem) || Elem <- ElemList]
+ ElemList -> ?DEBUG("ELS ~p", [ElemList]), [fxml:get_tag_cdata(Elem) || Elem <- ElemList]
end;
[] ->
throw(error_no_vcard_found)
@@ -933,7 +990,7 @@ get_vcard([Data], A1) ->
get_subtag(A1, Data).
get_subtag(Xmlelement, Name) ->
- [xml:get_subtag(Xmlelement, Name)].
+ [fxml:get_subtag(Xmlelement, Name)].
set_vcard_content(User, Server, Data, SomeContent) ->
ContentList = case SomeContent of
@@ -963,7 +1020,7 @@ set_vcard_content(User, Server, Data, SomeContent) ->
take_vcard_tel(TelType, [{xmlel, <<"TEL">>, _, SubEls}=OldEl | OldEls], NewEls, Taken) ->
{Taken2, NewEls2} = case lists:keymember(TelType, 2, SubEls) of
- true -> {xml:get_subtag(OldEl, <<"NUMBER">>), NewEls};
+ true -> {fxml:get_subtag(OldEl, <<"NUMBER">>), NewEls};
false -> {Taken, [OldEl | NewEls]}
end,
take_vcard_tel(TelType, OldEls, NewEls2, Taken2);
@@ -1126,7 +1183,7 @@ push_roster_item(LU, LS, R, U, S, Action) ->
ejabberd_sm:route(LJID, LJID, BroadcastEl),
Item = build_roster_item(U, S, Action),
ResIQ = build_iq_roster_push(Item),
- ejabberd_router:route(LJID, LJID, ResIQ).
+ ejabberd_router:route(jid:remove_resource(LJID), LJID, ResIQ).
build_roster_item(U, S, {add, Nick, Subs, Group}) ->
{xmlel, <<"item">>,
@@ -1206,10 +1263,10 @@ private_get(Username, Host, Element, Ns) ->
[{xmlel, <<"query">>,
[{<<"xmlns">>, ?NS_PRIVATE}],
[SubEl]}] = ResIq#iq.sub_el,
- binary_to_list(xml:element_to_binary(SubEl)).
+ binary_to_list(fxml:element_to_binary(SubEl)).
private_set(Username, Host, ElementString) ->
- case xml_stream:parse_element(ElementString) of
+ case fxml_stream:parse_element(ElementString) of
{error, Error} ->
io:format("Error found parsing the element:~n ~p~nError: ~p~n",
[ElementString, Error]),
@@ -1255,8 +1312,7 @@ srg_get_info(Group, Host) ->
Os when is_list(Os) -> Os;
error -> []
end,
- [{jlib:atom_to_binary(Title),
- io_lib:format("~p", [btl(Value)])} || {Title, Value} <- Opts].
+ [{jlib:atom_to_binary(Title), btl(Value)} || {Title, Value} <- Opts].
btl([]) -> [];
btl([B|L]) -> [btl(B)|btl(L)];
@@ -1333,7 +1389,7 @@ build_packet(Type, Subject, Body) ->
}.
send_stanza(FromString, ToString, Stanza) ->
- case xml_stream:parse_element(Stanza) of
+ case fxml_stream:parse_element(Stanza) of
{error, Error} ->
{error, Error};
XmlEl ->
@@ -1344,7 +1400,7 @@ send_stanza(FromString, ToString, Stanza) ->
end.
send_stanza_c2s(Username, Host, Resource, Stanza) ->
- case {xml_stream:parse_element(Stanza),
+ case {fxml_stream:parse_element(Stanza),
ejabberd_sm:get_session_pid(Username, Host, Resource)}
of
{{error, Error}, _} ->
@@ -1358,7 +1414,7 @@ send_stanza_c2s(Username, Host, Resource, Stanza) ->
privacy_set(Username, Host, QueryS) ->
From = jid:make(Username, Host, <<"">>),
To = jid:make(<<"">>, Host, <<"">>),
- QueryEl = xml_stream:parse_element(QueryS),
+ QueryEl = fxml_stream:parse_element(QueryS),
StanzaEl = {xmlel, <<"iq">>, [{<<"type">>, <<"set">>}], [QueryEl]},
IQ = jlib:iq_query_info(StanzaEl),
ejabberd_hooks:run_fold(
@@ -1515,6 +1571,20 @@ decide_rip_jid({UName, UServer}, Match_list) ->
end,
Match_list).
+user_action(User, Server, Fun, OK) ->
+ case ejabberd_auth:is_user_exists(User, Server) of
+ true ->
+ case catch Fun() of
+ OK -> ok;
+ {error, Error} -> throw(Error);
+ Error ->
+ ?ERROR_MSG("Command returned: ~p", [Error]),
+ 1
+ end;
+ false ->
+ throw({not_found, "unknown_user"})
+ end.
+
%% Copied from ejabberd-2.0.0/src/acl.erl
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
diff --git a/src/mod_announce.erl b/src/mod_announce.erl
index 3d0924a6..d7251c50 100644
--- a/src/mod_announce.erl
+++ b/src/mod_announce.erl
@@ -41,11 +41,16 @@
-include("logger.hrl").
-include("jlib.hrl").
-include("adhoc.hrl").
+-include("mod_announce.hrl").
--record(motd, {server = <<"">> :: binary(),
- packet = #xmlel{} :: xmlel()}).
--record(motd_users, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
- dummy = [] :: [] | '_'}).
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #motd{} | #motd_users{}) -> ok | pass.
+-callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> {atomic, any()}.
+-callback set_motd(binary(), xmlel()) -> {atomic, any()}.
+-callback delete_motd(binary()) -> {atomic, any()}.
+-callback get_motd(binary()) -> {ok, xmlel()} | error.
+-callback is_motd_user(binary(), binary()) -> boolean().
+-callback set_motd_user(binary(), binary()) -> {atomic, any()}.
-define(PROCNAME, ejabberd_announce).
@@ -55,20 +60,8 @@
tokenize(Node) -> str:tokens(Node, <<"/#">>).
start(Host, Opts) ->
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(motd,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, motd)}]),
- mnesia:create_table(motd_users,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, motd_users)}]),
- update_tables();
- _ ->
- ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
ejabberd_hooks:add(local_send_to_resource_hook, Host,
?MODULE, announce, 50),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50),
@@ -211,15 +204,15 @@ disco_identity(Acc, _From, _To, Node, Lang) ->
%%-------------------------------------------------------------------------
--define(INFO_RESULT(Allow, Feats),
+-define(INFO_RESULT(Allow, Feats, Lang),
case Allow of
deny ->
- {error, ?ERR_FORBIDDEN};
+ {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow ->
{result, Feats}
end).
-disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, _Lang) ->
+disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@@ -229,13 +222,14 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, _Lang)
case {acl:match_rule(LServer, Access1, From),
acl:match_rule(global, Access2, From)} of
{deny, deny} ->
- {error, ?ERR_FORBIDDEN};
+ Txt = <<"Denied by ACL">>,
+ {error, ?ERRT_FORBIDDEN(Lang, Txt)};
_ ->
{result, []}
end
end;
-disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
+disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@@ -246,25 +240,25 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
AllowGlobal = acl:match_rule(global, AccessGlobal, From),
case Node of
?NS_ADMIN_ANNOUNCE ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMIN_ANNOUNCE_ALL ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMIN_SET_MOTD ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMIN_EDIT_MOTD ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMIN_DELETE_MOTD ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMIN_ANNOUNCE_ALLHOSTS ->
- ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
+ ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS ->
- ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
+ ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
?NS_ADMIN_SET_MOTD_ALLHOSTS ->
- ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
+ ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
?NS_ADMIN_EDIT_MOTD_ALLHOSTS ->
- ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
+ ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
?NS_ADMIN_DELETE_MOTD_ALLHOSTS ->
- ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
+ ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
_ ->
Acc
end
@@ -283,10 +277,10 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
}
)).
--define(ITEMS_RESULT(Allow, Items),
+-define(ITEMS_RESULT(Allow, Items, Lang),
case Allow of
deny ->
- {error, ?ERR_FORBIDDEN};
+ {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow ->
{result, Items}
end).
@@ -320,7 +314,7 @@ disco_items(Acc, From, #jid{lserver = LServer} = To, <<"announce">>, Lang) ->
announce_items(Acc, From, To, Lang)
end;
-disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
+disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@@ -331,25 +325,25 @@ disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
AllowGlobal = acl:match_rule(global, AccessGlobal, From),
case Node of
?NS_ADMIN_ANNOUNCE ->
- ?ITEMS_RESULT(Allow, []);
+ ?ITEMS_RESULT(Allow, [], Lang);
?NS_ADMIN_ANNOUNCE_ALL ->
- ?ITEMS_RESULT(Allow, []);
+ ?ITEMS_RESULT(Allow, [], Lang);
?NS_ADMIN_SET_MOTD ->
- ?ITEMS_RESULT(Allow, []);
+ ?ITEMS_RESULT(Allow, [], Lang);
?NS_ADMIN_EDIT_MOTD ->
- ?ITEMS_RESULT(Allow, []);
+ ?ITEMS_RESULT(Allow, [], Lang);
?NS_ADMIN_DELETE_MOTD ->
- ?ITEMS_RESULT(Allow, []);
+ ?ITEMS_RESULT(Allow, [], Lang);
?NS_ADMIN_ANNOUNCE_ALLHOSTS ->
- ?ITEMS_RESULT(AllowGlobal, []);
+ ?ITEMS_RESULT(AllowGlobal, [], Lang);
?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS ->
- ?ITEMS_RESULT(AllowGlobal, []);
+ ?ITEMS_RESULT(AllowGlobal, [], Lang);
?NS_ADMIN_SET_MOTD_ALLHOSTS ->
- ?ITEMS_RESULT(AllowGlobal, []);
+ ?ITEMS_RESULT(AllowGlobal, [], Lang);
?NS_ADMIN_EDIT_MOTD_ALLHOSTS ->
- ?ITEMS_RESULT(AllowGlobal, []);
+ ?ITEMS_RESULT(AllowGlobal, [], Lang);
?NS_ADMIN_DELETE_MOTD_ALLHOSTS ->
- ?ITEMS_RESULT(AllowGlobal, []);
+ ?ITEMS_RESULT(AllowGlobal, [], Lang);
_ ->
Acc
end
@@ -396,7 +390,8 @@ announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang)
commands_result(Allow, From, To, Request) ->
case Allow of
deny ->
- {error, ?ERR_FORBIDDEN};
+ Lang = Request#adhoc_request.lang,
+ {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow ->
announce_commands(From, To, Request)
end.
@@ -463,12 +458,13 @@ announce_commands(From, To,
%% User returns form.
case jlib:parse_xdata_submit(XData) of
invalid ->
- {error, ?ERR_BAD_REQUEST};
+ {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)};
Fields ->
handle_adhoc_form(From, To, Request, Fields)
end;
true ->
- {error, ?ERR_BAD_REQUEST}
+ Txt = <<"Incorrect action or data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end.
-define(VVALUE(Val),
@@ -688,7 +684,9 @@ announce_all(From, To, Packet) ->
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"Denied by ACL">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
Local = jid:make(<<>>, To#jid.server, <<>>),
@@ -703,7 +701,9 @@ announce_all_hosts_all(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"Denied by ACL">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
Local = jid:make(<<>>, To#jid.server, <<>>),
@@ -719,7 +719,9 @@ announce_online(From, To, Packet) ->
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"Denied by ACL">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
announce_online1(ejabberd_sm:get_vh_session_list(Host),
@@ -731,7 +733,9 @@ announce_all_hosts_online(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"Denied by ACL">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
announce_online1(ejabberd_sm:dirty_get_sessions_list(),
@@ -752,7 +756,9 @@ announce_motd(From, To, Packet) ->
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"Denied by ACL">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd(Host, Packet)
@@ -762,7 +768,9 @@ announce_all_hosts_motd(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"Denied by ACL">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
Hosts = ?MYHOSTS,
@@ -774,48 +782,17 @@ announce_motd(Host, Packet) ->
announce_motd_update(LServer, Packet),
Sessions = ejabberd_sm:get_vh_session_list(LServer),
announce_online1(Sessions, LServer, Packet),
- case gen_mod:db_type(LServer, ?MODULE) of
- mnesia ->
- F = fun() ->
- lists:foreach(
- fun({U, S, _R}) ->
- mnesia:write(#motd_users{us = {U, S}})
- end, Sessions)
- end,
- mnesia:transaction(F);
- riak ->
- try
- lists:foreach(
- fun({U, S, _R}) ->
- ok = ejabberd_riak:put(#motd_users{us = {U, S}},
- motd_users_schema(),
- [{'2i', [{<<"server">>, S}]}])
- end, Sessions),
- {atomic, ok}
- catch _:{badmatch, Err} ->
- {atomic, Err}
- end;
- odbc ->
- F = fun() ->
- lists:foreach(
- fun({U, _S, _R}) ->
- Username = ejabberd_odbc:escape(U),
- odbc_queries:update_t(
- <<"motd">>,
- [<<"username">>, <<"xml">>],
- [Username, <<"">>],
- [<<"username='">>, Username, <<"'">>])
- end, Sessions)
- end,
- ejabberd_odbc:sql_transaction(LServer, F)
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:set_motd_users(LServer, Sessions).
announce_motd_update(From, To, Packet) ->
Host = To#jid.lserver,
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"Denied by ACL">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd_update(Host, Packet)
@@ -825,7 +802,9 @@ announce_all_hosts_motd_update(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"Denied by ACL">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
Hosts = ?MYHOSTS,
@@ -834,34 +813,17 @@ announce_all_hosts_motd_update(From, To, Packet) ->
announce_motd_update(LServer, Packet) ->
announce_motd_delete(LServer),
- case gen_mod:db_type(LServer, ?MODULE) of
- mnesia ->
- F = fun() ->
- mnesia:write(#motd{server = LServer, packet = Packet})
- end,
- mnesia:transaction(F);
- riak ->
- {atomic, ejabberd_riak:put(#motd{server = LServer,
- packet = Packet},
- motd_schema())};
- odbc ->
- XML = ejabberd_odbc:escape(xml:element_to_binary(Packet)),
- F = fun() ->
- odbc_queries:update_t(
- <<"motd">>,
- [<<"username">>, <<"xml">>],
- [<<"">>, XML],
- [<<"username=''">>])
- end,
- ejabberd_odbc:sql_transaction(LServer, F)
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:set_motd(LServer, Packet).
announce_motd_delete(From, To, Packet) ->
Host = To#jid.lserver,
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"Denied by ACL">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd_delete(Host)
@@ -871,7 +833,9 @@ announce_all_hosts_motd_delete(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
- Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"Denied by ACL">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
Hosts = ?MYHOSTS,
@@ -879,147 +843,37 @@ announce_all_hosts_motd_delete(From, To, Packet) ->
end.
announce_motd_delete(LServer) ->
- case gen_mod:db_type(LServer, ?MODULE) of
- mnesia ->
- F = fun() ->
- mnesia:delete({motd, LServer}),
- mnesia:write_lock_table(motd_users),
- Users = mnesia:select(
- motd_users,
- [{#motd_users{us = '$1', _ = '_'},
- [{'==', {element, 2, '$1'}, LServer}],
- ['$1']}]),
- lists:foreach(fun(US) ->
- mnesia:delete({motd_users, US})
- end, Users)
- end,
- mnesia:transaction(F);
- riak ->
- try
- ok = ejabberd_riak:delete(motd, LServer),
- ok = ejabberd_riak:delete_by_index(motd_users,
- <<"server">>,
- LServer),
- {atomic, ok}
- catch _:{badmatch, Err} ->
- {atomic, Err}
- end;
- odbc ->
- F = fun() ->
- ejabberd_odbc:sql_query_t([<<"delete from motd;">>])
- end,
- ejabberd_odbc:sql_transaction(LServer, F)
- end.
-
-send_motd(JID) ->
- send_motd(JID, gen_mod:db_type(JID#jid.lserver, ?MODULE)).
-
-send_motd(#jid{luser = LUser, lserver = LServer} = JID, mnesia) ->
- case catch mnesia:dirty_read({motd, LServer}) of
- [#motd{packet = Packet}] ->
- US = {LUser, LServer},
- case catch mnesia:dirty_read({motd_users, US}) of
- [#motd_users{}] ->
- ok;
- _ ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:delete_motd(LServer).
+
+send_motd(#jid{luser = LUser, lserver = LServer} = JID) when LUser /= <<>> ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:get_motd(LServer) of
+ {ok, Packet} ->
+ case Mod:is_motd_user(LUser, LServer) of
+ false ->
Local = jid:make(<<>>, LServer, <<>>),
ejabberd_router:route(Local, JID, Packet),
- F = fun() ->
- mnesia:write(#motd_users{us = US})
- end,
- mnesia:transaction(F)
+ Mod:set_motd_user(LUser, LServer);
+ true ->
+ ok
end;
- _ ->
+ error ->
ok
end;
-send_motd(#jid{luser = LUser, lserver = LServer} = JID, riak) ->
- case catch ejabberd_riak:get(motd, motd_schema(), LServer) of
- {ok, #motd{packet = Packet}} ->
- US = {LUser, LServer},
- case ejabberd_riak:get(motd_users, motd_users_schema(), US) of
- {ok, #motd_users{}} ->
- ok;
- _ ->
- Local = jid:make(<<>>, LServer, <<>>),
- ejabberd_router:route(Local, JID, Packet),
- {atomic, ejabberd_riak:put(
- #motd_users{us = US}, motd_users_schema(),
- [{'2i', [{<<"server">>, LServer}]}])}
- end;
- _ ->
- ok
- end;
-send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= <<>> ->
- case catch ejabberd_odbc:sql_query(
- LServer, [<<"select xml from motd where username='';">>]) of
- {selected, [<<"xml">>], [[XML]]} ->
- case xml_stream:parse_element(XML) of
- {error, _} ->
- ok;
- Packet ->
- Username = ejabberd_odbc:escape(LUser),
- case catch ejabberd_odbc:sql_query(
- LServer,
- [<<"select username from motd "
- "where username='">>, Username, <<"';">>]) of
- {selected, [<<"username">>], []} ->
- Local = jid:make(<<"">>, LServer, <<"">>),
- ejabberd_router:route(Local, JID, Packet),
- F = fun() ->
- odbc_queries:update_t(
- <<"motd">>,
- [<<"username">>, <<"xml">>],
- [Username, <<"">>],
- [<<"username='">>, Username, <<"'">>])
- end,
- ejabberd_odbc:sql_transaction(LServer, F);
- _ ->
- ok
- end
- end;
- _ ->
- ok
- end;
-send_motd(_, odbc) ->
+send_motd(_) ->
ok.
get_stored_motd(LServer) ->
- case get_stored_motd_packet(LServer, gen_mod:db_type(LServer, ?MODULE)) of
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:get_motd(LServer) of
{ok, Packet} ->
- {xml:get_subtag_cdata(Packet, <<"subject">>),
- xml:get_subtag_cdata(Packet, <<"body">>)};
+ {fxml:get_subtag_cdata(Packet, <<"subject">>),
+ fxml:get_subtag_cdata(Packet, <<"body">>)};
error ->
{<<>>, <<>>}
end.
-get_stored_motd_packet(LServer, mnesia) ->
- case catch mnesia:dirty_read({motd, LServer}) of
- [#motd{packet = Packet}] ->
- {ok, Packet};
- _ ->
- error
- end;
-get_stored_motd_packet(LServer, riak) ->
- case ejabberd_riak:get(motd, motd_schema(), LServer) of
- {ok, #motd{packet = Packet}} ->
- {ok, Packet};
- _ ->
- error
- end;
-get_stored_motd_packet(LServer, odbc) ->
- case catch ejabberd_odbc:sql_query(
- LServer, [<<"select xml from motd where username='';">>]) of
- {selected, [<<"xml">>], [[XML]]} ->
- case xml_stream:parse_element(XML) of
- {error, _} ->
- error;
- Packet ->
- {ok, Packet}
- end;
- _ ->
- error
- end.
-
%% This function is similar to others, but doesn't perform any ACL verification
send_announcement_to_all(Host, SubjectS, BodyS) ->
SubjectEls = if SubjectS /= <<>> ->
@@ -1053,96 +907,17 @@ get_access(Host) ->
none).
%%-------------------------------------------------------------------------
-
-update_tables() ->
- update_motd_table(),
- update_motd_users_table().
-
-update_motd_table() ->
- Fields = record_info(fields, motd),
- case mnesia:table_info(motd, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- motd, Fields, set,
- fun(#motd{server = S}) -> S end,
- fun(#motd{server = S, packet = P} = R) ->
- NewS = iolist_to_binary(S),
- NewP = xml:to_xmlel(P),
- R#motd{server = NewS, packet = NewP}
- end);
- _ ->
- ?INFO_MSG("Recreating motd table", []),
- mnesia:transform_table(motd, ignore, Fields)
- end.
-
-
-update_motd_users_table() ->
- Fields = record_info(fields, motd_users),
- case mnesia:table_info(motd_users, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- motd_users, Fields, set,
- fun(#motd_users{us = {U, _}}) -> U end,
- fun(#motd_users{us = {U, S}} = R) ->
- NewUS = {iolist_to_binary(U),
- iolist_to_binary(S)},
- R#motd_users{us = NewUS}
- end);
- _ ->
- ?INFO_MSG("Recreating motd_users table", []),
- mnesia:transform_table(motd_users, ignore, Fields)
- end.
-
-motd_schema() ->
- {record_info(fields, motd), #motd{}}.
-
-motd_users_schema() ->
- {record_info(fields, motd_users), #motd_users{}}.
-
-export(_Server) ->
- [{motd,
- fun(Host, #motd{server = LServer, packet = El})
- when LServer == Host ->
- [[<<"delete from motd where username='';">>],
- [<<"insert into motd(username, xml) values ('', '">>,
- ejabberd_odbc:escape(xml:element_to_binary(El)),
- <<"');">>]];
- (_Host, _R) ->
- []
- end},
- {motd_users,
- fun(Host, #motd_users{us = {LUser, LServer}})
- when LServer == Host, LUser /= <<"">> ->
- Username = ejabberd_odbc:escape(LUser),
- [[<<"delete from motd where username='">>, Username, <<"';">>],
- [<<"insert into motd(username, xml) values ('">>,
- Username, <<"', '');">>]];
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import(LServer) ->
- [{<<"select xml from motd where username='';">>,
- fun([XML]) ->
- El = xml_stream:parse_element(XML),
- #motd{server = LServer, packet = El}
- end},
- {<<"select username from motd where xml='';">>,
- fun([LUser]) ->
- #motd_users{us = {LUser, LServer}}
- end}].
-
-import(_LServer, mnesia, #motd{} = Motd) ->
- mnesia:dirty_write(Motd);
-import(_LServer, mnesia, #motd_users{} = Users) ->
- mnesia:dirty_write(Users);
-import(_LServer, riak, #motd{} = Motd) ->
- ejabberd_riak:put(Motd, motd_schema());
-import(_LServer, riak, #motd_users{us = {_, S}} = Users) ->
- ejabberd_riak:put(Users, motd_users_schema(),
- [{'2i', [{<<"server">>, S}]}]);
-import(_, _, _) ->
- pass.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
+
+import(LServer, DBType, LA) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, LA).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
diff --git a/src/mod_announce_mnesia.erl b/src/mod_announce_mnesia.erl
new file mode 100644
index 00000000..c43eb853
--- /dev/null
+++ b/src/mod_announce_mnesia.erl
@@ -0,0 +1,129 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_announce_mnesia).
+-behaviour(mod_announce).
+
+%% API
+-export([init/2, set_motd_users/2, set_motd/2, delete_motd/1,
+ get_motd/1, is_motd_user/2, set_motd_user/2, import/2]).
+
+-include("jlib.hrl").
+-include("mod_announce.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(motd,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, motd)}]),
+ mnesia:create_table(motd_users,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, motd_users)}]),
+ update_tables().
+
+set_motd_users(_LServer, USRs) ->
+ F = fun() ->
+ lists:foreach(
+ fun({U, S, _R}) ->
+ mnesia:write(#motd_users{us = {U, S}})
+ end, USRs)
+ end,
+ mnesia:transaction(F).
+
+set_motd(LServer, Packet) ->
+ F = fun() ->
+ mnesia:write(#motd{server = LServer, packet = Packet})
+ end,
+ mnesia:transaction(F).
+
+delete_motd(LServer) ->
+ F = fun() ->
+ mnesia:delete({motd, LServer}),
+ mnesia:write_lock_table(motd_users),
+ Users = mnesia:select(
+ motd_users,
+ [{#motd_users{us = '$1', _ = '_'},
+ [{'==', {element, 2, '$1'}, LServer}],
+ ['$1']}]),
+ lists:foreach(fun(US) ->
+ mnesia:delete({motd_users, US})
+ end, Users)
+ end,
+ mnesia:transaction(F).
+
+get_motd(LServer) ->
+ case mnesia:dirty_read({motd, LServer}) of
+ [#motd{packet = Packet}] ->
+ {ok, Packet};
+ _ ->
+ error
+ end.
+
+is_motd_user(LUser, LServer) ->
+ case mnesia:dirty_read({motd_users, {LUser, LServer}}) of
+ [#motd_users{}] -> true;
+ _ -> false
+ end.
+
+set_motd_user(LUser, LServer) ->
+ F = fun() ->
+ mnesia:write(#motd_users{us = {LUser, LServer}})
+ end,
+ mnesia:transaction(F).
+
+import(_LServer, #motd{} = Motd) ->
+ mnesia:dirty_write(Motd);
+import(_LServer, #motd_users{} = Users) ->
+ mnesia:dirty_write(Users).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_tables() ->
+ update_motd_table(),
+ update_motd_users_table().
+
+update_motd_table() ->
+ Fields = record_info(fields, motd),
+ case mnesia:table_info(motd, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ motd, Fields, set,
+ fun(#motd{server = S}) -> S end,
+ fun(#motd{server = S, packet = P} = R) ->
+ NewS = iolist_to_binary(S),
+ NewP = fxml:to_xmlel(P),
+ R#motd{server = NewS, packet = NewP}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating motd table", []),
+ mnesia:transform_table(motd, ignore, Fields)
+ end.
+
+
+update_motd_users_table() ->
+ Fields = record_info(fields, motd_users),
+ case mnesia:table_info(motd_users, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ motd_users, Fields, set,
+ fun(#motd_users{us = {U, _}}) -> U end,
+ fun(#motd_users{us = {U, S}} = R) ->
+ NewUS = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ R#motd_users{us = NewUS}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating motd_users table", []),
+ mnesia:transform_table(motd_users, ignore, Fields)
+ end.
diff --git a/src/mod_announce_riak.erl b/src/mod_announce_riak.erl
new file mode 100644
index 00000000..7ced0b3c
--- /dev/null
+++ b/src/mod_announce_riak.erl
@@ -0,0 +1,87 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_announce_riak).
+-behaviour(mod_announce).
+
+%% API
+-export([init/2, set_motd_users/2, set_motd/2, delete_motd/1,
+ get_motd/1, is_motd_user/2, set_motd_user/2, import/2]).
+
+-include("jlib.hrl").
+-include("mod_announce.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+set_motd_users(_LServer, USRs) ->
+ try
+ lists:foreach(
+ fun({U, S, _R}) ->
+ ok = ejabberd_riak:put(#motd_users{us = {U, S}},
+ motd_users_schema(),
+ [{'2i', [{<<"server">>, S}]}])
+ end, USRs),
+ {atomic, ok}
+ catch _:{badmatch, Err} ->
+ {atomic, Err}
+ end.
+
+set_motd(LServer, Packet) ->
+ {atomic, ejabberd_riak:put(#motd{server = LServer,
+ packet = Packet},
+ motd_schema())}.
+
+delete_motd(LServer) ->
+ try
+ ok = ejabberd_riak:delete(motd, LServer),
+ ok = ejabberd_riak:delete_by_index(motd_users,
+ <<"server">>,
+ LServer),
+ {atomic, ok}
+ catch _:{badmatch, Err} ->
+ {atomic, Err}
+ end.
+
+get_motd(LServer) ->
+ case ejabberd_riak:get(motd, motd_schema(), LServer) of
+ {ok, #motd{packet = Packet}} ->
+ {ok, Packet};
+ _ ->
+ error
+ end.
+
+is_motd_user(LUser, LServer) ->
+ case ejabberd_riak:get(motd_users, motd_users_schema(),
+ {LUser, LServer}) of
+ {ok, #motd_users{}} -> true;
+ _ -> false
+ end.
+
+set_motd_user(LUser, LServer) ->
+ {atomic, ejabberd_riak:put(
+ #motd_users{us = {LUser, LServer}}, motd_users_schema(),
+ [{'2i', [{<<"server">>, LServer}]}])}.
+
+import(_LServer, #motd{} = Motd) ->
+ ejabberd_riak:put(Motd, motd_schema());
+import(_LServer, #motd_users{us = {_, S}} = Users) ->
+ ejabberd_riak:put(Users, motd_users_schema(),
+ [{'2i', [{<<"server">>, S}]}]).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+motd_schema() ->
+ {record_info(fields, motd), #motd{}}.
+
+motd_users_schema() ->
+ {record_info(fields, motd_users), #motd_users{}}.
diff --git a/src/mod_announce_sql.erl b/src/mod_announce_sql.erl
new file mode 100644
index 00000000..692c8fae
--- /dev/null
+++ b/src/mod_announce_sql.erl
@@ -0,0 +1,132 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_announce_sql).
+-behaviour(mod_announce).
+
+%% API
+-export([init/2, set_motd_users/2, set_motd/2, delete_motd/1,
+ get_motd/1, is_motd_user/2, set_motd_user/2, import/1,
+ import/2, export/1]).
+
+-include("jlib.hrl").
+-include("mod_announce.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+set_motd_users(LServer, USRs) ->
+ F = fun() ->
+ lists:foreach(
+ fun({U, _S, _R}) ->
+ Username = ejabberd_sql:escape(U),
+ sql_queries:update_t(
+ <<"motd">>,
+ [<<"username">>, <<"xml">>],
+ [Username, <<"">>],
+ [<<"username='">>, Username, <<"'">>])
+ end, USRs)
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+set_motd(LServer, Packet) ->
+ XML = ejabberd_sql:escape(fxml:element_to_binary(Packet)),
+ F = fun() ->
+ sql_queries:update_t(
+ <<"motd">>,
+ [<<"username">>, <<"xml">>],
+ [<<"">>, XML],
+ [<<"username=''">>])
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+delete_motd(LServer) ->
+ F = fun() ->
+ ejabberd_sql:sql_query_t([<<"delete from motd;">>])
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+get_motd(LServer) ->
+ case catch ejabberd_sql:sql_query(
+ LServer, [<<"select xml from motd where username='';">>]) of
+ {selected, [<<"xml">>], [[XML]]} ->
+ case fxml_stream:parse_element(XML) of
+ {error, _} ->
+ error;
+ Packet ->
+ {ok, Packet}
+ end;
+ _ ->
+ error
+ end.
+
+is_motd_user(LUser, LServer) ->
+ Username = ejabberd_sql:escape(LUser),
+ case catch ejabberd_sql:sql_query(
+ LServer,
+ [<<"select username from motd "
+ "where username='">>, Username, <<"';">>]) of
+ {selected, [<<"username">>], [_|_]} ->
+ true;
+ _ ->
+ false
+ end.
+
+set_motd_user(LUser, LServer) ->
+ Username = ejabberd_sql:escape(LUser),
+ F = fun() ->
+ sql_queries:update_t(
+ <<"motd">>,
+ [<<"username">>, <<"xml">>],
+ [Username, <<"">>],
+ [<<"username='">>, Username, <<"'">>])
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+export(_Server) ->
+ [{motd,
+ fun(Host, #motd{server = LServer, packet = El})
+ when LServer == Host ->
+ [[<<"delete from motd where username='';">>],
+ [<<"insert into motd(username, xml) values ('', '">>,
+ ejabberd_sql:escape(fxml:element_to_binary(El)),
+ <<"');">>]];
+ (_Host, _R) ->
+ []
+ end},
+ {motd_users,
+ fun(Host, #motd_users{us = {LUser, LServer}})
+ when LServer == Host, LUser /= <<"">> ->
+ Username = ejabberd_sql:escape(LUser),
+ [[<<"delete from motd where username='">>, Username, <<"';">>],
+ [<<"insert into motd(username, xml) values ('">>,
+ Username, <<"', '');">>]];
+ (_Host, _R) ->
+ []
+ end}].
+
+import(LServer) ->
+ [{<<"select xml from motd where username='';">>,
+ fun([XML]) ->
+ El = fxml_stream:parse_element(XML),
+ #motd{server = LServer, packet = El}
+ end},
+ {<<"select username from motd where xml='';">>,
+ fun([LUser]) ->
+ #motd_users{us = {LUser, LServer}}
+ end}].
+
+import(_, _) ->
+ pass.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl
index 4f9c8273..af06e650 100644
--- a/src/mod_blocking.erl
+++ b/src/mod_blocking.erl
@@ -39,6 +39,10 @@
-include("mod_privacy.hrl").
+-callback process_blocklist_block(binary(), binary(), function()) -> {atomic, any()}.
+-callback unblock_by_filter(binary(), binary(), function()) -> {atomic, any()}.
+-callback process_blocklist_get(binary(), binary()) -> [listitem()] | error.
+
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
@@ -64,29 +68,33 @@ process_iq(_From, _To, IQ) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
process_iq_get(_, From, _To,
- #iq{xmlns = ?NS_BLOCKING,
+ #iq{xmlns = ?NS_BLOCKING, lang = Lang,
sub_el = #xmlel{name = <<"blocklist">>}},
_) ->
#jid{luser = LUser, lserver = LServer} = From,
- {stop, process_blocklist_get(LUser, LServer)};
+ {stop, process_blocklist_get(LUser, LServer, Lang)};
process_iq_get(Acc, _, _, _, _) -> Acc.
process_iq_set(_, From, _To,
- #iq{xmlns = ?NS_BLOCKING,
+ #iq{xmlns = ?NS_BLOCKING, lang = Lang,
sub_el =
#xmlel{name = SubElName, children = SubEls}}) ->
#jid{luser = LUser, lserver = LServer} = From,
- Res = case {SubElName, xml:remove_cdata(SubEls)} of
- {<<"block">>, []} -> {error, ?ERR_BAD_REQUEST};
+ Res = case {SubElName, fxml:remove_cdata(SubEls)} of
+ {<<"block">>, []} ->
+ Txt = <<"No items found in this query">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{<<"block">>, Els} ->
JIDs = parse_blocklist_items(Els, []),
- process_blocklist_block(LUser, LServer, JIDs);
+ process_blocklist_block(LUser, LServer, JIDs, Lang);
{<<"unblock">>, []} ->
- process_blocklist_unblock_all(LUser, LServer);
+ process_blocklist_unblock_all(LUser, LServer, Lang);
{<<"unblock">>, Els} ->
JIDs = parse_blocklist_items(Els, []),
- process_blocklist_unblock(LUser, LServer, JIDs);
- _ -> {error, ?ERR_BAD_REQUEST}
+ process_blocklist_unblock(LUser, LServer, JIDs, Lang);
+ _ ->
+ Txt = <<"Unknown blocking command">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end,
{stop, Res};
process_iq_set(Acc, _, _, _) -> Acc.
@@ -116,7 +124,7 @@ parse_blocklist_items([#xmlel{name = <<"item">>,
attrs = Attrs}
| Els],
JIDs) ->
- case xml:get_attr(<<"jid">>, Attrs) of
+ case fxml:get_attr(<<"jid">>, Attrs) of
{value, JID1} ->
JID = jid:tolower(jid:from_string(JID1)),
parse_blocklist_items(Els, [JID | JIDs]);
@@ -125,7 +133,7 @@ parse_blocklist_items([#xmlel{name = <<"item">>,
parse_blocklist_items([_ | Els], JIDs) ->
parse_blocklist_items(Els, JIDs).
-process_blocklist_block(LUser, LServer, JIDs) ->
+process_blocklist_block(LUser, LServer, JIDs, Lang) ->
Filter = fun (List) ->
AlreadyBlocked = list_to_blocklist_jids(List, []),
lists:foldr(fun (JID, List1) ->
@@ -143,9 +151,8 @@ process_blocklist_block(LUser, LServer, JIDs) ->
end,
List, JIDs)
end,
- case process_blocklist_block(LUser, LServer, Filter,
- gen_mod:db_type(LServer, mod_privacy))
- of
+ Mod = db_mod(LServer),
+ case Mod:process_blocklist_block(LUser, LServer, Filter) of
{atomic, {ok, Default, List}} ->
UserList = make_userlist(Default, List),
broadcast_list_update(LUser, LServer, Default,
@@ -155,110 +162,17 @@ process_blocklist_block(LUser, LServer, JIDs) ->
{result, [], UserList};
_Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
end.
-process_blocklist_block(LUser, LServer, Filter,
- mnesia) ->
- F = fun () ->
- case mnesia:wread({privacy, {LUser, LServer}}) of
- [] ->
- P = #privacy{us = {LUser, LServer}},
- NewDefault = <<"Blocked contacts">>,
- NewLists1 = [],
- List = [];
- [#privacy{default = Default, lists = Lists} = P] ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} ->
- NewDefault = Default,
- NewLists1 = lists:keydelete(Default, 1, Lists);
- false ->
- NewDefault = <<"Blocked contacts">>,
- NewLists1 = Lists,
- List = []
- end
- end,
- NewList = Filter(List),
- NewLists = [{NewDefault, NewList} | NewLists1],
- mnesia:write(P#privacy{default = NewDefault,
- lists = NewLists}),
- {ok, NewDefault, NewList}
- end,
- mnesia:transaction(F);
-process_blocklist_block(LUser, LServer, Filter,
- riak) ->
- {atomic,
- begin
- case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(),
- {LUser, LServer}) of
- {ok, #privacy{default = Default, lists = Lists} = P} ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} ->
- NewDefault = Default,
- NewLists1 = lists:keydelete(Default, 1, Lists);
- false ->
- NewDefault = <<"Blocked contacts">>,
- NewLists1 = Lists,
- List = []
- end;
- {error, _} ->
- P = #privacy{us = {LUser, LServer}},
- NewDefault = <<"Blocked contacts">>,
- NewLists1 = [],
- List = []
- end,
- NewList = Filter(List),
- NewLists = [{NewDefault, NewList} | NewLists1],
- case ejabberd_riak:put(P#privacy{default = NewDefault,
- lists = NewLists},
- mod_privacy:privacy_schema()) of
- ok ->
- {ok, NewDefault, NewList};
- Err ->
- Err
- end
- end};
-process_blocklist_block(LUser, LServer, Filter, odbc) ->
- F = fun () ->
- Default = case
- mod_privacy:sql_get_default_privacy_list_t(LUser)
- of
- {selected, [<<"name">>], []} ->
- Name = <<"Blocked contacts">>,
- mod_privacy:sql_add_privacy_list(LUser, Name),
- mod_privacy:sql_set_default_privacy_list(LUser,
- Name),
- Name;
- {selected, [<<"name">>], [[Name]]} -> Name
- end,
- {selected, [<<"id">>], [[ID]]} =
- mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
- case mod_privacy:sql_get_privacy_list_data_by_id_t(ID)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems = [_ | _]} ->
- List = lists:flatmap(fun mod_privacy:raw_to_item/1, RItems);
- _ -> List = []
- end,
- NewList = Filter(List),
- NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
- NewList),
- mod_privacy:sql_set_privacy_list(ID, NewRItems),
- {ok, Default, NewList}
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
-
-process_blocklist_unblock_all(LUser, LServer) ->
+process_blocklist_unblock_all(LUser, LServer, Lang) ->
Filter = fun (List) ->
lists:filter(fun (#listitem{action = A}) -> A =/= deny
end,
List)
end,
- DBType = gen_mod:db_type(LServer, mod_privacy),
- case unblock_by_filter(LUser, LServer, Filter, DBType) of
+ Mod = db_mod(LServer),
+ case Mod:unblock_by_filter(LUser, LServer, Filter) of
{atomic, ok} -> {result, []};
{atomic, {ok, Default, List}} ->
UserList = make_userlist(Default, List),
@@ -268,10 +182,10 @@ process_blocklist_unblock_all(LUser, LServer) ->
{result, [], UserList};
_Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer}, _Err]),
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
end.
-process_blocklist_unblock(LUser, LServer, JIDs) ->
+process_blocklist_unblock(LUser, LServer, JIDs, Lang) ->
Filter = fun (List) ->
lists:filter(fun (#listitem{action = deny, type = jid,
value = JID}) ->
@@ -280,8 +194,8 @@ process_blocklist_unblock(LUser, LServer, JIDs) ->
end,
List)
end,
- DBType = gen_mod:db_type(LServer, mod_privacy),
- case unblock_by_filter(LUser, LServer, Filter, DBType) of
+ Mod = db_mod(LServer),
+ case Mod:unblock_by_filter(LUser, LServer, Filter) of
{atomic, ok} -> {result, []};
{atomic, {ok, Default, List}} ->
UserList = make_userlist(Default, List),
@@ -292,84 +206,9 @@ process_blocklist_unblock(LUser, LServer, JIDs) ->
{result, [], UserList};
_Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
end.
-unblock_by_filter(LUser, LServer, Filter, mnesia) ->
- F = fun () ->
- case mnesia:read({privacy, {LUser, LServer}}) of
- [] ->
- % No lists, nothing to unblock
- ok;
- [#privacy{default = Default, lists = Lists} = P] ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} ->
- NewList = Filter(List),
- NewLists1 = lists:keydelete(Default, 1, Lists),
- NewLists = [{Default, NewList} | NewLists1],
- mnesia:write(P#privacy{lists = NewLists}),
- {ok, Default, NewList};
- false ->
- % No default list, nothing to unblock
- ok
- end
- end
- end,
- mnesia:transaction(F);
-unblock_by_filter(LUser, LServer, Filter, riak) ->
- {atomic,
- case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(),
- {LUser, LServer}) of
- {error, _} ->
- %% No lists, nothing to unblock
- ok;
- {ok, #privacy{default = Default, lists = Lists} = P} ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} ->
- NewList = Filter(List),
- NewLists1 = lists:keydelete(Default, 1, Lists),
- NewLists = [{Default, NewList} | NewLists1],
- case ejabberd_riak:put(P#privacy{lists = NewLists},
- mod_privacy:privacy_schema()) of
- ok ->
- {ok, Default, NewList};
- Err ->
- Err
- end;
- false ->
- %% No default list, nothing to unblock
- ok
- end
- end};
-unblock_by_filter(LUser, LServer, Filter, odbc) ->
- F = fun () ->
- case mod_privacy:sql_get_default_privacy_list_t(LUser)
- of
- {selected, [<<"name">>], []} -> ok;
- {selected, [<<"name">>], [[Default]]} ->
- {selected, [<<"id">>], [[ID]]} =
- mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
- case mod_privacy:sql_get_privacy_list_data_by_id_t(ID)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems = [_ | _]} ->
- List = lists:flatmap(fun mod_privacy:raw_to_item/1,
- RItems),
- NewList = Filter(List),
- NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
- NewList),
- mod_privacy:sql_set_privacy_list(ID, NewRItems),
- {ok, Default, NewList};
- _ -> ok
- end;
- _ -> ok
- end
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
-
make_userlist(Name, List) ->
NeedDb = mod_privacy:is_list_needdb(List),
#userlist{name = Name, list = List, needdb = NeedDb}.
@@ -385,11 +224,11 @@ broadcast_blocklist_event(LUser, LServer, Event) ->
ejabberd_sm:route(JID, JID,
{broadcast, {blocking, Event}}).
-process_blocklist_get(LUser, LServer) ->
- case process_blocklist_get(LUser, LServer,
- gen_mod:db_type(LServer, mod_privacy))
- of
- error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+process_blocklist_get(LUser, LServer, Lang) ->
+ Mod = db_mod(LServer),
+ case Mod:process_blocklist_get(LUser, LServer) of
+ error ->
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
List ->
JIDs = list_to_blocklist_jids(List, []),
Items = lists:map(fun (JID) ->
@@ -407,49 +246,9 @@ process_blocklist_get(LUser, LServer) ->
children = Items}]}
end.
-process_blocklist_get(LUser, LServer, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer})
- of
- {'EXIT', _Reason} -> error;
- [] -> [];
- [#privacy{default = Default, lists = Lists}] ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} -> List;
- _ -> []
- end
- end;
-process_blocklist_get(LUser, LServer, riak) ->
- case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(),
- {LUser, LServer}) of
- {ok, #privacy{default = Default, lists = Lists}} ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} -> List;
- _ -> []
- end;
- {error, notfound} ->
- [];
- {error, _} ->
- error
- end;
-process_blocklist_get(LUser, LServer, odbc) ->
- case catch
- mod_privacy:sql_get_default_privacy_list(LUser, LServer)
- of
- {selected, [<<"name">>], []} -> [];
- {selected, [<<"name">>], [[Default]]} ->
- case catch mod_privacy:sql_get_privacy_list_data(LUser,
- LServer, Default)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems} ->
- lists:flatmap(fun mod_privacy:raw_to_item/1, RItems);
- {'EXIT', _} -> error
- end;
- {'EXIT', _} -> error
- end.
+db_mod(LServer) ->
+ DBType = gen_mod:db_type(LServer, mod_privacy),
+ gen_mod:db_mod(DBType, ?MODULE).
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(_) -> [iqdisc].
diff --git a/src/mod_blocking_mnesia.erl b/src/mod_blocking_mnesia.erl
new file mode 100644
index 00000000..5a4bde64
--- /dev/null
+++ b/src/mod_blocking_mnesia.erl
@@ -0,0 +1,85 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_blocking_mnesia).
+
+-behaviour(mod_blocking).
+
+%% API
+-export([process_blocklist_block/3, unblock_by_filter/3,
+ process_blocklist_get/2]).
+
+-include("jlib.hrl").
+-include("mod_privacy.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+process_blocklist_block(LUser, LServer, Filter) ->
+ F = fun () ->
+ case mnesia:wread({privacy, {LUser, LServer}}) of
+ [] ->
+ P = #privacy{us = {LUser, LServer}},
+ NewDefault = <<"Blocked contacts">>,
+ NewLists1 = [],
+ List = [];
+ [#privacy{default = Default, lists = Lists} = P] ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} ->
+ NewDefault = Default,
+ NewLists1 = lists:keydelete(Default, 1, Lists);
+ false ->
+ NewDefault = <<"Blocked contacts">>,
+ NewLists1 = Lists,
+ List = []
+ end
+ end,
+ NewList = Filter(List),
+ NewLists = [{NewDefault, NewList} | NewLists1],
+ mnesia:write(P#privacy{default = NewDefault,
+ lists = NewLists}),
+ {ok, NewDefault, NewList}
+ end,
+ mnesia:transaction(F).
+
+unblock_by_filter(LUser, LServer, Filter) ->
+ F = fun () ->
+ case mnesia:read({privacy, {LUser, LServer}}) of
+ [] ->
+ %% No lists, nothing to unblock
+ ok;
+ [#privacy{default = Default, lists = Lists} = P] ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} ->
+ NewList = Filter(List),
+ NewLists1 = lists:keydelete(Default, 1, Lists),
+ NewLists = [{Default, NewList} | NewLists1],
+ mnesia:write(P#privacy{lists = NewLists}),
+ {ok, Default, NewList};
+ false ->
+ %% No default list, nothing to unblock
+ ok
+ end
+ end
+ end,
+ mnesia:transaction(F).
+
+process_blocklist_get(LUser, LServer) ->
+ case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
+ {'EXIT', _Reason} -> error;
+ [] -> [];
+ [#privacy{default = Default, lists = Lists}] ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} -> List;
+ _ -> []
+ end
+ end.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_blocking_riak.erl b/src/mod_blocking_riak.erl
new file mode 100644
index 00000000..5dd5cfa9
--- /dev/null
+++ b/src/mod_blocking_riak.erl
@@ -0,0 +1,98 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_blocking_riak).
+
+-behaviour(mod_blocking).
+
+%% API
+-export([process_blocklist_block/3, unblock_by_filter/3,
+ process_blocklist_get/2]).
+
+-include("jlib.hrl").
+-include("mod_privacy.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+process_blocklist_block(LUser, LServer, Filter) ->
+ {atomic,
+ begin
+ case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(),
+ {LUser, LServer}) of
+ {ok, #privacy{default = Default, lists = Lists} = P} ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} ->
+ NewDefault = Default,
+ NewLists1 = lists:keydelete(Default, 1, Lists);
+ false ->
+ NewDefault = <<"Blocked contacts">>,
+ NewLists1 = Lists,
+ List = []
+ end;
+ {error, _} ->
+ P = #privacy{us = {LUser, LServer}},
+ NewDefault = <<"Blocked contacts">>,
+ NewLists1 = [],
+ List = []
+ end,
+ NewList = Filter(List),
+ NewLists = [{NewDefault, NewList} | NewLists1],
+ case ejabberd_riak:put(P#privacy{default = NewDefault,
+ lists = NewLists},
+ mod_privacy_riak:privacy_schema()) of
+ ok ->
+ {ok, NewDefault, NewList};
+ Err ->
+ Err
+ end
+ end}.
+
+unblock_by_filter(LUser, LServer, Filter) ->
+ {atomic,
+ case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(),
+ {LUser, LServer}) of
+ {error, _} ->
+ %% No lists, nothing to unblock
+ ok;
+ {ok, #privacy{default = Default, lists = Lists} = P} ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} ->
+ NewList = Filter(List),
+ NewLists1 = lists:keydelete(Default, 1, Lists),
+ NewLists = [{Default, NewList} | NewLists1],
+ case ejabberd_riak:put(P#privacy{lists = NewLists},
+ mod_privacy_riak:privacy_schema()) of
+ ok ->
+ {ok, Default, NewList};
+ Err ->
+ Err
+ end;
+ false ->
+ %% No default list, nothing to unblock
+ ok
+ end
+ end}.
+
+process_blocklist_get(LUser, LServer) ->
+ case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(),
+ {LUser, LServer}) of
+ {ok, #privacy{default = Default, lists = Lists}} ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} -> List;
+ _ -> []
+ end;
+ {error, notfound} ->
+ [];
+ {error, _} ->
+ error
+ end.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_blocking_sql.erl b/src/mod_blocking_sql.erl
new file mode 100644
index 00000000..fb8380e2
--- /dev/null
+++ b/src/mod_blocking_sql.erl
@@ -0,0 +1,87 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_blocking_sql).
+
+-behaviour(mod_blocking).
+
+%% API
+-export([process_blocklist_block/3, unblock_by_filter/3,
+ process_blocklist_get/2]).
+
+-include("jlib.hrl").
+-include("mod_privacy.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+process_blocklist_block(LUser, LServer, Filter) ->
+ F = fun () ->
+ Default = case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of
+ {selected, []} ->
+ Name = <<"Blocked contacts">>,
+ mod_privacy_sql:sql_add_privacy_list(LUser, Name),
+ mod_privacy_sql:sql_set_default_privacy_list(LUser, Name),
+ Name;
+ {selected, [{Name}]} -> Name
+ end,
+ {selected, [{ID}]} =
+ mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Default),
+ case mod_privacy_sql:sql_get_privacy_list_data_by_id_t(ID) of
+ {selected, RItems = [_ | _]} ->
+ List = lists:flatmap(fun mod_privacy_sql:raw_to_item/1, RItems);
+ _ ->
+ List = []
+ end,
+ NewList = Filter(List),
+ NewRItems = lists:map(fun mod_privacy_sql:item_to_raw/1,
+ NewList),
+ mod_privacy_sql:sql_set_privacy_list(ID, NewRItems),
+ {ok, Default, NewList}
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+unblock_by_filter(LUser, LServer, Filter) ->
+ F = fun () ->
+ case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of
+ {selected, []} -> ok;
+ {selected, [{Default}]} ->
+ {selected, [{ID}]} =
+ mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Default),
+ case mod_privacy_sql:sql_get_privacy_list_data_by_id_t(ID) of
+ {selected, RItems = [_ | _]} ->
+ List = lists:flatmap(fun mod_privacy_sql:raw_to_item/1,
+ RItems),
+ NewList = Filter(List),
+ NewRItems = lists:map(fun mod_privacy_sql:item_to_raw/1,
+ NewList),
+ mod_privacy_sql:sql_set_privacy_list(ID, NewRItems),
+ {ok, Default, NewList};
+ _ -> ok
+ end;
+ _ -> ok
+ end
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+process_blocklist_get(LUser, LServer) ->
+ case catch mod_privacy_sql:sql_get_default_privacy_list(LUser, LServer) of
+ {selected, []} -> [];
+ {selected, [{Default}]} ->
+ case catch mod_privacy_sql:sql_get_privacy_list_data(
+ LUser, LServer, Default) of
+ {selected, RItems} ->
+ lists:flatmap(fun mod_privacy_sql:raw_to_item/1, RItems);
+ {'EXIT', _} -> error
+ end;
+ {'EXIT', _} -> error
+ end.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_caps.erl b/src/mod_caps.erl
index 7d764c4b..3d5c360a 100644
--- a/src/mod_caps.erl
+++ b/src/mod_caps.erl
@@ -80,6 +80,12 @@
-record(state, {host = <<"">> :: binary()}).
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback caps_read(binary(), {binary(), binary()}) ->
+ {ok, non_neg_integer() | [binary()]} | error.
+-callback caps_write(binary(), {binary(), binary()},
+ non_neg_integer() | [binary()]) -> any().
+
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE,
@@ -120,12 +126,12 @@ read_caps(Els) -> read_caps(Els, nothing).
read_caps([#xmlel{name = <<"c">>, attrs = Attrs}
| Tail],
Result) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_CAPS ->
- Node = xml:get_attr_s(<<"node">>, Attrs),
- Version = xml:get_attr_s(<<"ver">>, Attrs),
- Hash = xml:get_attr_s(<<"hash">>, Attrs),
- Exts = str:tokens(xml:get_attr_s(<<"ext">>, Attrs),
+ Node = fxml:get_attr_s(<<"node">>, Attrs),
+ Version = fxml:get_attr_s(<<"ver">>, Attrs),
+ Hash = fxml:get_attr_s(<<"hash">>, Attrs),
+ Exts = str:tokens(fxml:get_attr_s(<<"ext">>, Attrs),
<<" ">>),
read_caps(Tail,
#caps{node = Node, hash = Hash, version = Version,
@@ -135,7 +141,7 @@ read_caps([#xmlel{name = <<"c">>, attrs = Attrs}
read_caps([#xmlel{name = <<"x">>, attrs = Attrs}
| Tail],
Result) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC_USER -> nothing;
_ -> read_caps(Tail, Result)
end;
@@ -149,7 +155,7 @@ user_send_packet(#xmlel{name = <<"presence">>, attrs = Attrs,
#jid{luser = User, lserver = Server} = From,
#jid{luser = User, lserver = Server,
lresource = <<"">>}) ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
if Type == <<"">>; Type == <<"available">> ->
case read_caps(Els) of
nothing -> ok;
@@ -167,7 +173,7 @@ user_receive_packet(#xmlel{name = <<"presence">>, attrs = Attrs,
_C2SState,
#jid{lserver = Server},
From, _To) ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
IsRemote = not lists:member(From#jid.lserver, ?MYHOSTS),
if IsRemote and
((Type == <<"">>) or (Type == <<"available">>)) ->
@@ -227,7 +233,7 @@ disco_info(Acc, Host, Module, Node, Lang) ->
c2s_presence_in(C2SState,
{From, To, {_, _, Attrs, Els}}) ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
Subscription = ejabberd_c2s:get_subscription(From,
C2SState),
Insert = ((Type == <<"">>) or (Type == <<"available">>))
@@ -300,28 +306,9 @@ c2s_broadcast_recipients(InAcc, Host, C2SState,
end;
c2s_broadcast_recipients(Acc, _, _, _, _, _) -> Acc.
-init_db(mnesia, _Host) ->
- case catch mnesia:table_info(caps_features, storage_type) of
- {'EXIT', _} ->
- ok;
- disc_only_copies ->
- ok;
- _ ->
- mnesia:delete_table(caps_features)
- end,
- mnesia:create_table(caps_features,
- [{disc_only_copies, [node()]},
- {local_content, true},
- {attributes,
- record_info(fields, caps_features)}]),
- update_table(),
- mnesia:add_table_copy(caps_features, node(),
- disc_only_copies);
-init_db(_, _) ->
- ok.
-
init([Host, Opts]) ->
- init_db(gen_mod:db_type(Host, Opts), Host),
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
MaxSize = gen_mod:get_opt(cache_size, Opts,
fun(I) when is_integer(I), I>0 -> I end,
1000),
@@ -434,7 +421,7 @@ feature_response(#iq{type = result,
Features = lists:flatmap(fun (#xmlel{name =
<<"feature">>,
attrs = FAttrs}) ->
- [xml:get_attr_s(<<"var">>, FAttrs)];
+ [fxml:get_attr_s(<<"var">>, FAttrs)];
(_) -> []
end,
Els),
@@ -450,65 +437,13 @@ feature_response(_IQResult, Host, From, Caps,
caps_read_fun(Host, Node) ->
LServer = jid:nameprep(Host),
- DBType = gen_mod:db_type(LServer, ?MODULE),
- caps_read_fun(LServer, Node, DBType).
-
-caps_read_fun(_LServer, Node, mnesia) ->
- fun () ->
- case mnesia:dirty_read({caps_features, Node}) of
- [#caps_features{features = Features}] -> {ok, Features};
- _ -> error
- end
- end;
-caps_read_fun(_LServer, Node, riak) ->
- fun() ->
- case ejabberd_riak:get(caps_features, caps_features_schema(), Node) of
- {ok, #caps_features{features = Features}} -> {ok, Features};
- _ -> error
- end
- end;
-caps_read_fun(LServer, {Node, SubNode}, odbc) ->
- fun() ->
- SNode = ejabberd_odbc:escape(Node),
- SSubNode = ejabberd_odbc:escape(SubNode),
- case ejabberd_odbc:sql_query(
- LServer, [<<"select feature from caps_features where ">>,
- <<"node='">>, SNode, <<"' and subnode='">>,
- SSubNode, <<"';">>]) of
- {selected, [<<"feature">>], [[H]|_] = Fs} ->
- case catch jlib:binary_to_integer(H) of
- Int when is_integer(Int), Int>=0 ->
- {ok, Int};
- _ ->
- {ok, lists:flatten(Fs)}
- end;
- _ ->
- error
- end
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ fun() -> Mod:caps_read(LServer, Node) end.
caps_write_fun(Host, Node, Features) ->
LServer = jid:nameprep(Host),
- DBType = gen_mod:db_type(LServer, ?MODULE),
- caps_write_fun(LServer, Node, Features, DBType).
-
-caps_write_fun(_LServer, Node, Features, mnesia) ->
- fun () ->
- mnesia:dirty_write(#caps_features{node_pair = Node,
- features = Features})
- end;
-caps_write_fun(_LServer, Node, Features, riak) ->
- fun () ->
- ejabberd_riak:put(#caps_features{node_pair = Node,
- features = Features},
- caps_features_schema())
- end;
-caps_write_fun(LServer, NodePair, Features, odbc) ->
- fun () ->
- ejabberd_odbc:sql_transaction(
- LServer,
- sql_write_features_t(NodePair, Features))
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ fun() -> Mod:caps_write(LServer, Node, Features) end.
make_my_disco_hash(Host) ->
JID = jid:make(<<"">>, Host, <<"">>),
@@ -567,7 +502,7 @@ concat_features(Els) ->
lists:usort(lists:flatmap(fun (#xmlel{name =
<<"feature">>,
attrs = Attrs}) ->
- [[xml:get_attr_s(<<"var">>, Attrs), $<]];
+ [[fxml:get_attr_s(<<"var">>, Attrs), $<]];
(_) -> []
end,
Els)).
@@ -576,11 +511,11 @@ concat_identities(Els) ->
lists:sort(lists:flatmap(fun (#xmlel{name =
<<"identity">>,
attrs = Attrs}) ->
- [[xml:get_attr_s(<<"category">>, Attrs),
- $/, xml:get_attr_s(<<"type">>, Attrs),
+ [[fxml:get_attr_s(<<"category">>, Attrs),
+ $/, fxml:get_attr_s(<<"type">>, Attrs),
$/,
- xml:get_attr_s(<<"xml:lang">>, Attrs),
- $/, xml:get_attr_s(<<"name">>, Attrs),
+ fxml:get_attr_s(<<"xml:lang">>, Attrs),
+ $/, fxml:get_attr_s(<<"name">>, Attrs),
$<]];
(_) -> []
end,
@@ -589,8 +524,8 @@ concat_identities(Els) ->
concat_info(Els) ->
lists:sort(lists:flatmap(fun (#xmlel{name = <<"x">>,
attrs = Attrs, children = Fields}) ->
- case {xml:get_attr_s(<<"xmlns">>, Attrs),
- xml:get_attr_s(<<"type">>, Attrs)}
+ case {fxml:get_attr_s(<<"xmlns">>, Attrs),
+ fxml:get_attr_s(<<"type">>, Attrs)}
of
{?NS_XDATA, <<"result">>} ->
[concat_xdata_fields(Fields)];
@@ -606,10 +541,10 @@ concat_xdata_fields(Fields) ->
attrs = Attrs, children = Els} =
El,
[FormType, VarFields] = Acc) ->
- case xml:get_attr_s(<<"var">>, Attrs) of
+ case fxml:get_attr_s(<<"var">>, Attrs) of
<<"">> -> Acc;
<<"FORM_TYPE">> ->
- [xml:get_subtag_cdata(El,
+ [fxml:get_subtag_cdata(El,
<<"value">>),
VarFields];
Var ->
@@ -622,7 +557,7 @@ concat_xdata_fields(Fields) ->
children
=
VEls}) ->
- [[xml:get_cdata(VEls),
+ [[fxml:get_cdata(VEls),
$<]];
(_) ->
[]
@@ -658,64 +593,23 @@ is_valid_node(Node) ->
false
end.
-update_table() ->
- Fields = record_info(fields, caps_features),
- case mnesia:table_info(caps_features, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- caps_features, Fields, set,
- fun(#caps_features{node_pair = {N, _}}) -> N end,
- fun(#caps_features{node_pair = {N, P},
- features = Fs} = R) ->
- NewFs = if is_integer(Fs) ->
- Fs;
- true ->
- [iolist_to_binary(F) || F <- Fs]
- end,
- R#caps_features{node_pair = {iolist_to_binary(N),
- iolist_to_binary(P)},
- features = NewFs}
- end);
- _ ->
- ?INFO_MSG("Recreating caps_features table", []),
- mnesia:transform_table(caps_features, ignore, Fields)
- end.
-
-sql_write_features_t({Node, SubNode}, Features) ->
- SNode = ejabberd_odbc:escape(Node),
- SSubNode = ejabberd_odbc:escape(SubNode),
- NewFeatures = if is_integer(Features) ->
- [jlib:integer_to_binary(Features)];
- true ->
- Features
- end,
- [[<<"delete from caps_features where node='">>,
- SNode, <<"' and subnode='">>, SSubNode, <<"';">>]|
- [[<<"insert into caps_features(node, subnode, feature) ">>,
- <<"values ('">>, SNode, <<"', '">>, SSubNode, <<"', '">>,
- ejabberd_odbc:escape(F), <<"');">>] || F <- NewFeatures]].
-
caps_features_schema() ->
{record_info(fields, caps_features), #caps_features{}}.
-export(_Server) ->
- [{caps_features,
- fun(_Host, #caps_features{node_pair = NodePair,
- features = Features}) ->
- sql_write_features_t(NodePair, Features);
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import_info() ->
[{<<"caps_features">>, 4}].
import_start(LServer, DBType) ->
ets:new(caps_features_tmp, [private, named_table, bag]),
- init_db(DBType, LServer),
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:init(LServer, []),
ok.
-import(_LServer, {odbc, _}, _DBType, <<"caps_features">>,
+import(_LServer, {sql, _}, _DBType, <<"caps_features">>,
[Node, SubNode, Feature, _TimeStamp]) ->
Feature1 = case catch jlib:binary_to_integer(Feature) of
I when is_integer(I), I>0 -> I;
@@ -748,7 +642,7 @@ import_next(LServer, DBType, NodePair) ->
ejabberd_riak:put(
#caps_features{node_pair = NodePair, features = Features},
caps_features_schema());
- _ when DBType == odbc ->
+ _ when DBType == sql ->
ok
end,
import_next(LServer, DBType, ets:next(caps_features_tmp, NodePair)).
diff --git a/src/mod_caps_mnesia.erl b/src/mod_caps_mnesia.erl
new file mode 100644
index 00000000..0bf04b2c
--- /dev/null
+++ b/src/mod_caps_mnesia.erl
@@ -0,0 +1,73 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_caps_mnesia).
+-behaviour(mod_caps).
+
+%% API
+-export([init/2, caps_read/2, caps_write/3]).
+
+-include("mod_caps.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ case catch mnesia:table_info(caps_features, storage_type) of
+ {'EXIT', _} ->
+ ok;
+ disc_only_copies ->
+ ok;
+ _ ->
+ mnesia:delete_table(caps_features)
+ end,
+ mnesia:create_table(caps_features,
+ [{disc_only_copies, [node()]},
+ {local_content, true},
+ {attributes,
+ record_info(fields, caps_features)}]),
+ update_table(),
+ mnesia:add_table_copy(caps_features, node(),
+ disc_only_copies).
+
+caps_read(_LServer, Node) ->
+ case mnesia:dirty_read({caps_features, Node}) of
+ [#caps_features{features = Features}] -> {ok, Features};
+ _ -> error
+ end.
+
+caps_write(_LServer, Node, Features) ->
+ mnesia:dirty_write(#caps_features{node_pair = Node,
+ features = Features}).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_table() ->
+ Fields = record_info(fields, caps_features),
+ case mnesia:table_info(caps_features, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ caps_features, Fields, set,
+ fun(#caps_features{node_pair = {N, _}}) -> N end,
+ fun(#caps_features{node_pair = {N, P},
+ features = Fs} = R) ->
+ NewFs = if is_integer(Fs) ->
+ Fs;
+ true ->
+ [iolist_to_binary(F) || F <- Fs]
+ end,
+ R#caps_features{node_pair = {iolist_to_binary(N),
+ iolist_to_binary(P)},
+ features = NewFs}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating caps_features table", []),
+ mnesia:transform_table(caps_features, ignore, Fields)
+ end.
diff --git a/src/mod_caps_riak.erl b/src/mod_caps_riak.erl
new file mode 100644
index 00000000..6e59ba86
--- /dev/null
+++ b/src/mod_caps_riak.erl
@@ -0,0 +1,38 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_caps_riak).
+-behaviour(mod_caps).
+
+%% API
+-export([init/2, caps_read/2, caps_write/3]).
+
+-include("mod_caps.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+caps_read(_LServer, Node) ->
+ case ejabberd_riak:get(caps_features, caps_features_schema(), Node) of
+ {ok, #caps_features{features = Features}} -> {ok, Features};
+ _ -> error
+ end.
+
+caps_write(_LServer, Node, Features) ->
+ ejabberd_riak:put(#caps_features{node_pair = Node,
+ features = Features},
+ caps_features_schema()).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+caps_features_schema() ->
+ {record_info(fields, caps_features), #caps_features{}}.
diff --git a/src/mod_caps_sql.erl b/src/mod_caps_sql.erl
new file mode 100644
index 00000000..9f587db3
--- /dev/null
+++ b/src/mod_caps_sql.erl
@@ -0,0 +1,71 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_caps_sql).
+-behaviour(mod_caps).
+
+%% API
+-export([init/2, caps_read/2, caps_write/3, export/1]).
+
+-include("mod_caps.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+caps_read(LServer, {Node, SubNode}) ->
+ SNode = ejabberd_sql:escape(Node),
+ SSubNode = ejabberd_sql:escape(SubNode),
+ case ejabberd_sql:sql_query(
+ LServer, [<<"select feature from caps_features where ">>,
+ <<"node='">>, SNode, <<"' and subnode='">>,
+ SSubNode, <<"';">>]) of
+ {selected, [<<"feature">>], [[H]|_] = Fs} ->
+ case catch jlib:binary_to_integer(H) of
+ Int when is_integer(Int), Int>=0 ->
+ {ok, Int};
+ _ ->
+ {ok, lists:flatten(Fs)}
+ end;
+ _ ->
+ error
+ end.
+
+caps_write(LServer, NodePair, Features) ->
+ ejabberd_sql:sql_transaction(
+ LServer,
+ sql_write_features_t(NodePair, Features)).
+
+export(_Server) ->
+ [{caps_features,
+ fun(_Host, #caps_features{node_pair = NodePair,
+ features = Features}) ->
+ sql_write_features_t(NodePair, Features);
+ (_Host, _R) ->
+ []
+ end}].
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+sql_write_features_t({Node, SubNode}, Features) ->
+ SNode = ejabberd_sql:escape(Node),
+ SSubNode = ejabberd_sql:escape(SubNode),
+ NewFeatures = if is_integer(Features) ->
+ [jlib:integer_to_binary(Features)];
+ true ->
+ Features
+ end,
+ [[<<"delete from caps_features where node='">>,
+ SNode, <<"' and subnode='">>, SSubNode, <<"';">>]|
+ [[<<"insert into caps_features(node, subnode, feature) ">>,
+ <<"values ('">>, SNode, <<"', '">>, SSubNode, <<"', '">>,
+ ejabberd_sql:escape(F), <<"');">>] || F <- NewFeatures]].
+
diff --git a/src/mod_carboncopy.erl b/src/mod_carboncopy.erl
index a4ca45dd..ebf1d0b0 100644
--- a/src/mod_carboncopy.erl
+++ b/src/mod_carboncopy.erl
@@ -43,21 +43,20 @@
-include("logger.hrl").
-include("jlib.hrl").
-define(PROCNAME, ?MODULE).
--define(TABLE, carboncopy).
--type matchspec_atom() :: '_' | '$1' | '$2' | '$3'.
--record(carboncopy,{us :: {binary(), binary()} | matchspec_atom(),
- resource :: binary() | matchspec_atom(),
- version :: binary() | matchspec_atom()}).
+-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()}].
is_carbon_copy(Packet) ->
is_carbon_copy(Packet, <<"sent">>) orelse
is_carbon_copy(Packet, <<"received">>).
is_carbon_copy(Packet, Direction) ->
- case xml:get_subtag(Packet, Direction) of
+ case fxml:get_subtag(Packet, Direction) of
#xmlel{name = Direction, attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_CARBONS_2 -> true;
?NS_CARBONS_1 -> true;
_ -> false
@@ -69,17 +68,8 @@ start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts,fun gen_iq_handler:check_type/1, one_queue),
mod_disco:register_feature(Host, ?NS_CARBONS_1),
mod_disco:register_feature(Host, ?NS_CARBONS_2),
- Fields = record_info(fields, ?TABLE),
- try mnesia:table_info(?TABLE, attributes) of
- Fields -> ok;
- _ -> mnesia:delete_table(?TABLE) %% recreate..
- catch _:_Error -> ok %%probably table don't exist
- end,
- mnesia:create_table(?TABLE,
- [{ram_copies, [node()]},
- {attributes, record_info(fields, ?TABLE)},
- {type, bag}]),
- mnesia:add_table_copy(?TABLE, node(), ram_copies),
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:init(Host, Opts),
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),
@@ -102,7 +92,9 @@ iq_handler2(From, To, IQ) ->
iq_handler1(From, To, IQ) ->
iq_handler(From, To, IQ, ?NS_CARBONS_1).
-iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children = []}} = IQ, CC)->
+iq_handler(From, _To,
+ #iq{type=set, lang = Lang,
+ sub_el = #xmlel{name = Operation} = SubEl} = IQ, CC)->
?DEBUG("carbons IQ received: ~p", [IQ]),
{U, S, R} = jid:tolower(From),
Result = case Operation of
@@ -118,12 +110,14 @@ iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children
?DEBUG("carbons IQ result: ok", []),
IQ#iq{type=result, sub_el=[]};
{error,_Error} ->
- ?WARNING_MSG("Error enabling / disabling carbons: ~p", [Result]),
- IQ#iq{type=error,sub_el = [?ERR_BAD_REQUEST]}
+ ?ERROR_MSG("Error enabling / disabling carbons: ~p", [Result]),
+ Txt = <<"Database failure">>,
+ IQ#iq{type=error,sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
end;
-iq_handler(_From, _To, IQ, _CC)->
- IQ#iq{type=error, sub_el = [?ERR_NOT_ALLOWED]}.
+iq_handler(_From, _To, #iq{lang = Lang, sub_el = SubEl} = IQ, _CC)->
+ Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
+ IQ#iq{type=error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}.
user_send_packet(Packet, _C2SState, From, To) ->
check_and_forward(From, To, Packet, sent).
@@ -137,8 +131,8 @@ user_receive_packet(Packet, _C2SState, JID, _From, To) ->
% - we also replicate "read" notifications
check_and_forward(JID, To, Packet, Direction)->
case is_chat_message(Packet) andalso
- xml:get_subtag(Packet, <<"private">>) == false andalso
- xml:get_subtag(Packet, <<"no-copy">>) == false of
+ fxml:get_subtag(Packet, <<"private">>) == false andalso
+ fxml:get_subtag(Packet, <<"no-copy">>) == false of
true ->
case is_carbon_copy(Packet) of
false ->
@@ -240,18 +234,13 @@ build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_1) ->
enable(Host, U, R, CC)->
?DEBUG("enabling for ~p", [U]),
- try mnesia:dirty_write(#carboncopy{us = {U, Host}, resource=R, version = CC}) of
- ok -> ok
- catch _:Error -> {error, Error}
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:enable(U, Host, R, CC).
disable(Host, U, R)->
?DEBUG("disabling for ~p", [U]),
- ToDelete = mnesia:dirty_match_object(?TABLE, #carboncopy{us = {U, Host}, resource = R, version = '_'}),
- try lists:foreach(fun mnesia:dirty_delete_object/1, ToDelete) of
- ok -> ok
- catch _:Error -> {error, Error}
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:disable(U, Host, R).
complete_packet(From, #xmlel{name = <<"message">>, attrs = OrigAttrs} = Packet, sent) ->
%% if this is a packet sent by user on this host, then Packet doesn't
@@ -268,7 +257,7 @@ complete_packet(_From, #xmlel{name = <<"message">>, attrs=OrigAttrs} = Packet, r
Packet#xmlel{attrs = Attrs}.
message_type(#xmlel{attrs = Attrs}) ->
- case xml:get_attr(<<"type">>, Attrs) of
+ case fxml:get_attr(<<"type">>, Attrs) of
{value, Type} -> Type;
false -> <<"normal">>
end.
@@ -282,12 +271,16 @@ is_chat_message(#xmlel{name = <<"message">>} = Packet) ->
is_chat_message(_Packet) -> false.
has_non_empty_body(Packet) ->
- xml:get_subtag_cdata(Packet, <<"body">>) =/= <<"">>.
+ fxml:get_subtag_cdata(Packet, <<"body">>) =/= <<"">>.
%% list {resource, cc_version} with carbons enabled for given user and host
list(User, Server) ->
- mnesia:dirty_select(?TABLE, [{#carboncopy{us = {User, Server}, resource = '$2', version = '$3'}, [], [{{'$2','$3'}}]}]).
-
+ Mod = gen_mod:db_mod(Server, ?MODULE),
+ Mod:list(User, Server).
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
-mod_opt_type(_) -> [iqdisc].
+mod_opt_type(db_type) ->
+ fun(internal) -> mnesia;
+ (mnesia) -> mnesia
+ end;
+mod_opt_type(_) -> [db_type, iqdisc].
diff --git a/src/mod_carboncopy_mnesia.erl b/src/mod_carboncopy_mnesia.erl
new file mode 100644
index 00000000..bf69bd21
--- /dev/null
+++ b/src/mod_carboncopy_mnesia.erl
@@ -0,0 +1,68 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_carboncopy_mnesia).
+
+-behaviour(mod_carboncopy).
+
+%% API
+-export([init/2, enable/4, disable/3, list/2]).
+
+-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,
+ mnesia:create_table(carboncopy,
+ [{ram_copies, [node()]},
+ {attributes, record_info(fields, carboncopy)},
+ {type, bag}]),
+ mnesia:add_table_copy(carboncopy, node(), ram_copies).
+
+enable(LUser, LServer, LResource, NS) ->
+ try mnesia:dirty_write(
+ #carboncopy{us = {LUser, LServer},
+ resource = LResource,
+ version = NS}) of
+ ok -> ok
+ catch _:Error ->
+ {error, Error}
+ end.
+
+disable(LUser, LServer, LResource) ->
+ ToDelete = mnesia:dirty_match_object(
+ #carboncopy{us = {LUser, LServer},
+ resource = LResource,
+ version = '_'}),
+ try lists:foreach(fun mnesia:dirty_delete_object/1, ToDelete) of
+ ok -> ok
+ catch _:Error ->
+ {error, Error}
+ end.
+
+list(LUser, LServer) ->
+ mnesia:dirty_select(
+ carboncopy,
+ [{#carboncopy{us = {LUser, LServer}, resource = '$2', version = '$3'},
+ [], [{{'$2','$3'}}]}]).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_client_state.erl b/src/mod_client_state.erl
index 3c046d77..dfbfc028 100644
--- a/src/mod_client_state.erl
+++ b/src/mod_client_state.erl
@@ -80,7 +80,7 @@ add_stream_feature(Features, _Host) ->
[Feature | Features].
filter_presence(_Action, #xmlel{name = <<"presence">>, attrs = Attrs}) ->
- case xml:get_attr(<<"type">>, Attrs) of
+ case fxml:get_attr(<<"type">>, Attrs) of
{value, Type} when Type /= <<"unavailable">> ->
?DEBUG("Got important presence stanza", []),
{stop, send};
diff --git a/src/mod_configure.erl b/src/mod_configure.erl
index 0c7e7526..a836c33b 100644
--- a/src/mod_configure.erl
+++ b/src/mod_configure.erl
@@ -198,81 +198,81 @@ get_local_identity(Acc, _From, _To, Node, Lang) ->
%%%-----------------------------------------------------------------------
--define(INFO_RESULT(Allow, Feats),
+-define(INFO_RESULT(Allow, Feats, Lang),
case Allow of
- deny -> {error, ?ERR_FORBIDDEN};
+ deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow -> {result, Feats}
end).
get_sm_features(Acc, From,
- #jid{lserver = LServer} = _To, Node, _Lang) ->
+ #jid{lserver = LServer} = _To, Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false -> Acc;
_ ->
Allow = acl:match_rule(LServer, configure, From),
case Node of
- <<"config">> -> ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ <<"config">> -> ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
_ -> Acc
end
end.
get_local_features(Acc, From,
- #jid{lserver = LServer} = _To, Node, _Lang) ->
+ #jid{lserver = LServer} = _To, Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false -> Acc;
_ ->
LNode = tokenize(Node),
Allow = acl:match_rule(LServer, configure, From),
case LNode of
- [<<"config">>] -> ?INFO_RESULT(Allow, []);
- [<<"user">>] -> ?INFO_RESULT(Allow, []);
- [<<"online users">>] -> ?INFO_RESULT(Allow, []);
- [<<"all users">>] -> ?INFO_RESULT(Allow, []);
+ [<<"config">>] -> ?INFO_RESULT(Allow, [], Lang);
+ [<<"user">>] -> ?INFO_RESULT(Allow, [], Lang);
+ [<<"online users">>] -> ?INFO_RESULT(Allow, [], Lang);
+ [<<"all users">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"all users">>, <<$@, _/binary>>] ->
- ?INFO_RESULT(Allow, []);
- [<<"outgoing s2s">> | _] -> ?INFO_RESULT(Allow, []);
- [<<"running nodes">>] -> ?INFO_RESULT(Allow, []);
- [<<"stopped nodes">>] -> ?INFO_RESULT(Allow, []);
+ ?INFO_RESULT(Allow, [], Lang);
+ [<<"outgoing s2s">> | _] -> ?INFO_RESULT(Allow, [], Lang);
+ [<<"running nodes">>] -> ?INFO_RESULT(Allow, [], Lang);
+ [<<"stopped nodes">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>, _ENode] ->
- ?INFO_RESULT(Allow, [?NS_STATS]);
+ ?INFO_RESULT(Allow, [?NS_STATS], Lang);
[<<"running nodes">>, _ENode, <<"DB">>] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"modules">>] ->
- ?INFO_RESULT(Allow, []);
+ ?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>, _ENode, <<"modules">>, _] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"backup">>] ->
- ?INFO_RESULT(Allow, []);
+ ?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>, _ENode, <<"backup">>, _] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"import">>] ->
- ?INFO_RESULT(Allow, []);
+ ?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>, _ENode, <<"import">>, _] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"restart">>] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"config">>, _] ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"add-user">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"delete-user">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"end-user-session">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-user-password">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"change-user-password">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-user-lastlogin">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"user-stats">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-registered-users-num">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-online-users-num">>) ->
- ?INFO_RESULT(Allow, [?NS_COMMANDS]);
+ ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
_ -> Acc
end
end.
@@ -318,7 +318,8 @@ get_sm_items(Acc, From,
{result,
Items ++ Nodes ++ get_user_resources(User, Server)};
{allow, <<"config">>} -> {result, []};
- {_, <<"config">>} -> {error, ?ERR_FORBIDDEN};
+ {_, <<"config">>} ->
+ {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
_ -> Acc
end
end.
@@ -350,7 +351,7 @@ adhoc_local_items(Acc, From,
Nodes = recursively_get_local_items(PermLev, LServer,
<<"">>, Server, Lang),
Nodes1 = lists:filter(fun (N) ->
- Nd = xml:get_tag_attr_s(<<"node">>, N),
+ Nd = fxml:get_tag_attr_s(<<"node">>, N),
F = get_local_features([], From, To, Nd,
Lang),
case F of
@@ -379,9 +380,9 @@ recursively_get_local_items(PermLev, LServer, Node,
{error, _Error} -> []
end,
Nodes = lists:flatten(lists:map(fun (N) ->
- S = xml:get_tag_attr_s(<<"jid">>,
+ S = fxml:get_tag_attr_s(<<"jid">>,
N),
- Nd = xml:get_tag_attr_s(<<"node">>,
+ Nd = fxml:get_tag_attr_s(<<"node">>,
N),
if (S /= Server) or
(Nd == <<"">>) ->
@@ -448,63 +449,64 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To,
_ ->
LNode = tokenize(Node),
Allow = acl:match_rule(LServer, configure, From),
+ Err = ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>),
case LNode of
[<<"config">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"user">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"online users">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"all users">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"all users">>, <<$@, _/binary>>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"outgoing s2s">> | _] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"stopped nodes">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"DB">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"modules">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"modules">>, _] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"backup">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"backup">>, _] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"import">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"import">>, _] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"restart">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"config">>, _] ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"add-user">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"delete-user">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"end-user-session">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-user-password">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"change-user-password">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-user-lastlogin">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"user-stats">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-registered-users-num">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-online-users-num">>) ->
- ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
+ ?ITEMS_RESULT(Allow, LNode, {error, Err});
_ -> Acc
end
end.
@@ -562,33 +564,29 @@ get_local_items({_, Host}, [<<"all users">>], _Server,
get_local_items({_, Host},
[<<"all users">>, <<$@, Diap/binary>>], _Server,
_Lang) ->
- case catch ejabberd_auth:get_vh_registered_users(Host)
- of
- {'EXIT', _Reason} -> ?ERR_INTERNAL_SERVER_ERROR;
- Users ->
- SUsers = lists:sort([{S, U} || {U, S} <- Users]),
- case catch begin
- [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
- N1 = jlib:binary_to_integer(S1),
- N2 = jlib:binary_to_integer(S2),
- Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
- lists:map(fun ({S, U}) ->
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>,
- <<U/binary, "@",
- S/binary>>},
- {<<"name">>,
- <<U/binary, "@",
- S/binary>>}],
- children = []}
- end,
- Sub)
- end
- of
- {'EXIT', _Reason} -> ?ERR_NOT_ACCEPTABLE;
- Res -> {result, Res}
- end
+ Users = ejabberd_auth:get_vh_registered_users(Host),
+ SUsers = lists:sort([{S, U} || {U, S} <- Users]),
+ case catch begin
+ [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
+ N1 = jlib:binary_to_integer(S1),
+ N2 = jlib:binary_to_integer(S2),
+ Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
+ lists:map(fun ({S, U}) ->
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ <<U/binary, "@",
+ S/binary>>},
+ {<<"name">>,
+ <<U/binary, "@",
+ S/binary>>}],
+ children = []}
+ end,
+ Sub)
+ end
+ of
+ {'EXIT', _Reason} -> ?ERR_NOT_ACCEPTABLE;
+ Res -> {result, Res}
end;
get_local_items({_, Host}, [<<"outgoing s2s">>],
_Server, Lang) ->
@@ -826,33 +824,33 @@ get_stopped_nodes(_Lang) ->
%%-------------------------------------------------------------------------
-define(COMMANDS_RESULT(LServerOrGlobal, From, To,
- Request),
+ Request, Lang),
case acl:match_rule(LServerOrGlobal, configure, From) of
- deny -> {error, ?ERR_FORBIDDEN};
+ deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow -> adhoc_local_commands(From, To, Request)
end).
adhoc_local_commands(Acc, From,
#jid{lserver = LServer} = To,
- #adhoc_request{node = Node} = Request) ->
+ #adhoc_request{node = Node, lang = Lang} = Request) ->
LNode = tokenize(Node),
case LNode of
[<<"running nodes">>, _ENode, <<"DB">>] ->
- ?COMMANDS_RESULT(global, From, To, Request);
+ ?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"modules">>, _] ->
- ?COMMANDS_RESULT(LServer, From, To, Request);
+ ?COMMANDS_RESULT(LServer, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"backup">>, _] ->
- ?COMMANDS_RESULT(global, From, To, Request);
+ ?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"import">>, _] ->
- ?COMMANDS_RESULT(global, From, To, Request);
+ ?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"restart">>] ->
- ?COMMANDS_RESULT(global, From, To, Request);
+ ?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
- ?COMMANDS_RESULT(global, From, To, Request);
+ ?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"config">>, _] ->
- ?COMMANDS_RESULT(LServer, From, To, Request);
+ ?COMMANDS_RESULT(LServer, From, To, Request, Lang);
?NS_ADMINL(_) ->
- ?COMMANDS_RESULT(LServer, From, To, Request);
+ ?COMMANDS_RESULT(LServer, From, To, Request, Lang);
_ -> Acc
end.
@@ -882,7 +880,8 @@ adhoc_local_commands(From,
end;
XData /= false, ActionIsExecute ->
case jlib:parse_xdata_submit(XData) of
- invalid -> {error, ?ERR_BAD_REQUEST};
+ invalid ->
+ {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)};
Fields ->
case catch set_form(From, LServer, LNode, Lang, Fields)
of
@@ -898,7 +897,8 @@ adhoc_local_commands(From,
{error, Error} -> {error, Error}
end
end;
- true -> {error, ?ERR_BAD_REQUEST}
+ true ->
+ {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect action or data form">>)}
end.
-define(TVFIELD(Type, Var, Val),
@@ -980,10 +980,14 @@ adhoc_local_commands(From,
get_form(_Host, [<<"running nodes">>, ENode, <<"DB">>],
Lang) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case ejabberd_cluster:call(Node, mnesia, system_info, [tables]) of
- {badrpc, _Reason} ->
+ {badrpc, Reason} ->
+ ?ERROR_MSG("RPC call mnesia:system_info(tables) on node "
+ "~s failed: ~p", [Node, Reason]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
Tables ->
STables = lists:sort(Tables),
@@ -1023,10 +1027,14 @@ get_form(Host,
[<<"running nodes">>, ENode, <<"modules">>, <<"stop">>],
Lang) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case ejabberd_cluster:call(Node, gen_mod, loaded_modules, [Host]) of
- {badrpc, _Reason} ->
+ {badrpc, Reason} ->
+ ?ERROR_MSG("RPC call gen_mod:loaded_modules(~s) on node "
+ "~s failed: ~p", [Host, Node, Reason]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
Modules ->
SModules = lists:sort(Modules),
@@ -1562,9 +1570,11 @@ get_form(_Host, _, _Lang) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
set_form(_From, _Host,
- [<<"running nodes">>, ENode, <<"DB">>], _Lang, XData) ->
+ [<<"running nodes">>, ENode, <<"DB">>], Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
lists:foreach(fun ({SVar, SVals}) ->
Table = jlib:binary_to_atom(SVar),
@@ -1596,9 +1606,11 @@ set_form(_From, _Host,
end;
set_form(_From, Host,
[<<"running nodes">>, ENode, <<"modules">>, <<"stop">>],
- _Lang, XData) ->
+ Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
lists:foreach(fun ({Var, Vals}) ->
case Vals of
@@ -1615,12 +1627,16 @@ set_form(_From, Host,
set_form(_From, Host,
[<<"running nodes">>, ENode, <<"modules">>,
<<"start">>],
- _Lang, XData) ->
+ Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"modules">>, 1, XData) of
- false -> {error, ?ERR_BAD_REQUEST};
+ false ->
+ Txt = <<"No 'modules' found in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{value, {_, Strings}} ->
String = lists:foldl(fun (S, Res) ->
<<Res/binary, S/binary, "\n">>
@@ -1637,98 +1653,143 @@ set_form(_From, Host,
end,
Modules),
{result, []};
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)}
end
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"backup">>],
- _Lang, XData) ->
+ Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"path">>, 1, XData) of
- false -> {error, ?ERR_BAD_REQUEST};
+ false ->
+ Txt = <<"No 'path' found in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{value, {_, [String]}} ->
case ejabberd_cluster:call(Node, mnesia, backup, [String]) of
- {badrpc, _Reason} ->
+ {badrpc, Reason} ->
+ ?ERROR_MSG("RPC call mnesia:backup(~s) to node ~s "
+ "failed: ~p", [String, Node, Reason]),
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, Reason} ->
+ ?ERROR_MSG("RPC call mnesia:backup(~s) to node ~s "
+ "failed: ~p", [String, Node, Reason]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
- {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
_ -> {result, []}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Incorrect value of 'path' in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"restore">>],
- _Lang, XData) ->
+ Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"path">>, 1, XData) of
- false -> {error, ?ERR_BAD_REQUEST};
+ false ->
+ Txt = <<"No 'path' found in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{value, {_, [String]}} ->
case ejabberd_cluster:call(Node, ejabberd_admin, restore, [String])
of
- {badrpc, _Reason} ->
+ {badrpc, Reason} ->
+ ?ERROR_MSG("RPC call ejabberd_admin:restore(~s) to node "
+ "~s failed: ~p", [String, Node, Reason]),
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, Reason} ->
+ ?ERROR_MSG("RPC call ejabberd_admin:restore(~s) to node "
+ "~s failed: ~p", [String, Node, Reason]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
- {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
_ -> {result, []}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Incorrect value of 'path' in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"textfile">>],
- _Lang, XData) ->
+ Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"path">>, 1, XData) of
- false -> {error, ?ERR_BAD_REQUEST};
+ false ->
+ Txt = <<"No 'path' found in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{value, {_, [String]}} ->
case ejabberd_cluster:call(Node, ejabberd_admin, dump_to_textfile,
[String])
of
- {badrpc, _Reason} ->
+ {badrpc, Reason} ->
+ ?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~s) "
+ "to node ~s failed: ~p", [String, Node, Reason]),
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, Reason} ->
+ ?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~s) "
+ "to node ~s failed: ~p", [String, Node, Reason]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
- {error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
_ -> {result, []}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Incorrect value of 'path' in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"import">>, <<"file">>],
- _Lang, XData) ->
+ Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"path">>, 1, XData) of
- false -> {error, ?ERR_BAD_REQUEST};
+ false ->
+ Txt = <<"No 'path' found in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{value, {_, [String]}} ->
ejabberd_cluster:call(Node, jd2ejd, import_file, [String]),
{result, []};
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Incorrect value of 'path' in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"import">>, <<"dir">>],
- _Lang, XData) ->
+ Lang, XData) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"path">>, 1, XData) of
- false -> {error, ?ERR_BAD_REQUEST};
+ false ->
+ Txt = <<"No 'path' found in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{value, {_, [String]}} ->
ejabberd_cluster:call(Node, jd2ejd, import_dir, [String]),
{result, []};
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Incorrect value of 'path' in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
set_form(From, Host,
@@ -1739,7 +1800,7 @@ set_form(From, Host,
[<<"running nodes">>, ENode, <<"shutdown">>], _Lang,
XData) ->
stop_node(From, Host, ENode, stop, XData);
-set_form(_From, Host, [<<"config">>, <<"acls">>], _Lang,
+set_form(_From, Host, [<<"config">>, <<"acls">>], Lang,
XData) ->
case lists:keysearch(<<"acls">>, 1, XData) of
{value, {_, Strings}} ->
@@ -1753,14 +1814,16 @@ set_form(_From, Host, [<<"config">>, <<"acls">>], _Lang,
{ok, ACLs} ->
acl:add_list(Host, ACLs, true),
{result, []};
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"No 'acls' found in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end;
set_form(_From, Host, [<<"config">>, <<"access">>],
- _Lang, XData) ->
+ Lang, XData) ->
SetAccess = fun (Rs) ->
mnesia:transaction(fun () ->
Os = mnesia:select(local_config,
@@ -1803,11 +1866,13 @@ set_form(_From, Host, [<<"config">>, <<"access">>],
{atomic, _} -> {result, []};
_ -> {error, ?ERR_BAD_REQUEST}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"No 'access' found in data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end;
set_form(From, Host, ?NS_ADMINL(<<"add-user">>), _Lang,
XData) ->
@@ -2052,7 +2117,7 @@ adhoc_sm_commands(_Acc, From,
action = Action, xdata = XData} =
Request) ->
case acl:match_rule(LServer, configure, From) of
- deny -> {error, ?ERR_FORBIDDEN};
+ deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow ->
ActionIsExecute = lists:member(Action,
[<<"">>, <<"execute">>,
@@ -2071,11 +2136,15 @@ adhoc_sm_commands(_Acc, From,
end;
XData /= false, ActionIsExecute ->
case jlib:parse_xdata_submit(XData) of
- invalid -> {error, ?ERR_BAD_REQUEST};
+ invalid ->
+ Txt = <<"Incorrect data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
Fields ->
set_sm_form(User, Server, <<"config">>, Request, Fields)
end;
- true -> {error, ?ERR_BAD_REQUEST}
+ true ->
+ Txt = <<"Incorrect action or data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
adhoc_sm_commands(Acc, _From, _To, _Request) -> Acc.
@@ -2135,12 +2204,16 @@ set_sm_form(User, Server, <<"config">>,
{value, {_, [Password]}} ->
ejabberd_auth:set_password(User, Server, Password),
adhoc:produce_response(Response);
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ ->
+ Txt = <<"No 'password' found in data form">>,
+ {error, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)}
end;
{value, {_, [<<"remove">>]}} ->
catch ejabberd_auth:remove_user(User, Server),
adhoc:produce_response(Response);
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ ->
+ Txt = <<"Incorrect value of 'action' in data form">>,
+ {error, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)}
end;
set_sm_form(_User, _Server, _Node, _Request, _Fields) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
diff --git a/src/mod_configure2.erl b/src/mod_configure2.erl
index 3e00a9cf..a8287b4d 100644
--- a/src/mod_configure2.erl
+++ b/src/mod_configure2.erl
@@ -56,16 +56,18 @@ stop(Host) ->
?NS_ECONFIGURE).
process_local_iq(From, To,
- #iq{type = Type, lang = _Lang, sub_el = SubEl} = IQ) ->
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case acl:match_rule(To#jid.lserver, configure, From) of
deny ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Denied by ACL">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
allow ->
case Type of
set ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]};
- %%case xml:get_tag_attr_s("type", SubEl) of
+ sub_el = [SubEl, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)]};
+ %%case fxml:get_tag_attr_s("type", SubEl) of
%% "cancel" ->
%% IQ#iq{type = result,
%% sub_el = [{xmlelement, "query",
@@ -79,7 +81,7 @@ process_local_iq(From, To,
%% _ ->
%% Node =
%% string:tokens(
- %% xml:get_tag_attr_s("node", SubEl),
+ %% fxml:get_tag_attr_s("node", SubEl),
%% "/"),
%% case set_form(Node, Lang, XData) of
%% {result, Res} ->
@@ -98,7 +100,7 @@ process_local_iq(From, To,
%% sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
%%end;
get ->
- case process_get(SubEl) of
+ case process_get(SubEl, Lang) of
{result, Res} -> IQ#iq{type = result, sub_el = [Res]};
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]}
@@ -106,7 +108,7 @@ process_local_iq(From, To,
end
end.
-process_get(#xmlel{name = <<"info">>}) ->
+process_get(#xmlel{name = <<"info">>}, _Lang) ->
S2SConns = ejabberd_s2s:dirty_get_connections(),
TConns = lists:usort([element(2, C) || C <- S2SConns]),
Attrs = [{<<"registered-users">>,
@@ -130,7 +132,7 @@ process_get(#xmlel{name = <<"info">>}) ->
attrs = [{<<"xmlns">>, ?NS_ECONFIGURE} | Attrs],
children = []}};
process_get(#xmlel{name = <<"welcome-message">>,
- attrs = Attrs}) ->
+ attrs = Attrs}, _Lang) ->
{Subj, Body} = ejabberd_config:get_option(
welcome_message,
fun({Subj, Body}) ->
@@ -146,7 +148,7 @@ process_get(#xmlel{name = <<"welcome-message">>,
#xmlel{name = <<"body">>, attrs = [],
children = [{xmlcdata, Body}]}]}};
process_get(#xmlel{name = <<"registration-watchers">>,
- attrs = Attrs}) ->
+ attrs = Attrs}, _Lang) ->
SubEls = ejabberd_config:get_option(
registration_watchers,
fun(JIDs) when is_list(JIDs) ->
@@ -160,14 +162,14 @@ process_get(#xmlel{name = <<"registration-watchers">>,
{result,
#xmlel{name = <<"registration_watchers">>,
attrs = Attrs, children = SubEls}};
-process_get(#xmlel{name = <<"acls">>, attrs = Attrs}) ->
+process_get(#xmlel{name = <<"acls">>, attrs = Attrs}, _Lang) ->
Str = iolist_to_binary(io_lib:format("~p.",
[ets:tab2list(acl)])),
{result,
#xmlel{name = <<"acls">>, attrs = Attrs,
children = [{xmlcdata, Str}]}};
process_get(#xmlel{name = <<"access">>,
- attrs = Attrs}) ->
+ attrs = Attrs}, _Lang) ->
Str = iolist_to_binary(io_lib:format("~p.",
[ets:select(local_config,
[{{local_config, {access, '$1'},
@@ -178,13 +180,14 @@ process_get(#xmlel{name = <<"access">>,
{result,
#xmlel{name = <<"access">>, attrs = Attrs,
children = [{xmlcdata, Str}]}};
-process_get(#xmlel{name = <<"last">>, attrs = Attrs}) ->
+process_get(#xmlel{name = <<"last">>, attrs = Attrs}, Lang) ->
case catch mnesia:dirty_select(last_activity,
[{{last_activity, '_', '$1', '_'}, [],
['$1']}])
of
{'EXIT', _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
+ Txt = <<"Database failure">>,
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)};
Vals ->
TimeStamp = p1_time_compat:system_time(seconds),
Str = list_to_binary(
@@ -196,7 +199,7 @@ process_get(#xmlel{name = <<"last">>, attrs = Attrs}) ->
end;
%%process_get({xmlelement, Name, Attrs, SubEls}) ->
%% {result, };
-process_get(_) -> {error, ?ERR_BAD_REQUEST}.
+process_get(_, _) -> {error, ?ERR_BAD_REQUEST}.
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(_) -> [iqdisc].
diff --git a/src/mod_disco.erl b/src/mod_disco.erl
index 6e0b9c92..0d5abcb4 100644
--- a/src/mod_disco.erl
+++ b/src/mod_disco.erl
@@ -150,9 +150,10 @@ process_local_iq_items(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
Host = To#jid.lserver,
case ejabberd_hooks:run_fold(disco_local_items, Host,
empty, [From, To, Node, Lang])
@@ -177,10 +178,11 @@ process_local_iq_info(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
Host = To#jid.lserver,
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
Identity = ejabberd_hooks:run_fold(disco_local_identity,
Host, [], [From, To, Node, Lang]),
Info = ejabberd_hooks:run_fold(disco_info, Host, [],
@@ -229,10 +231,12 @@ get_local_features(Acc, _From, To, <<>>, _Lang) ->
ets:select(disco_features,
[{{{'_', Host}}, [], ['$_']}])
++ Feats};
-get_local_features(Acc, _From, _To, _Node, _Lang) ->
+get_local_features(Acc, _From, _To, _Node, Lang) ->
case Acc of
{result, _Features} -> Acc;
- empty -> {error, ?ERR_ITEM_NOT_FOUND}
+ empty ->
+ Txt = <<"No features available">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}
end.
features_to_xml(FeatureList) ->
@@ -271,8 +275,8 @@ get_local_services(Acc, _From, To, <<>>, _Lang) ->
get_local_services({result, _} = Acc, _From, _To, _Node,
_Lang) ->
Acc;
-get_local_services(empty, _From, _To, _Node, _Lang) ->
- {error, ?ERR_ITEM_NOT_FOUND}.
+get_local_services(empty, _From, _To, _Node, Lang) ->
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, <<"No services available">>)}.
get_vh_services(Host) ->
Hosts = lists:sort(fun (H1, H2) ->
@@ -300,12 +304,13 @@ process_sm_iq_items(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
case is_presence_subscribed(From, To) of
true ->
Host = To#jid.lserver,
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
case ejabberd_hooks:run_fold(disco_sm_items, Host,
empty, [From, To, Node, Lang])
of
@@ -325,8 +330,9 @@ process_sm_iq_items(From, To,
IQ#iq{type = error, sub_el = [SubEl, Error]}
end;
false ->
+ Txt = <<"Not subscribed">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
+ sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]}
end
end.
@@ -347,12 +353,14 @@ get_sm_items(Acc, From,
get_sm_items({result, _} = Acc, _From, _To, _Node,
_Lang) ->
Acc;
-get_sm_items(empty, From, To, _Node, _Lang) ->
+get_sm_items(empty, From, To, _Node, Lang) ->
#jid{luser = LFrom, lserver = LSFrom} = From,
#jid{luser = LTo, lserver = LSTo} = To,
case {LFrom, LSFrom} of
{LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
- _ -> {error, ?ERR_NOT_ALLOWED}
+ _ ->
+ Txt = <<"Query to another users is forbidden">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
end.
is_presence_subscribed(#jid{luser = User, lserver = Server},
@@ -373,15 +381,18 @@ process_sm_iq_info(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
case is_presence_subscribed(From, To) of
true ->
Host = To#jid.lserver,
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
Identity = ejabberd_hooks:run_fold(disco_sm_identity,
Host, [],
[From, To, Node, Lang]),
+ Info = ejabberd_hooks:run_fold(disco_info, Host, [],
+ [From, To, Node, Lang]),
case ejabberd_hooks:run_fold(disco_sm_features, Host,
empty, [From, To, Node, Lang])
of
@@ -397,14 +408,15 @@ process_sm_iq_info(From, To,
[{<<"xmlns">>, ?NS_DISCO_INFO}
| ANode],
children =
- Identity ++
+ Identity ++ Info ++
features_to_xml(Features)}]};
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]}
end;
false ->
+ Txt = <<"Not subscribed">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
+ sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]}
end
end.
@@ -421,12 +433,14 @@ get_sm_identity(Acc, _From,
_ -> []
end.
-get_sm_features(empty, From, To, _Node, _Lang) ->
+get_sm_features(empty, From, To, _Node, Lang) ->
#jid{luser = LFrom, lserver = LSFrom} = From,
#jid{luser = LTo, lserver = LSTo} = To,
case {LFrom, LSFrom} of
{LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
- _ -> {error, ?ERR_NOT_ALLOWED}
+ _ ->
+ Txt = <<"Query to another users is forbidden">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
end;
get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
diff --git a/src/mod_echo.erl b/src/mod_echo.erl
index 3f91f1d0..7d9f81f8 100644
--- a/src/mod_echo.erl
+++ b/src/mod_echo.erl
@@ -86,7 +86,7 @@ stop(Host) ->
init([Host, Opts]) ->
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"echo.@HOST@">>),
- ejabberd_router:register_route(MyHost),
+ ejabberd_router:register_route(MyHost, Host),
{ok, #state{host = MyHost}}.
%%--------------------------------------------------------------------
@@ -118,7 +118,10 @@ handle_cast(_Msg, State) -> {noreply, State}.
handle_info({route, From, To, Packet}, State) ->
Packet2 = case From#jid.user of
<<"">> ->
- jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST);
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Txt = <<"User part of JID in 'from' is empty">>,
+ jlib:make_error_reply(
+ Packet, ?ERRT_BAD_REQUEST(Lang, Txt));
_ -> Packet
end,
do_client_version(disabled, To, From),
@@ -182,7 +185,7 @@ do_client_version(enabled, From, To) ->
Els = receive
{route, To, From2, IQ} ->
#xmlel{name = <<"query">>, children = List} =
- xml:get_subtag(IQ, <<"query">>),
+ fxml:get_subtag(IQ, <<"query">>),
List
after 5000 -> % Timeout in miliseconds: 5 seconds
[]
diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl
index 7f86d5cc..c4fae202 100644
--- a/src/mod_http_api.erl
+++ b/src/mod_http_api.erl
@@ -28,9 +28,14 @@
%% in ejabberd_http listener
%% request_handlers:
%% "/api": mod_http_api
-%%
+%%
+%% To use a specific API version N, add a vN element in the URL path:
+%% in ejabberd_http listener
+%% request_handlers:
+%% "/api/v2": mod_http_api
+%%
%% Access rights are defined with:
-%% commands_admin_access: configure
+%% commands_admin_access: configure
%% commands:
%% - add_commands: user
%%
@@ -43,6 +48,25 @@
%%
%% Then to perform an action, send a POST request to the following URL:
%% http://localhost:5280/api/<call_name>
+%%
+%% It's also possible to enable unrestricted access to some commands from group
+%% of IP addresses by using option `admin_ip_access` by having fragment like
+%% this in configuration file:
+%% modules:
+%% mod_http_api:
+%% admin_ip_access: admin_ip_access_rule
+%%...
+%% access:
+%% admin_ip_access_rule:
+%% admin_ip_acl:
+%% - command1
+%% - command2
+%% %% use `all` to give access to all commands
+%%...
+%% acl:
+%% admin_ip_acl:
+%% ip:
+%% - "127.0.0.1/8"
-module(mod_http_api).
@@ -57,6 +81,8 @@
-include("logger.hrl").
-include("ejabberd_http.hrl").
+-define(DEFAULT_API_VERSION, 0).
+
-define(CT_PLAIN,
{<<"Content-Type">>, <<"text/plain">>}).
@@ -97,60 +123,94 @@ start(_Host, _Opts) ->
stop(_Host) ->
ok.
-
%% ----------
%% basic auth
%% ----------
-check_permissions(#request{auth = HTTPAuth, headers = Headers}, Command)
- when HTTPAuth /= undefined ->
+check_permissions(Request, Command) ->
case catch binary_to_existing_atom(Command, utf8) of
Call when is_atom(Call) ->
- Admin =
- case lists:keysearch(<<"X-Admin">>, 1, Headers) of
- {value, {_, <<"true">>}} -> true;
- _ -> false
- end,
- Auth =
- case HTTPAuth of
- {SJID, Pass} ->
- case jid:from_string(SJID) of
- #jid{user = User, server = Server} ->
- case ejabberd_auth:check_password(User, Server, Pass) of
- true -> {ok, {User, Server, Pass, Admin}};
- false -> false
- end;
- _ ->
- false
- end;
- {oauth, Token, _} ->
- case ejabberd_oauth:check_token(Command, Token) of
- {ok, User, Server} ->
- {ok, {User, Server, {oauth, Token}, Admin}};
- false ->
- false
+ {ok, CommandPolicy} = ejabberd_commands:get_command_policy(Call),
+ check_permissions2(Request, Call, CommandPolicy);
+ _ ->
+ unauthorized_response()
+ end.
+
+check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _)
+ when HTTPAuth /= undefined ->
+ Admin =
+ case lists:keysearch(<<"X-Admin">>, 1, Headers) of
+ {value, {_, <<"true">>}} -> true;
+ _ -> false
+ end,
+ Auth =
+ case HTTPAuth of
+ {SJID, Pass} ->
+ case jid:from_string(SJID) of
+ #jid{user = User, server = Server} ->
+ case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
+ true -> {ok, {User, Server, Pass, Admin}};
+ false -> false
end;
_ ->
false
- end,
- case Auth of
- {ok, A} -> {allowed, Call, A};
+ end;
+ {oauth, Token, _} ->
+ case oauth_check_token(Call, Token) of
+ {ok, User, Server} ->
+ {ok, {User, Server, {oauth, Token}, Admin}};
+ false ->
+ false
+ end;
+ _ ->
+ false
+ end,
+ case Auth of
+ {ok, A} -> {allowed, Call, A};
+ _ -> unauthorized_response()
+ end;
+check_permissions2(_Request, Call, open) ->
+ {allowed, Call, noauth};
+check_permissions2(#request{ip={IP, _Port}}, Call, _Policy) ->
+ Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access,
+ mod_opt_type(admin_ip_access),
+ none),
+ Res = acl:match_rule(global, Access, IP),
+ case Res of
+ all ->
+ {allowed, Call, admin};
+ [all] ->
+ {allowed, Call, admin};
+ allow ->
+ {allowed, Call, admin};
+ Commands when is_list(Commands) ->
+ case lists:member(Call, Commands) of
+ true -> {allowed, Call, admin};
_ -> unauthorized_response()
end;
- _ ->
+ E ->
+ ?DEBUG("Unauthorized: ~p", [E]),
unauthorized_response()
end;
-check_permissions(_, _Command) ->
+check_permissions2(_Request, _Call, _Policy) ->
unauthorized_response().
+oauth_check_token(Scope, Token) when is_atom(Scope) ->
+ oauth_check_token(atom_to_binary(Scope, utf8), Token);
+oauth_check_token(Scope, Token) ->
+ ejabberd_oauth:check_token(Scope, Token).
+
%% ------------------
%% command processing
%% ------------------
+%process(Call, Request) ->
+% ?DEBUG("~p~n~p", [Call, Request]), ok;
process(_, #request{method = 'POST', data = <<>>}) ->
?DEBUG("Bad Request: no data", []),
- badrequest_response();
+ badrequest_response(<<"Missing POST data">>);
process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
+ Version = get_api_version(Req),
try
Args = case jiffy:decode(Data) of
List when is_list(List) -> List;
@@ -160,31 +220,37 @@ process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
log(Call, Args, IP),
case check_permissions(Req, Call) of
{allowed, Cmd, Auth} ->
- {Code, Result} = handle(Cmd, Auth, Args),
+ {Code, Result} = handle(Cmd, Auth, Args, Version),
json_response(Code, jiffy:encode(Result));
+ %% Warning: check_permission direcly formats 401 reply if not authorized
ErrorResponse ->
ErrorResponse
end
- catch _:Error ->
- ?DEBUG("Bad Request: ~p", [Error]),
- badrequest_response()
+ catch _:{error,{_,invalid_json}} = _Err ->
+ ?DEBUG("Bad Request: ~p", [_Err]),
+ badrequest_response(<<"Invalid JSON input">>);
+ _:_Error ->
+ ?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
+ badrequest_response()
end;
process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
+ Version = get_api_version(Req),
try
Args = case Data of
- [{nokey, <<>>}] -> [];
- _ -> Data
- end,
+ [{nokey, <<>>}] -> [];
+ _ -> Data
+ end,
log(Call, Args, IP),
case check_permissions(Req, Call) of
{allowed, Cmd, Auth} ->
- {Code, Result} = handle(Cmd, Auth, Args),
+ {Code, Result} = handle(Cmd, Auth, Args, Version),
json_response(Code, jiffy:encode(Result));
+ %% Warning: check_permission direcly formats 401 reply if not authorized
ErrorResponse ->
ErrorResponse
end
- catch _:Error ->
- ?DEBUG("Bad Request: ~p", [Error]),
+ catch _:_Error ->
+ ?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
badrequest_response()
end;
process([], #request{method = 'OPTIONS', data = <<>>}) ->
@@ -193,13 +259,28 @@ process(_Path, Request) ->
?DEBUG("Bad Request: no handler ~p", [Request]),
badrequest_response().
+% get API version N from last "vN" element in URL path
+get_api_version(#request{path = Path}) ->
+ get_api_version(lists:reverse(Path));
+get_api_version([<<"v", String/binary>> | Tail]) ->
+ case catch jlib:binary_to_integer(String) of
+ N when is_integer(N) ->
+ N;
+ _ ->
+ get_api_version(Tail)
+ end;
+get_api_version([_Head | Tail]) ->
+ get_api_version(Tail);
+get_api_version([]) ->
+ ?DEFAULT_API_VERSION.
+
%% ----------------
%% command handlers
%% ----------------
% generic ejabberd command handler
-handle(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
- case ejabberd_commands:get_command_format(Call, Auth) of
+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 = [{jlib:binary_to_atom(Key), Value} || {Key, Value} <- Args],
Spec = lists:foldr(
@@ -214,23 +295,48 @@ handle(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
({Key, atom}, Acc) ->
[{Key, undefined}|Acc]
end, [], ArgsSpec),
- handle2(Call, Auth, match(Args2, Spec));
+ try
+ handle2(Call, Auth, match(Args2, Spec), Version)
+ catch throw:not_found ->
+ {404, <<"not_found">>};
+ throw:{not_found, Why} when is_atom(Why) ->
+ {404, jlib:atom_to_binary(Why)};
+ throw:{not_found, Msg} ->
+ {404, iolist_to_binary(Msg)};
+ throw:not_allowed ->
+ {401, <<"not_allowed">>};
+ throw:{not_allowed, Why} when is_atom(Why) ->
+ {401, jlib:atom_to_binary(Why)};
+ throw:{not_allowed, Msg} ->
+ {401, iolist_to_binary(Msg)};
+ throw:{error, account_unprivileged} ->
+ {401, iolist_to_binary(<<"Unauthorized: Account Unpriviledged">>)};
+ throw:{invalid_parameter, Msg} ->
+ {400, iolist_to_binary(Msg)};
+ throw:{error, Why} when is_atom(Why) ->
+ {400, jlib:atom_to_binary(Why)};
+ throw:{error, Msg} ->
+ {400, iolist_to_binary(Msg)};
+ throw:Error when is_atom(Error) ->
+ {400, jlib:atom_to_binary(Error)};
+ throw:Msg when is_list(Msg); is_binary(Msg) ->
+ {400, iolist_to_binary(Msg)};
+ _Error ->
+ ?ERROR_MSG("REST API Error: ~p ~p", [_Error, erlang:get_stacktrace()]),
+ {500, <<"internal_error">>}
+ end;
{error, Msg} ->
+ ?ERROR_MSG("REST API Error: ~p", [Msg]),
{400, Msg};
_Error ->
+ ?ERROR_MSG("REST API Error: ~p", [_Error]),
{400, <<"Error">>}
end.
-handle2(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
- {ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth),
+handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
+ {ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth, Version),
ArgsFormatted = format_args(Args, ArgsF),
- case ejabberd_command(Auth, Call, ArgsFormatted, 400) of
- 0 -> {200, <<"OK">>};
- 1 -> {500, <<"500 Internal server error">>};
- 400 -> {400, <<"400 Bad Request">>};
- 404 -> {404, <<"404 Not found">>};
- Res -> format_command_result(Call, Auth, Res)
- end.
+ ejabberd_command(Auth, Call, ArgsFormatted, Version).
get_elem_delete(A, L) ->
case proplists:get_all_values(A, L) of
@@ -294,7 +400,9 @@ format_arg(undefined, binary) -> <<>>;
format_arg(undefined, string) -> <<>>;
format_arg(Arg, Format) ->
?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
- error.
+ throw({invalid_parameter,
+ io_lib:format("Arg ~p is not in format ~p",
+ [Arg, Format])}).
process_unicode_codepoints(Str) ->
iolist_to_binary(lists:map(fun(X) when X > 255 -> unicode:characters_to_binary([X]);
@@ -308,32 +416,37 @@ process_unicode_codepoints(Str) ->
match(Args, Spec) ->
[{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec].
-ejabberd_command(Auth, Cmd, Args, Default) ->
- case catch ejabberd_commands:execute_command(undefined, Auth, Cmd, Args) of
- {'EXIT', _} -> Default;
- {error, _} -> Default;
- Result -> Result
+ejabberd_command(Auth, Cmd, Args, Version) ->
+ Access = case Auth of
+ admin -> [];
+ _ -> undefined
+ end,
+ case ejabberd_commands:execute_command(Access, Auth, Cmd, Args, Version) of
+ {error, Error} ->
+ throw(Error);
+ Res ->
+ format_command_result(Cmd, Auth, Res, Version)
end.
-format_command_result(Cmd, Auth, Result) ->
- {_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth),
+format_command_result(Cmd, Auth, Result, Version) ->
+ {_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth, Version),
case {ResultFormat, Result} of
- {{_, rescode}, V} when V == true; V == ok ->
- {200, <<"">>};
- {{_, rescode}, _} ->
- {500, <<"">>};
- {{_, restuple}, {V1, Text1}} when V1 == true; V1 == ok ->
- {200, iolist_to_binary(Text1)};
- {{_, restuple}, {_, Text2}} ->
- {500, iolist_to_binary(Text2)};
- {{_, {list, _}}, _V} ->
- {_, L} = format_result(Result, ResultFormat),
- {200, L};
- {{_, {tuple, _}}, _V} ->
- {_, T} = format_result(Result, ResultFormat),
- {200, T};
- _ ->
- {200, {[format_result(Result, ResultFormat)]}}
+ {{_, rescode}, V} when V == true; V == ok ->
+ {200, 0};
+ {{_, rescode}, _} ->
+ {200, 1};
+ {{_, restuple}, {V1, Text1}} when V1 == true; V1 == ok ->
+ {200, iolist_to_binary(Text1)};
+ {{_, restuple}, {_, Text2}} ->
+ {500, iolist_to_binary(Text2)};
+ {{_, {list, _}}, _V} ->
+ {_, L} = format_result(Result, ResultFormat),
+ {200, L};
+ {{_, {tuple, _}}, _V} ->
+ {_, T} = format_result(Result, ResultFormat),
+ {200, T};
+ _ ->
+ {200, {[format_result(Result, ResultFormat)]}}
end.
format_result(Atom, {Name, atom}) ->
@@ -372,21 +485,24 @@ format_result(404, {_Name, _}) ->
"not_found".
unauthorized_response() ->
- {401, ?HEADER(?CT_XML),
- #xmlel{name = <<"h1">>, attrs = [],
- children = [{xmlcdata, <<"401 Unauthorized">>}]}}.
+ unauthorized_response(<<"401 Unauthorized">>).
+unauthorized_response(Body) ->
+ json_response(401, jiffy:encode(Body)).
badrequest_response() ->
- {400, ?HEADER(?CT_XML),
- #xmlel{name = <<"h1">>, attrs = [],
- children = [{xmlcdata, <<"400 Bad Request">>}]}}.
+ badrequest_response(<<"400 Bad Request">>).
+badrequest_response(Body) ->
+ json_response(400, jiffy:encode(Body)).
+
json_response(Code, Body) when is_integer(Code) ->
{Code, ?HEADER(?CT_JSON), Body}.
log(Call, Args, {Addr, Port}) ->
AddrS = jlib:ip_to_list({Addr, Port}),
- ?INFO_MSG("Admin call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]).
+ ?INFO_MSG("API call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]);
+log(Call, Args, IP) ->
+ ?INFO_MSG("API call ~s ~p (~p)", [Call, Args, IP]).
-mod_opt_type(access) ->
+mod_opt_type(admin_ip_access) ->
fun(Access) when is_atom(Access) -> Access end;
-mod_opt_type(_) -> [access].
+mod_opt_type(_) -> [admin_ip_access].
diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl
index 36d3626b..b01ca9d2 100644
--- a/src/mod_http_upload.erl
+++ b/src/mod_http_upload.erl
@@ -86,7 +86,8 @@
%% Utility functions.
-export([get_proc_name/2,
- expand_home/1]).
+ expand_home/1,
+ expand_host/2]).
-include("ejabberd.hrl").
-include("ejabberd_http.hrl").
@@ -273,6 +274,8 @@ init({ServerHost, Opts}) ->
Thumbnail = gen_mod:get_opt(thumbnail, Opts,
fun(B) when is_boolean(B) -> B end,
true),
+ DocRoot1 = expand_home(str:strip(DocRoot, right, $/)),
+ DocRoot2 = expand_host(DocRoot1, ServerHost),
case ServiceURL of
undefined ->
ok;
@@ -289,7 +292,7 @@ init({ServerHost, Opts}) ->
undefined ->
ok;
Mode ->
- file:change_mode(DocRoot, Mode)
+ file:change_mode(DocRoot2, Mode)
end,
case Thumbnail of
true ->
@@ -303,13 +306,13 @@ init({ServerHost, Opts}) ->
false ->
ok
end,
- ejabberd_router:register_route(Host),
+ ejabberd_router:register_route(Host, ServerHost),
{ok, #state{server_host = ServerHost, host = Host, name = Name,
access = Access, max_size = MaxSize,
secret_length = SecretLength, jid_in_url = JIDinURL,
file_mode = FileMode, dir_mode = DirMode,
thumbnail = Thumbnail,
- docroot = expand_home(str:strip(DocRoot, right, $/)),
+ docroot = DocRoot2,
put_url = expand_host(str:strip(PutURL, right, $/), ServerHost),
get_url = expand_host(str:strip(GetURL, right, $/), ServerHost),
service_url = ServiceURL}}.
@@ -372,7 +375,7 @@ handle_info(Info, State) ->
?ERROR_MSG("Got unexpected info: ~p", [Info]),
{noreply, State}.
--spec terminate(normal | shutdown | {shutdown, _} | _, _) -> ok.
+-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
terminate(Reason, #state{server_host = ServerHost, host = Host}) ->
?DEBUG("Stopping HTTP upload process for ~s: ~p", [ServerHost, Reason]),
@@ -509,6 +512,12 @@ expand_home(Subject) ->
Parts = binary:split(Subject, <<"@HOME@">>, [global]),
str:join(Parts, list_to_binary(Home)).
+-spec expand_host(binary(), binary()) -> binary().
+
+expand_host(Subject, Host) ->
+ Parts = binary:split(Subject, <<"@HOST@">>, [global]),
+ str:join(Parts, Host).
+
%%--------------------------------------------------------------------
%% Internal functions.
%%--------------------------------------------------------------------
@@ -526,7 +535,8 @@ process_iq(_From,
IQ#iq{type = result,
sub_el = [#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}],
- children = iq_disco_info(Lang, Name) ++ AddInfo}]};
+ children = iq_disco_info(ServerHost, Lang, Name)
+ ++ AddInfo}]};
process_iq(From,
#iq{type = get, xmlns = XMLNS, lang = Lang, sub_el = SubEl} = IQ,
#state{server_host = ServerHost, access = Access} = State)
@@ -559,7 +569,8 @@ process_iq(From,
deny ->
?DEBUG("Denying HTTP upload slot request from ~s",
[jid:to_string(From)]),
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ Txt = <<"Denied by ACL">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
end;
process_iq(_From, #iq{sub_el = SubEl} = IQ, _State) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
@@ -572,12 +583,12 @@ process_iq(_From, invalid, _State) ->
-> {ok, binary(), pos_integer(), binary()} | {error, xmlel()}.
parse_request(#xmlel{name = <<"request">>, attrs = Attrs} = Request, Lang) ->
- case xml:get_attr(<<"xmlns">>, Attrs) of
+ case fxml:get_attr(<<"xmlns">>, Attrs) of
{value, XMLNS} when XMLNS == ?NS_HTTP_UPLOAD;
XMLNS == ?NS_HTTP_UPLOAD_OLD ->
- case {xml:get_subtag_cdata(Request, <<"filename">>),
- xml:get_subtag_cdata(Request, <<"size">>),
- xml:get_subtag_cdata(Request, <<"content-type">>)} of
+ case {fxml:get_subtag_cdata(Request, <<"filename">>),
+ fxml:get_subtag_cdata(Request, <<"size">>),
+ fxml:get_subtag_cdata(Request, <<"content-type">>)} of
{File, SizeStr, ContentType} when byte_size(File) > 0 ->
case catch jlib:binary_to_integer(SizeStr) of
Size when is_integer(Size), Size > 0 ->
@@ -591,7 +602,8 @@ parse_request(#xmlel{name = <<"request">>, attrs = Attrs} = Request, Lang) ->
{error, ?ERRT_BAD_REQUEST(Lang, Text)}
end;
_ ->
- {error, ?ERR_BAD_REQUEST}
+ Text = <<"No or invalid XML namespace">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Text)}
end;
parse_request(_El, _Lang) -> {error, ?ERR_BAD_REQUEST}.
@@ -629,7 +641,7 @@ create_slot(#state{service_url = undefined,
end;
create_slot(#state{service_url = ServiceURL},
#jid{luser = U, lserver = S} = JID, File, Size, ContentType,
- _Lang) ->
+ Lang) ->
Options = [{body_format, binary}, {full_result, false}],
HttpOptions = [{timeout, ?SERVICE_REQUEST_TIMEOUT}],
SizeStr = jlib:integer_to_binary(Size),
@@ -649,7 +661,8 @@ create_slot(#state{service_url = ServiceURL},
Lines ->
?ERROR_MSG("Can't parse data received for ~s from <~s>: ~p",
[jid:to_string(JID), ServiceURL, Lines]),
- {error, ?ERR_SERVICE_UNAVAILABLE}
+ Txt = <<"Failed to parse HTTP response">>,
+ {error, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)}
end;
{ok, {402, _Body}} ->
?INFO_MSG("Got status code 402 for ~s from <~s>",
@@ -737,20 +750,41 @@ map_int_to_char(N) when N =< 9 -> N + 48; % Digit.
map_int_to_char(N) when N =< 35 -> N + 55; % Upper-case character.
map_int_to_char(N) when N =< 61 -> N + 61. % Lower-case character.
--spec expand_host(binary(), binary()) -> binary().
-
-expand_host(Subject, Host) ->
- Parts = binary:split(Subject, <<"@HOST@">>, [global]),
- str:join(Parts, Host).
-
-spec yield_content_type(binary()) -> binary().
yield_content_type(<<"">>) -> ?DEFAULT_CONTENT_TYPE;
yield_content_type(Type) -> Type.
--spec iq_disco_info(binary(), binary()) -> [xmlel()].
-
-iq_disco_info(Lang, Name) ->
+-spec iq_disco_info(binary(), binary(), binary()) -> [xmlel()].
+
+iq_disco_info(Host, Lang, Name) ->
+ Form = case gen_mod:get_module_opt(Host, ?MODULE, max_size,
+ fun(I) when is_integer(I), I > 0 -> I;
+ (infinity) -> infinity
+ end,
+ 104857600) of
+ infinity ->
+ [];
+ MaxSize ->
+ MaxSizeStr = jlib:integer_to_binary(MaxSize),
+ Fields = [#xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, <<"hidden">>},
+ {<<"var">>, <<"FORM_TYPE">>}],
+ children = [#xmlel{name = <<"value">>,
+ children =
+ [{xmlcdata,
+ ?NS_HTTP_UPLOAD}]}]},
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"var">>, <<"max-file-size">>}],
+ children = [#xmlel{name = <<"value">>,
+ children =
+ [{xmlcdata,
+ MaxSizeStr}]}]}],
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"result">>}],
+ children = Fields}]
+ end,
[#xmlel{name = <<"identity">>,
attrs = [{<<"category">>, <<"store">>},
{<<"type">>, <<"file">>},
@@ -758,7 +792,7 @@ iq_disco_info(Lang, Name) ->
#xmlel{name = <<"feature">>,
attrs = [{<<"var">>, ?NS_HTTP_UPLOAD}]},
#xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_HTTP_UPLOAD_OLD}]}].
+ attrs = [{<<"var">>, ?NS_HTTP_UPLOAD_OLD}]} | Form].
%% HTTP request handling.
@@ -796,7 +830,7 @@ store_file(Path, Data, FileMode, DirMode, GetPrefix, Slot, Thumbnail) ->
{ok,
[{<<"Content-Type">>,
<<"text/xml; charset=utf-8">>}],
- xml:element_to_binary(ThumbEl)};
+ fxml:element_to_binary(ThumbEl)};
pass ->
ok
end;
@@ -974,8 +1008,9 @@ remove_user(User, Server) ->
(node) -> node
end,
sha1),
+ DocRoot1 = expand_host(expand_home(DocRoot), ServerHost),
UserStr = make_user_string(jid:make(User, Server, <<"">>), JIDinURL),
- UserDir = str:join([expand_home(DocRoot), UserStr], <<$/>>),
+ UserDir = str:join([DocRoot1, UserStr], <<$/>>),
case del_tree(UserDir) of
ok ->
?INFO_MSG("Removed HTTP upload directory of ~s@~s", [User, Server]);
diff --git a/src/mod_http_upload_quota.erl b/src/mod_http_upload_quota.erl
index db0b4aa4..4f9d9521 100644
--- a/src/mod_http_upload_quota.erl
+++ b/src/mod_http_upload_quota.erl
@@ -132,6 +132,7 @@ init({ServerHost, Opts}) ->
fun iolist_to_binary/1,
<<"@HOME@/upload">>),
DocRoot2 = mod_http_upload:expand_home(str:strip(DocRoot1, right, $/)),
+ DocRoot3 = mod_http_upload:expand_host(DocRoot2, ServerHost),
Timers = if MaxDays == infinity -> [];
true ->
{ok, T1} = timer:send_after(?INITIAL_TIMEOUT, sweep),
@@ -144,7 +145,7 @@ init({ServerHost, Opts}) ->
access_soft_quota = AccessSoftQuota,
access_hard_quota = AccessHardQuota,
max_days = MaxDays,
- docroot = DocRoot2,
+ docroot = DocRoot3,
timers = Timers}}.
-spec handle_call(_, {pid(), _}, state()) -> {noreply, state()}.
@@ -238,7 +239,7 @@ handle_info(Info, State) ->
?ERROR_MSG("Got unexpected info: ~p", [Info]),
{noreply, State}.
--spec terminate(normal | shutdown | {shutdown, _} | _, _) -> ok.
+-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
terminate(Reason, #state{server_host = ServerHost, timers = Timers}) ->
?DEBUG("Stopping upload quota process for ~s: ~p", [ServerHost, Reason]),
diff --git a/src/mod_irc.erl b/src/mod_irc.erl
index 18308895..e0c658de 100644
--- a/src/mod_irc.erl
+++ b/src/mod_irc.erl
@@ -33,7 +33,8 @@
%% API
-export([start_link/2, start/2, stop/1, export/1, import/1,
- import/3, closed_connection/3, get_connection_params/3]).
+ import/3, closed_connection/3, get_connection_params/3,
+ data_to_binary/2]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
@@ -46,6 +47,8 @@
-include("adhoc.hrl").
+-include("mod_irc.hrl").
+
-define(DEFAULT_IRC_ENCODING, <<"iso8859-15">>).
-define(DEFAULT_IRC_PORT, 6667).
@@ -58,27 +61,19 @@
[<<"koi8-r">>, <<"iso8859-15">>, <<"iso8859-1">>, <<"iso8859-2">>,
<<"utf-8">>, <<"utf-8+latin-1">>]).
--type conn_param() :: {binary(), binary(), inet:port_number(), binary()} |
- {binary(), binary(), inet:port_number()} |
- {binary(), binary()} |
- {binary()}.
-
--record(irc_connection,
- {jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()},
- pid = self() :: pid()}).
-
--record(irc_custom,
- {us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()},
- binary()},
- data = [] :: [{username, binary()} |
- {connections_params, [conn_param()]}]}).
-
-record(state, {host = <<"">> :: binary(),
server_host = <<"">> :: binary(),
access = all :: atom()}).
-define(PROCNAME, ejabberd_mod_irc).
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #irc_custom{}) -> ok | pass.
+-callback get_data(binary(), binary(), {binary(), binary()}) ->
+ error | empty | irc_data().
+-callback set_data(binary(), binary(), {binary(), binary()}, irc_data()) ->
+ {atomic, any()}.
+
%%====================================================================
%% API
%%====================================================================
@@ -116,24 +111,18 @@ stop(Host) ->
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Host, Opts]) ->
- ejabberd:start_app(p1_iconv),
+ ejabberd:start_app(iconv),
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"irc.@HOST@">>),
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(irc_custom,
- [{disc_copies, [node()]},
- {attributes, record_info(fields, irc_custom)}]),
- update_table();
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
Access = gen_mod:get_opt(access, Opts,
fun(A) when is_atom(A) -> A end,
all),
catch ets:new(irc_connection,
[named_table, public,
{keypos, #irc_connection.jid_server_host}]),
- ejabberd_router:register_route(MyHost),
+ ejabberd_router:register_route(MyHost, Host),
{ok,
#state{host = MyHost, server_host = Host,
access = Access}}.
@@ -216,7 +205,7 @@ do_route(Host, ServerHost, Access, From, To, Packet) ->
allow -> do_route1(Host, ServerHost, From, To, Packet);
_ ->
#xmlel{attrs = Attrs} = Packet,
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText = <<"Access denied by service policy">>,
Err = jlib:make_error_reply(Packet,
?ERRT_FORBIDDEN(Lang, ErrText)),
@@ -234,7 +223,7 @@ do_route1(Host, ServerHost, From, To, Packet) ->
#iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS,
sub_el = SubEl, lang = Lang} =
IQ ->
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
[],
[ServerHost, ?MODULE,
@@ -262,7 +251,7 @@ do_route1(Host, ServerHost, From, To, Packet) ->
#iq{type = get, xmlns = (?NS_DISCO_ITEMS) = XMLNS,
sub_el = SubEl, lang = Lang} =
IQ ->
- Node = xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
case Node of
<<>> ->
ResIQ = IQ#iq{type = result,
@@ -304,8 +293,9 @@ do_route1(Host, ServerHost, From, To, Packet) ->
Lang)}]},
Res = jlib:iq_to_xml(ResIQ);
_ ->
- Res = jlib:make_error_reply(Packet,
- ?ERR_ITEM_NOT_FOUND)
+ Txt = <<"Node not found">>,
+ Res = jlib:make_error_reply(
+ Packet, ?ERRT_ITEM_NOT_FOUND(Lang, Txt))
end,
ejabberd_router:route(To, From, Res);
#iq{xmlns = ?NS_REGISTER} = IQ ->
@@ -319,7 +309,7 @@ do_route1(Host, ServerHost, From, To, Packet) ->
attrs = [{<<"xmlns">>, XMLNS}],
children = iq_get_vcard(Lang)}]},
ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
- #iq{type = set, xmlns = ?NS_COMMANDS, lang = _Lang,
+ #iq{type = set, xmlns = ?NS_COMMANDS, lang = Lang,
sub_el = SubEl} =
IQ ->
Request = adhoc:parse_request(IQ),
@@ -348,8 +338,9 @@ do_route1(Host, ServerHost, From, To, Packet) ->
true -> ok
end;
_ ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_ITEM_NOT_FOUND),
+ Txt = <<"Node not found">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end;
#iq{} = _IQ ->
@@ -407,12 +398,14 @@ do_route1(Host, ServerHost, From, To, Packet) ->
ok
end;
_ ->
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
case str:tokens(ChanServ, <<"!">>) of
[<<_, _/binary>> = Nick, <<_, _/binary>> = Server] ->
case ets:lookup(irc_connection, {From, Server, Host}) of
[] ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_SERVICE_UNAVAILABLE),
+ Txt = <<"IRC connection not found">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err);
[R] ->
Pid = R#irc_connection.pid,
@@ -421,7 +414,9 @@ do_route1(Host, ServerHost, From, To, Packet) ->
ok
end;
_ ->
- Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
+ Txt = <<"Failed to parse chanserv">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end
end
@@ -516,7 +511,7 @@ find_xdata_el1([]) -> false;
find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
children = SubEls}
| Els]) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
#xmlel{name = Name, attrs = Attrs, children = SubEls};
_ -> find_xdata_el1(Els)
@@ -532,10 +527,11 @@ process_irc_register(ServerHost, Host, From, _To,
XDataEl = find_xdata_el(SubEl),
case XDataEl of
false ->
+ Txt1 = <<"No data form found">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]};
+ sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, Txt1)]};
#xmlel{attrs = Attrs} ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"cancel">> ->
IQ#iq{type = result,
sub_el =
@@ -546,10 +542,11 @@ process_irc_register(ServerHost, Host, From, _To,
XData = jlib:parse_xdata_submit(XDataEl),
case XData of
invalid ->
+ Txt2 = <<"Incorrect data form">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_BAD_REQUEST]};
+ sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt2)]};
_ ->
- Node = str:tokens(xml:get_tag_attr_s(<<"node">>,
+ Node = str:tokens(fxml:get_tag_attr_s(<<"node">>,
SubEl),
<<"/">>),
case set_form(ServerHost, Host, From, Node, Lang,
@@ -567,11 +564,13 @@ process_irc_register(ServerHost, Host, From, _To,
end
end;
_ ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ Txt3 = <<"Incorrect value of 'type' attribute">>,
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt3)]}
end
end;
get ->
- Node = str:tokens(xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, SubEl),
<<"/">>),
case get_form(ServerHost, Host, From, Node, Lang) of
{result, Res} ->
@@ -587,49 +586,16 @@ process_irc_register(ServerHost, Host, From, _To,
get_data(ServerHost, Host, From) ->
LServer = jid:nameprep(ServerHost),
- get_data(LServer, Host, From,
- gen_mod:db_type(LServer, ?MODULE)).
-
-get_data(_LServer, Host, From, mnesia) ->
- #jid{luser = LUser, lserver = LServer} = From,
- US = {LUser, LServer},
- case catch mnesia:dirty_read({irc_custom, {US, Host}})
- of
- {'EXIT', _Reason} -> error;
- [] -> empty;
- [#irc_custom{data = Data}] -> Data
- end;
-get_data(LServer, Host, From, riak) ->
- #jid{luser = LUser, lserver = LServer} = From,
- US = {LUser, LServer},
- case ejabberd_riak:get(irc_custom, irc_custom_schema(), {US, Host}) of
- {ok, #irc_custom{data = Data}} ->
- Data;
- {error, notfound} ->
- empty;
- _Err ->
- error
- end;
-get_data(LServer, Host, From, odbc) ->
- SJID =
- ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
- SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select data from irc_custom where jid='">>,
- SJID, <<"' and host='">>, SHost,
- <<"';">>])
- of
- {selected, [<<"data">>], [[SData]]} ->
- data_to_binary(From, ejabberd_odbc:decode_term(SData));
- {'EXIT', _} -> error;
- {selected, _, _} -> empty
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_data(LServer, Host, From).
get_form(ServerHost, Host, From, [], Lang) ->
#jid{user = User, server = Server} = From,
DefaultEncoding = get_default_encoding(Host),
Customs = case get_data(ServerHost, Host, From) of
- error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+ error ->
+ Txt1 = <<"Database failure">>,
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt1)};
empty -> {User, []};
Data -> get_username_and_connection_params(Data)
end,
@@ -731,39 +697,10 @@ get_form(_ServerHost, _Host, _, _, _Lang) ->
set_data(ServerHost, Host, From, Data) ->
LServer = jid:nameprep(ServerHost),
- set_data(LServer, Host, From, data_to_binary(From, Data),
- gen_mod:db_type(LServer, ?MODULE)).
-
-set_data(_LServer, Host, From, Data, mnesia) ->
- {LUser, LServer, _} = jid:tolower(From),
- US = {LUser, LServer},
- F = fun () ->
- mnesia:write(#irc_custom{us_host = {US, Host},
- data = Data})
- end,
- mnesia:transaction(F);
-set_data(LServer, Host, From, Data, riak) ->
- {LUser, LServer, _} = jid:tolower(From),
- US = {LUser, LServer},
- {atomic, ejabberd_riak:put(#irc_custom{us_host = {US, Host},
- data = Data},
- irc_custom_schema())};
-set_data(LServer, Host, From, Data, odbc) ->
- SJID =
- ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
- SHost = ejabberd_odbc:escape(Host),
- SData = ejabberd_odbc:encode_term(Data),
- F = fun () ->
- odbc_queries:update_t(<<"irc_custom">>,
- [<<"jid">>, <<"host">>, <<"data">>],
- [SJID, SHost, SData],
- [<<"jid='">>, SJID, <<"' and host='">>,
- SHost, <<"'">>]),
- ok
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
-
-set_form(ServerHost, Host, From, [], _Lang, XData) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:set_data(LServer, Host, From, data_to_binary(From, Data)).
+
+set_form(ServerHost, Host, From, [], Lang, XData) ->
case {lists:keysearch(<<"username">>, 1, XData),
lists:keysearch(<<"connections_params">>, 1, XData)}
of
@@ -781,11 +718,11 @@ set_form(ServerHost, Host, From, [], _Lang, XData) ->
{connections_params, ConnectionsParams}])
of
{atomic, _} -> {result, []};
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Database failure">>)}
end;
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Parse error">>)}
end;
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Scan error">>)}
end;
_ -> {error, ?ERR_NOT_ACCEPTABLE}
end;
@@ -909,7 +846,9 @@ adhoc_join(From, To,
elements = [Form]});
true ->
case jlib:parse_xdata_submit(XData) of
- invalid -> {error, ?ERR_BAD_REQUEST};
+ invalid ->
+ Txt1 = <<"Incorrect data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt1)};
Fields ->
Channel = case lists:keysearch(<<"channel">>, 1, Fields)
of
@@ -998,7 +937,8 @@ adhoc_register(ServerHost, From, To,
true ->
case jlib:parse_xdata_submit(XData) of
invalid ->
- Error = {error, ?ERR_BAD_REQUEST},
+ Txt1 = <<"Incorrect data form">>,
+ Error = {error, ?ERRT_BAD_REQUEST(Lang, Txt1)},
Username = false,
ConnectionsParams = false;
Fields ->
@@ -1021,7 +961,9 @@ adhoc_register(ServerHost, From, To,
{atomic, _} ->
adhoc:produce_response(Request,
#adhoc_response{status = completed});
- _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
+ _ ->
+ Txt2 = <<"Database failure">>,
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt2)}
end;
true ->
Form = generate_adhoc_register_form(Lang, Username,
@@ -1297,66 +1239,17 @@ conn_params_to_list(Params) ->
Port, binary_to_list(P)}
end, Params).
-irc_custom_schema() ->
- {record_info(fields, irc_custom), #irc_custom{}}.
-
-update_table() ->
- Fields = record_info(fields, irc_custom),
- case mnesia:table_info(irc_custom, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- irc_custom, Fields, set,
- fun(#irc_custom{us_host = {_, H}}) -> H end,
- fun(#irc_custom{us_host = {{U, S}, H},
- data = Data} = R) ->
- JID = jid:make(U, S, <<"">>),
- R#irc_custom{us_host = {{iolist_to_binary(U),
- iolist_to_binary(S)},
- iolist_to_binary(H)},
- data = data_to_binary(JID, Data)}
- end);
- _ ->
- ?INFO_MSG("Recreating irc_custom table", []),
- mnesia:transform_table(irc_custom, ignore, Fields)
- end.
-
-export(_Server) ->
- [{irc_custom,
- fun(Host, #irc_custom{us_host = {{U, S}, IRCHost},
- data = Data}) ->
- case str:suffix(Host, IRCHost) of
- true ->
- SJID = ejabberd_odbc:escape(
- jid:to_string(
- jid:make(U, S, <<"">>))),
- SIRCHost = ejabberd_odbc:escape(IRCHost),
- SData = ejabberd_odbc:encode_term(Data),
- [[<<"delete from irc_custom where jid='">>, SJID,
- <<"' and host='">>, SIRCHost, <<"';">>],
- [<<"insert into irc_custom(jid, host, "
- "data) values ('">>,
- SJID, <<"', '">>, SIRCHost, <<"', '">>, SData,
- <<"');">>]];
- false ->
- []
- end
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
-import(_LServer) ->
- [{<<"select jid, host, data from irc_custom;">>,
- fun([SJID, IRCHost, SData]) ->
- #jid{luser = U, lserver = S} = jid:from_string(SJID),
- Data = ejabberd_odbc:decode_term(SData),
- #irc_custom{us_host = {{U, S}, IRCHost},
- data = Data}
- end}].
+import(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
-import(_LServer, mnesia, #irc_custom{} = R) ->
- mnesia:dirty_write(R);
-import(_LServer, riak, #irc_custom{} = R) ->
- ejabberd_riak:put(R, irc_custom_schema());
-import(_, _, _) ->
- pass.
+import(LServer, DBType, Data) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, Data).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
@@ -1368,7 +1261,7 @@ mod_opt_type(_) ->
[access, db_type, default_encoding, host].
extract_ident(Packet) ->
- case xml:get_subtag(Packet, <<"headers">>) of
+ case fxml:get_subtag(Packet, <<"headers">>) of
{xmlel, _Name, _Attrs, Headers} ->
extract_header(<<"X-Irc-Ident">>, Headers);
_ ->
@@ -1376,7 +1269,7 @@ extract_ident(Packet) ->
end.
extract_ip_address(Packet) ->
- case xml:get_subtag(Packet, <<"headers">>) of
+ case fxml:get_subtag(Packet, <<"headers">>) of
{xmlel, _Name, _Attrs, Headers} ->
extract_header(<<"X-Forwarded-For">>, Headers);
_ ->
@@ -1384,7 +1277,7 @@ extract_ip_address(Packet) ->
end.
extract_header(HeaderName, [{xmlel, _Name, _Attrs, [{xmlcdata, Value}]} | Tail]) ->
- case xml:get_attr(<<"name">>, _Attrs) of
+ case fxml:get_attr(<<"name">>, _Attrs) of
{value, HeaderName} ->
binary_to_list(Value);
_ ->
diff --git a/src/mod_irc_connection.erl b/src/mod_irc_connection.erl
index ae709dc2..098c8c28 100644
--- a/src/mod_irc_connection.erl
+++ b/src/mod_irc_connection.erl
@@ -233,7 +233,7 @@ get_password_from_presence(#xmlel{name = <<"presence">>,
case lists:filter(fun (El) ->
case El of
#xmlel{name = <<"x">>, attrs = Attrs} ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC -> true;
_ -> false
end;
@@ -243,9 +243,9 @@ get_password_from_presence(#xmlel{name = <<"presence">>,
Els)
of
[ElXMUC | _] ->
- case xml:get_subtag(ElXMUC, <<"password">>) of
+ case fxml:get_subtag(ElXMUC, <<"password">>) of
#xmlel{name = <<"password">>} = PasswordTag ->
- {true, xml:get_tag_cdata(PasswordTag)};
+ {true, fxml:get_tag_cdata(PasswordTag)};
_ -> false
end;
_ -> false
@@ -261,7 +261,7 @@ handle_info({route_chan, Channel, Resource,
#xmlel{name = <<"presence">>, attrs = Attrs} =
Presence},
StateName, StateData) ->
- NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of
+ NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of
<<"unavailable">> ->
send_stanza_unavailable(Channel, StateData),
S1 = (?SEND((io_lib:format("PART #~s\r\n",
@@ -312,9 +312,9 @@ handle_info({route_chan, Channel, Resource,
handle_info({route_chan, Channel, Resource,
#xmlel{name = <<"message">>, attrs = Attrs} = El},
StateName, StateData) ->
- NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of
+ NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of
<<"groupchat">> ->
- case xml:get_path_s(El, [{elem, <<"subject">>}, cdata])
+ case fxml:get_path_s(El, [{elem, <<"subject">>}, cdata])
of
<<"">> ->
ejabberd_router:route(
@@ -325,7 +325,7 @@ handle_info({route_chan, Channel, Resource,
StateData#state.host,
StateData#state.nick),
StateData#state.user, El),
- Body = xml:get_path_s(El,
+ Body = fxml:get_path_s(El,
[{elem, <<"body">>},
cdata]),
case Body of
@@ -386,7 +386,7 @@ handle_info({route_chan, Channel, Resource,
when Type == <<"chat">>;
Type == <<"">>;
Type == <<"normal">> ->
- Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]),
+ Body = fxml:get_path_s(El, [{elem, <<"body">>}, cdata]),
case Body of
<<"/quote ", Rest/binary>> ->
?SEND(<<Rest/binary, "\r\n">>);
@@ -481,9 +481,9 @@ handle_info({route_chan, _Channel, _Resource, _Packet},
handle_info({route_nick, Nick,
#xmlel{name = <<"message">>, attrs = Attrs} = El},
StateName, StateData) ->
- NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of
+ NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of
<<"chat">> ->
- Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]),
+ Body = fxml:get_path_s(El, [{elem, <<"body">>}, cdata]),
case Body of
<<"/quote ", Rest/binary>> ->
?SEND(<<Rest/binary, "\r\n">>);
@@ -778,13 +778,13 @@ bounce_messages(Reason) ->
receive
{send_element, El} ->
#xmlel{attrs = Attrs} = El,
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
_ ->
Err = jlib:make_error_reply(El, <<"502">>, Reason),
- From = jid:from_string(xml:get_attr_s(<<"from">>,
+ From = jid:from_string(fxml:get_attr_s(<<"from">>,
Attrs)),
- To = jid:from_string(xml:get_attr_s(<<"to">>,
+ To = jid:from_string(fxml:get_attr_s(<<"to">>,
Attrs)),
ejabberd_router:route(To, From, Err)
end,
@@ -1535,14 +1535,14 @@ iq_admin(StateData, Channel, From, To,
end.
process_iq_admin(StateData, Channel, set, SubEl) ->
- case xml:get_subtag(SubEl, <<"item">>) of
+ case fxml:get_subtag(SubEl, <<"item">>) of
false -> {error, ?ERR_BAD_REQUEST};
ItemEl ->
- Nick = xml:get_tag_attr_s(<<"nick">>, ItemEl),
- Affiliation = xml:get_tag_attr_s(<<"affiliation">>,
+ Nick = fxml:get_tag_attr_s(<<"nick">>, ItemEl),
+ Affiliation = fxml:get_tag_attr_s(<<"affiliation">>,
ItemEl),
- Role = xml:get_tag_attr_s(<<"role">>, ItemEl),
- Reason = xml:get_path_s(ItemEl,
+ Role = fxml:get_tag_attr_s(<<"role">>, ItemEl),
+ Reason = fxml:get_path_s(ItemEl,
[{elem, <<"reason">>}, cdata]),
process_admin(StateData, Channel, Nick, Affiliation,
Role, Reason)
diff --git a/src/mod_irc_mnesia.erl b/src/mod_irc_mnesia.erl
new file mode 100644
index 00000000..9f8117ad
--- /dev/null
+++ b/src/mod_irc_mnesia.erl
@@ -0,0 +1,69 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_irc_mnesia).
+
+-behaviour(mod_irc).
+
+%% API
+-export([init/2, get_data/3, set_data/4, import/2]).
+
+-include("jlib.hrl").
+-include("mod_irc.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(irc_custom,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, irc_custom)}]),
+ update_table().
+
+get_data(_LServer, Host, From) ->
+ {U, S, _} = jid:tolower(From),
+ case catch mnesia:dirty_read({irc_custom, {{U, S}, Host}}) of
+ {'EXIT', _Reason} -> error;
+ [] -> empty;
+ [#irc_custom{data = Data}] -> Data
+ end.
+
+set_data(_LServer, Host, From, Data) ->
+ {U, S, _} = jid:tolower(From),
+ F = fun () ->
+ mnesia:write(#irc_custom{us_host = {{U, S}, Host},
+ data = Data})
+ end,
+ mnesia:transaction(F).
+
+import(_LServer, #irc_custom{} = R) ->
+ mnesia:dirty_write(R).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_table() ->
+ Fields = record_info(fields, irc_custom),
+ case mnesia:table_info(irc_custom, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ irc_custom, Fields, set,
+ fun(#irc_custom{us_host = {_, H}}) -> H end,
+ fun(#irc_custom{us_host = {{U, S}, H},
+ data = Data} = R) ->
+ JID = jid:make(U, S, <<"">>),
+ R#irc_custom{us_host = {{iolist_to_binary(U),
+ iolist_to_binary(S)},
+ iolist_to_binary(H)},
+ data = mod_irc:data_to_binary(JID, Data)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating irc_custom table", []),
+ mnesia:transform_table(irc_custom, ignore, Fields)
+ end.
diff --git a/src/mod_irc_riak.erl b/src/mod_irc_riak.erl
new file mode 100644
index 00000000..6ac7befd
--- /dev/null
+++ b/src/mod_irc_riak.erl
@@ -0,0 +1,49 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_irc_riak).
+
+-behaviour(mod_irc).
+
+%% API
+-export([init/2, get_data/3, set_data/4, import/2]).
+
+-include("jlib.hrl").
+-include("mod_irc.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+get_data(_LServer, Host, From) ->
+ {U, S, _} = jid:tolower(From),
+ case ejabberd_riak:get(irc_custom, irc_custom_schema(), {{U, S}, Host}) of
+ {ok, #irc_custom{data = Data}} ->
+ Data;
+ {error, notfound} ->
+ empty;
+ _Err ->
+ error
+ end.
+
+set_data(_LServer, Host, From, Data) ->
+ {U, S, _} = jid:tolower(From),
+ {atomic, ejabberd_riak:put(#irc_custom{us_host = {{U, S}, Host},
+ data = Data},
+ irc_custom_schema())}.
+
+import(_LServer, #irc_custom{} = R) ->
+ ejabberd_riak:put(R, irc_custom_schema()).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+irc_custom_schema() ->
+ {record_info(fields, irc_custom), #irc_custom{}}.
diff --git a/src/mod_irc_sql.erl b/src/mod_irc_sql.erl
new file mode 100644
index 00000000..9a97d572
--- /dev/null
+++ b/src/mod_irc_sql.erl
@@ -0,0 +1,91 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_irc_sql).
+
+-behaviour(mod_irc).
+
+%% API
+-export([init/2, get_data/3, set_data/4, import/1, import/2, export/1]).
+
+-include("jlib.hrl").
+-include("mod_irc.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+get_data(LServer, Host, From) ->
+ LJID = jid:tolower(jid:remove_resource(From)),
+ SJID = ejabberd_sql:escape(jid:to_string(LJID)),
+ SHost = ejabberd_sql:escape(Host),
+ case catch ejabberd_sql:sql_query(
+ LServer,
+ [<<"select data from irc_custom where jid='">>,
+ SJID, <<"' and host='">>, SHost,
+ <<"';">>]) of
+ {selected, [<<"data">>], [[SData]]} ->
+ mod_irc:data_to_binary(From, ejabberd_sql:decode_term(SData));
+ {'EXIT', _} -> error;
+ {selected, _, _} -> empty
+ end.
+
+set_data(LServer, Host, From, Data) ->
+ LJID = jid:tolower(jid:remove_resource(From)),
+ SJID = ejabberd_sql:escape(jid:to_string(LJID)),
+ SHost = ejabberd_sql:escape(Host),
+ SData = ejabberd_sql:encode_term(Data),
+ F = fun () ->
+ sql_queries:update_t(<<"irc_custom">>,
+ [<<"jid">>, <<"host">>, <<"data">>],
+ [SJID, SHost, SData],
+ [<<"jid='">>, SJID, <<"' and host='">>,
+ SHost, <<"'">>]),
+ ok
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+export(_Server) ->
+ [{irc_custom,
+ fun(Host, #irc_custom{us_host = {{U, S}, IRCHost},
+ data = Data}) ->
+ case str:suffix(Host, IRCHost) of
+ true ->
+ SJID = ejabberd_sql:escape(
+ jid:to_string(
+ jid:make(U, S, <<"">>))),
+ SIRCHost = ejabberd_sql:escape(IRCHost),
+ SData = ejabberd_sql:encode_term(Data),
+ [[<<"delete from irc_custom where jid='">>, SJID,
+ <<"' and host='">>, SIRCHost, <<"';">>],
+ [<<"insert into irc_custom(jid, host, "
+ "data) values ('">>,
+ SJID, <<"', '">>, SIRCHost, <<"', '">>, SData,
+ <<"');">>]];
+ false ->
+ []
+ end
+ end}].
+
+import(_LServer) ->
+ [{<<"select jid, host, data from irc_custom;">>,
+ fun([SJID, IRCHost, SData]) ->
+ #jid{luser = U, lserver = S} = jid:from_string(SJID),
+ Data = ejabberd_sql:decode_term(SData),
+ #irc_custom{us_host = {{U, S}, IRCHost},
+ data = Data}
+ end}].
+
+import(_, _) ->
+ pass.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_last.erl b/src/mod_last.erl
index d76b6049..1af1847b 100644
--- a/src/mod_last.erl
+++ b/src/mod_last.erl
@@ -45,23 +45,21 @@
-include("jlib.hrl").
-include("mod_privacy.hrl").
+-include("mod_last.hrl").
--record(last_activity, {us = {<<"">>, <<"">>} :: {binary(), binary()},
- timestamp = 0 :: non_neg_integer(),
- status = <<"">> :: binary()}).
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #last_activity{}) -> ok | pass.
+-callback get_last(binary(), binary()) ->
+ {ok, non_neg_integer(), binary()} | not_found | {error, any()}.
+-callback store_last_info(binary(), binary(), non_neg_integer(), binary()) ->
+ {atomic, any()}.
+-callback remove_user(binary(), binary()) -> {atomic, any()}.
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(last_activity,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, last_activity)}]),
- update_table();
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_LAST, ?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
@@ -86,10 +84,11 @@ stop(Host) ->
%%%
process_local_iq(_From, _To,
- #iq{type = Type, sub_el = SubEl} = IQ) ->
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
Sec = get_node_uptime(),
IQ#iq{type = result,
@@ -123,10 +122,11 @@ now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
%%%
process_sm_iq(From, To,
- #iq{type = Type, sub_el = SubEl} = IQ) ->
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
User = To#jid.luser,
Server = To#jid.lserver,
@@ -153,62 +153,29 @@ process_sm_iq(From, To,
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
end;
true ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ Txt = <<"Not subscribed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
end
end.
%% @spec (LUser::string(), LServer::string()) ->
%% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason}
get_last(LUser, LServer) ->
- get_last(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_last(LUser, LServer).
-get_last(LUser, LServer, mnesia) ->
- case catch mnesia:dirty_read(last_activity,
- {LUser, LServer})
- of
- {'EXIT', Reason} -> {error, Reason};
- [] -> not_found;
- [#last_activity{timestamp = TimeStamp,
- status = Status}] ->
- {ok, TimeStamp, Status}
- end;
-get_last(LUser, LServer, riak) ->
- case ejabberd_riak:get(last_activity, last_activity_schema(),
- {LUser, LServer}) of
- {ok, #last_activity{timestamp = TimeStamp,
- status = Status}} ->
- {ok, TimeStamp, Status};
- {error, notfound} ->
- not_found;
- Err ->
- Err
- end;
-get_last(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_last(LServer, Username) of
- {selected, [<<"seconds">>, <<"state">>], []} ->
- not_found;
- {selected, [<<"seconds">>, <<"state">>],
- [[STimeStamp, Status]]} ->
- case catch jlib:binary_to_integer(STimeStamp) of
- TimeStamp when is_integer(TimeStamp) ->
- {ok, TimeStamp, Status};
- Reason -> {error, {invalid_timestamp, Reason}}
- end;
- Reason -> {error, {invalid_result, Reason}}
- end.
-
-get_last_iq(IQ, SubEl, LUser, LServer) ->
+get_last_iq(#iq{lang = Lang} = IQ, SubEl, LUser, LServer) ->
case ejabberd_sm:get_user_resources(LUser, LServer) of
[] ->
case get_last(LUser, LServer) of
{error, _Reason} ->
+ Txt = <<"Database failure">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
+ sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]};
not_found ->
+ Txt = <<"No info about last activity found">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
+ sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]};
{ok, TimeStamp, Status} ->
TimeStamp2 = p1_time_compat:system_time(seconds),
Sec = TimeStamp2 - TimeStamp,
@@ -238,34 +205,8 @@ on_presence_update(User, Server, _Resource, Status) ->
store_last_info(User, Server, TimeStamp, Status) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- DBType = gen_mod:db_type(LServer, ?MODULE),
- store_last_info(LUser, LServer, TimeStamp, Status,
- DBType).
-
-store_last_info(LUser, LServer, TimeStamp, Status,
- mnesia) ->
- US = {LUser, LServer},
- F = fun () ->
- mnesia:write(#last_activity{us = US,
- timestamp = TimeStamp,
- status = Status})
- end,
- mnesia:transaction(F);
-store_last_info(LUser, LServer, TimeStamp, Status,
- riak) ->
- US = {LUser, LServer},
- {atomic, ejabberd_riak:put(#last_activity{us = US,
- timestamp = TimeStamp,
- status = Status},
- last_activity_schema())};
-store_last_info(LUser, LServer, TimeStamp, Status,
- odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- Seconds =
- ejabberd_odbc:escape(iolist_to_binary(integer_to_list(TimeStamp))),
- State = ejabberd_odbc:escape(Status),
- odbc_queries:set_last_t(LServer, Username, Seconds,
- State).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:store_last_info(LUser, LServer, TimeStamp, Status).
%% @spec (LUser::string(), LServer::string()) ->
%% {ok, TimeStamp::integer(), Status::string()} | not_found
@@ -278,72 +219,20 @@ get_last_info(LUser, LServer) ->
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- DBType = gen_mod:db_type(LServer, ?MODULE),
- remove_user(LUser, LServer, DBType).
-
-remove_user(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () -> mnesia:delete({last_activity, US}) end,
- mnesia:transaction(F);
-remove_user(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_last(LServer, Username);
-remove_user(LUser, LServer, riak) ->
- {atomic, ejabberd_riak:delete(last_activity, {LUser, LServer})}.
-
-update_table() ->
- Fields = record_info(fields, last_activity),
- case mnesia:table_info(last_activity, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- last_activity, Fields, set,
- fun(#last_activity{us = {U, _}}) -> U end,
- fun(#last_activity{us = {U, S}, status = Status} = R) ->
- R#last_activity{us = {iolist_to_binary(U),
- iolist_to_binary(S)},
- status = iolist_to_binary(Status)}
- end);
- _ ->
- ?INFO_MSG("Recreating last_activity table", []),
- mnesia:transform_table(last_activity, ignore, Fields)
- end.
-
-last_activity_schema() ->
- {record_info(fields, last_activity), #last_activity{}}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_user(LUser, LServer).
-export(_Server) ->
- [{last_activity,
- fun(Host, #last_activity{us = {LUser, LServer},
- timestamp = TimeStamp, status = Status})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- Seconds =
- ejabberd_odbc:escape(jlib:integer_to_binary(TimeStamp)),
- State = ejabberd_odbc:escape(Status),
- [[<<"delete from last where username='">>, Username, <<"';">>],
- [<<"insert into last(username, seconds, "
- "state) values ('">>,
- Username, <<"', '">>, Seconds, <<"', '">>, State,
- <<"');">>]];
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import(LServer) ->
- [{<<"select username, seconds, state from last">>,
- fun([LUser, TimeStamp, State]) ->
- #last_activity{us = {LUser, LServer},
- timestamp = jlib:binary_to_integer(
- TimeStamp),
- status = State}
- end}].
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
-import(_LServer, mnesia, #last_activity{} = LA) ->
- mnesia:dirty_write(LA);
-import(_LServer, riak, #last_activity{} = LA) ->
- ejabberd_riak:put(LA, last_activity_schema());
-import(_, _, _) ->
- pass.
+import(LServer, DBType, LA) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, LA).
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
diff --git a/src/mod_last_mnesia.erl b/src/mod_last_mnesia.erl
new file mode 100644
index 00000000..7a1610ab
--- /dev/null
+++ b/src/mod_last_mnesia.erl
@@ -0,0 +1,72 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_last_mnesia).
+-behaviour(mod_last).
+
+%% API
+-export([init/2, import/2, get_last/2, store_last_info/4, remove_user/2]).
+
+-include("mod_last.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(last_activity,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, last_activity)}]),
+ update_table().
+
+get_last(LUser, LServer) ->
+ case mnesia:dirty_read(last_activity, {LUser, LServer}) of
+ [] ->
+ not_found;
+ [#last_activity{timestamp = TimeStamp,
+ status = Status}] ->
+ {ok, TimeStamp, Status}
+ end.
+
+store_last_info(LUser, LServer, TimeStamp, Status) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ mnesia:write(#last_activity{us = US,
+ timestamp = TimeStamp,
+ status = Status})
+ end,
+ mnesia:transaction(F).
+
+remove_user(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () -> mnesia:delete({last_activity, US}) end,
+ mnesia:transaction(F).
+
+import(_LServer, #last_activity{} = LA) ->
+ mnesia:dirty_write(LA).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_table() ->
+ Fields = record_info(fields, last_activity),
+ case mnesia:table_info(last_activity, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ last_activity, Fields, set,
+ fun(#last_activity{us = {U, _}}) -> U end,
+ fun(#last_activity{us = {U, S}, status = Status} = R) ->
+ R#last_activity{us = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ status = iolist_to_binary(Status)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating last_activity table", []),
+ mnesia:transform_table(last_activity, ignore, Fields)
+ end.
diff --git a/src/mod_last_riak.erl b/src/mod_last_riak.erl
new file mode 100644
index 00000000..d25a3a15
--- /dev/null
+++ b/src/mod_last_riak.erl
@@ -0,0 +1,53 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_last_riak).
+-behaviour(mod_last).
+
+%% API
+-export([init/2, import/2, get_last/2, store_last_info/4, remove_user/2]).
+
+-include("mod_last.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+get_last(LUser, LServer) ->
+ case ejabberd_riak:get(last_activity, last_activity_schema(),
+ {LUser, LServer}) of
+ {ok, #last_activity{timestamp = TimeStamp,
+ status = Status}} ->
+ {ok, TimeStamp, Status};
+ {error, notfound} ->
+ not_found;
+ Err ->
+ Err
+ end.
+
+store_last_info(LUser, LServer, TimeStamp, Status) ->
+ US = {LUser, LServer},
+ {atomic, ejabberd_riak:put(#last_activity{us = US,
+ timestamp = TimeStamp,
+ status = Status},
+ last_activity_schema())}.
+
+remove_user(LUser, LServer) ->
+ {atomic, ejabberd_riak:delete(last_activity, {LUser, LServer})}.
+
+import(_LServer, #last_activity{} = LA) ->
+ ejabberd_riak:put(LA, last_activity_schema()).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+last_activity_schema() ->
+ {record_info(fields, last_activity), #last_activity{}}.
diff --git a/src/mod_last_sql.erl b/src/mod_last_sql.erl
new file mode 100644
index 00000000..5f67b14f
--- /dev/null
+++ b/src/mod_last_sql.erl
@@ -0,0 +1,75 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_last_sql).
+-behaviour(mod_last).
+
+%% API
+-export([init/2, get_last/2, store_last_info/4, remove_user/2,
+ import/1, import/2, export/1]).
+
+-include("mod_last.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+get_last(LUser, LServer) ->
+ case catch sql_queries:get_last(LServer, LUser) of
+ {selected, []} ->
+ not_found;
+ {selected, [{TimeStamp, Status}]} ->
+ {ok, TimeStamp, Status};
+ Reason ->
+ ?ERROR_MSG("failed to get last for user ~s@~s: ~p",
+ [LUser, LServer, Reason]),
+ {error, {invalid_result, Reason}}
+ end.
+
+store_last_info(LUser, LServer, TimeStamp, Status) ->
+ sql_queries:set_last_t(LServer, LUser, TimeStamp, Status).
+
+remove_user(LUser, LServer) ->
+ sql_queries:del_last(LServer, LUser).
+
+import(_LServer, _LA) ->
+ pass.
+
+export(_Server) ->
+ [{last_activity,
+ fun(Host, #last_activity{us = {LUser, LServer},
+ timestamp = TimeStamp, status = Status})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ Seconds =
+ ejabberd_sql:escape(jlib:integer_to_binary(TimeStamp)),
+ State = ejabberd_sql:escape(Status),
+ [[<<"delete from last where username='">>, Username, <<"';">>],
+ [<<"insert into last(username, seconds, "
+ "state) values ('">>,
+ Username, <<"', '">>, Seconds, <<"', '">>, State,
+ <<"');">>]];
+ (_Host, _R) ->
+ []
+ end}].
+
+import(LServer) ->
+ [{<<"select username, seconds, state from last">>,
+ fun([LUser, TimeStamp, State]) ->
+ #last_activity{us = {LUser, LServer},
+ timestamp = jlib:binary_to_integer(
+ TimeStamp),
+ status = State}
+ end}].
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_mam.erl b/src/mod_mam.erl
index d6e4e936..f84519a0 100644
--- a/src/mod_mam.erl
+++ b/src/mod_mam.erl
@@ -35,41 +35,37 @@
-export([user_send_packet/4, user_receive_packet/5,
process_iq_v0_2/3, process_iq_v0_3/3, disco_sm_features/5,
- remove_user/2, remove_user/3, mod_opt_type/1, muc_process_iq/4,
+ remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/4,
muc_filter_message/5, message_is_archived/5, delete_old_messages/2,
- get_commands_spec/0]).
+ get_commands_spec/0, msg_to_el/4]).
--include_lib("stdlib/include/ms_transform.hrl").
-include("jlib.hrl").
-include("logger.hrl").
-include("mod_muc_room.hrl").
-include("ejabberd_commands.hrl").
+-include("mod_mam.hrl").
-define(DEF_PAGE_SIZE, 50).
-define(MAX_PAGE_SIZE, 250).
--define(BIN_GREATER_THAN(A, B),
- ((A > B andalso byte_size(A) == byte_size(B))
- orelse byte_size(A) > byte_size(B))).
--define(BIN_LESS_THAN(A, B),
- ((A < B andalso byte_size(A) == byte_size(B))
- orelse byte_size(A) < byte_size(B))).
-
--record(archive_msg,
- {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$2',
- id = <<>> :: binary() | '_',
- timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_' | '$1',
- peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3' | undefined,
- bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3',
- packet = #xmlel{} :: xmlel() | '_',
- nick = <<"">> :: binary(),
- type = chat :: chat | groupchat}).
-
--record(archive_prefs,
- {us = {<<"">>, <<"">>} :: {binary(), binary()},
- default = never :: never | always | roster,
- always = [] :: [ljid()],
- never = [] :: [ljid()]}).
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback remove_user(binary(), binary()) -> any().
+-callback remove_room(binary(), binary(), binary()) -> any().
+-callback delete_old_messages(binary() | global,
+ erlang:timestamp(),
+ all | chat | groupchat) -> any().
+-callback extended_fields() -> [xmlel()].
+-callback store(xmlel(), binary(), {binary(), binary()}, chat | groupchat,
+ jid(), binary(), recv | send) -> {ok, binary()} | any().
+-callback write_prefs(binary(), binary(), #archive_prefs{}, binary()) -> ok | any().
+-callback get_prefs(binary(), binary()) -> {ok, #archive_prefs{}} | error.
+-callback select(binary(), jid(), jid(),
+ none | erlang:timestamp(),
+ none | erlang:timestamp(),
+ none | ljid() | {text, binary()},
+ none | #rsm_in{},
+ chat | groupchat) ->
+ {[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()}.
%%%===================================================================
%%% API
@@ -77,9 +73,9 @@
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
- DBType = gen_mod:db_type(Host, Opts),
- init_db(DBType, Host),
- init_cache(DBType, Opts),
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
+ init_cache(Opts),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_MAM_TMP, ?MODULE, process_iq_v0_2, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
@@ -120,18 +116,7 @@ start(Host, Opts) ->
ejabberd_commands:register_commands(get_commands_spec()),
ok.
-init_db(mnesia, _Host) ->
- mnesia:create_table(archive_msg,
- [{disc_only_copies, [node()]},
- {type, bag},
- {attributes, record_info(fields, archive_msg)}]),
- mnesia:create_table(archive_prefs,
- [{disc_only_copies, [node()]},
- {attributes, record_info(fields, archive_prefs)}]);
-init_db(_, _) ->
- ok.
-
-init_cache(_DBType, Opts) ->
+init_cache(Opts) ->
MaxSize = gen_mod:get_opt(cache_size, Opts,
fun(I) when is_integer(I), I>0 -> I end,
1000),
@@ -179,24 +164,14 @@ stop(Host) ->
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- remove_user(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_user(LUser, LServer).
-remove_user(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () ->
- mnesia:delete({archive_msg, US}),
- mnesia:delete({archive_prefs, US})
- end,
- mnesia:transaction(F);
-remove_user(LUser, LServer, odbc) ->
- SUser = ejabberd_odbc:escape(LUser),
- ejabberd_odbc:sql_query(
- LServer,
- [<<"delete from archive where username='">>, SUser, <<"';">>]),
- ejabberd_odbc:sql_query(
- LServer,
- [<<"delete from archive_prefs where username='">>, SUser, <<"';">>]).
+remove_room(LServer, Name, Host) ->
+ LName = jid:nodeprep(Name),
+ LHost = jid:nameprep(Host),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_room(LServer, LName, LHost).
user_receive_packet(Pkt, C2SState, JID, Peer, To) ->
LUser = JID#jid.luser,
@@ -287,7 +262,7 @@ muc_process_iq(#iq{type = set,
sub_el = #xmlel{name = <<"query">>,
attrs = Attrs} = SubEl} = IQ,
MUCState, From, To) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
muc_process_iq(IQ, MUCState, From, To, get_xdata_fields(SubEl));
_ ->
@@ -297,7 +272,7 @@ muc_process_iq(#iq{type = get,
sub_el = #xmlel{name = <<"query">>,
attrs = Attrs} = SubEl} = IQ,
MUCState, From, To) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MAM_TMP ->
muc_process_iq(IQ, MUCState, From, To, parse_query_v0_2(SubEl));
NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
@@ -310,8 +285,8 @@ muc_process_iq(IQ, _MUCState, _From, _To) ->
IQ.
get_xdata_fields(SubEl) ->
- case {xml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA),
- xml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of
+ case {fxml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA),
+ fxml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of
{#xmlel{} = XData, false} ->
jlib:parse_xdata_submit(XData);
{#xmlel{} = XData, #xmlel{}} ->
@@ -343,10 +318,10 @@ message_is_archived(false, C2SState, Peer,
if_enabled ->
get_prefs(LUser, LServer);
on_request ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
cache_tab:lookup(archive_prefs, {LUser, LServer},
fun() ->
- get_prefs(LUser, LServer, DBType)
+ Mod:get_prefs(LUser, LServer)
end);
never ->
error
@@ -365,21 +340,19 @@ delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
Diff = Days * 24 * 60 * 60 * 1000000,
TimeStamp = usec_to_now(p1_time_compat:system_time(micro_seconds) - Diff),
Type = jlib:binary_to_atom(TypeBin),
- {Results, _} =
- lists:foldl(fun(Host, {Results, MnesiaDone}) ->
- case {gen_mod:db_type(Host, ?MODULE), MnesiaDone} of
- {mnesia, true} ->
- {Results, true};
- {mnesia, false} ->
- Res = delete_old_messages(TimeStamp, Type,
- global, mnesia),
- {[Res|Results], true};
- {DBType, _} ->
- Res = delete_old_messages(TimeStamp, Type,
- Host, DBType),
- {[Res|Results], MnesiaDone}
- end
- end, {[], false}, ?MYHOSTS),
+ DBTypes = lists:usort(
+ lists:map(
+ fun(Host) ->
+ case gen_mod:db_type(Host, ?MODULE) of
+ sql -> {sql, Host};
+ Other -> {Other, global}
+ end
+ end, ?MYHOSTS)),
+ Results = lists:map(
+ fun({DBType, ServerHost}) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:delete_old_messages(ServerHost, TimeStamp, Type)
+ end, DBTypes),
case lists:filter(fun(Res) -> Res /= ok end, Results) of
[] -> ok;
[NotOk|_] -> NotOk
@@ -387,27 +360,12 @@ delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
delete_old_messages(_TypeBin, _Days) ->
unsupported_type.
-delete_old_messages(TimeStamp, Type, global, mnesia) ->
- MS = ets:fun2ms(fun(#archive_msg{timestamp = MsgTS,
- type = MsgType} = Msg)
- when MsgTS < TimeStamp,
- MsgType == Type orelse Type == all ->
- Msg
- end),
- OldMsgs = mnesia:dirty_select(archive_msg, MS),
- lists:foreach(fun(Rec) ->
- ok = mnesia:dirty_delete_object(Rec)
- end, OldMsgs);
-delete_old_messages(_TimeStamp, _Type, _Host, _DBType) ->
- %% TODO
- not_implemented.
-
%%%===================================================================
%%% Internal functions
%%%===================================================================
process_iq(LServer, #iq{sub_el = #xmlel{attrs = Attrs}} = IQ) ->
- NS = case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ NS = case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MAM_0 ->
?NS_MAM_0;
_ ->
@@ -427,15 +385,9 @@ process_iq(LServer, #iq{sub_el = #xmlel{attrs = Attrs}} = IQ) ->
#xmlel{name = <<"field">>,
attrs = [{<<"type">>, <<"text-single">>},
{<<"var">>, <<"end">>}]}],
- Fields = case gen_mod:db_type(LServer, ?MODULE) of
- odbc ->
- WithText = #xmlel{name = <<"field">>,
- attrs = [{<<"type">>, <<"text-single">>},
- {<<"var">>, <<"withtext">>}]},
- [WithText|CommonFields];
- _ ->
- CommonFields
- end,
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ ExtendedFields = Mod:extended_fields(),
+ Fields = ExtendedFields ++ CommonFields,
Form = #xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
children = Fields},
@@ -447,8 +399,8 @@ process_iq(LServer, #iq{sub_el = #xmlel{attrs = Attrs}} = IQ) ->
% Preference setting (both v0.2 & v0.3)
process_iq(#jid{luser = LUser, lserver = LServer},
#jid{lserver = LServer},
- #iq{type = set, sub_el = #xmlel{name = <<"prefs">>} = SubEl} = IQ) ->
- try {case xml:get_tag_attr_s(<<"default">>, SubEl) of
+ #iq{type = set, lang = Lang, sub_el = #xmlel{name = <<"prefs">>} = SubEl} = IQ) ->
+ try {case fxml:get_tag_attr_s(<<"default">>, SubEl) of
<<"always">> -> always;
<<"never">> -> never;
<<"roster">> -> roster
@@ -461,14 +413,17 @@ process_iq(#jid{luser = LUser, lserver = LServer},
(_, {A, N}) ->
{A, N}
end, {[], []}, SubEl#xmlel.children)} of
- {Default, {Always, Never}} ->
- case write_prefs(LUser, LServer, LServer, Default,
- lists:usort(Always), lists:usort(Never)) of
+ {Default, {Always0, Never0}} ->
+ Always = lists:usort(Always0),
+ Never = lists:usort(Never0),
+ case write_prefs(LUser, LServer, LServer, Default, Always, Never) of
ok ->
- IQ#iq{type = result, sub_el = []};
+ NewPrefs = prefs_el(Default, Always, Never, IQ#iq.xmlns),
+ IQ#iq{type = result, sub_el = [NewPrefs]};
_Err ->
+ Txt = <<"Database failure">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+ sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
end
catch _:_ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
@@ -477,21 +432,11 @@ process_iq(#jid{luser = LUser, lserver = LServer},
#jid{lserver = LServer},
#iq{type = get, sub_el = #xmlel{name = <<"prefs">>}} = IQ) ->
Prefs = get_prefs(LUser, LServer),
- Default = jlib:atom_to_binary(Prefs#archive_prefs.default),
- JFun = fun(L) ->
- [#xmlel{name = <<"jid">>,
- children = [{xmlcdata, jid:to_string(J)}]}
- || J <- L]
- end,
- Always = #xmlel{name = <<"always">>,
- children = JFun(Prefs#archive_prefs.always)},
- Never = #xmlel{name = <<"never">>,
- children = JFun(Prefs#archive_prefs.never)},
- IQ#iq{type = result,
- sub_el = [#xmlel{name = <<"prefs">>,
- attrs = [{<<"xmlns">>, IQ#iq.xmlns},
- {<<"default">>, Default}],
- children = [Always, Never]}]};
+ PrefsEl = prefs_el(Prefs#archive_prefs.default,
+ Prefs#archive_prefs.always,
+ Prefs#archive_prefs.never,
+ IQ#iq.xmlns),
+ IQ#iq{type = result, sub_el = [PrefsEl]};
process_iq(_, _, #iq{sub_el = SubEl} = IQ) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
@@ -524,16 +469,13 @@ process_iq(LServer, #jid{luser = LUser} = From, To, IQ, SubEl, Fs, MsgType) ->
{_Start, _End, _With, #rsm_in{index = Index}} when is_integer(Index) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]};
{Start, End, With, RSM} ->
- NS = xml:get_tag_attr_s(<<"xmlns">>, SubEl),
+ NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl),
select_and_send(LServer, From, To, Start, End,
With, limit_max(RSM, NS), IQ, MsgType)
end.
-muc_process_iq(#iq{lang = Lang, sub_el = SubEl} = IQ,
- #state{config = #config{members_only = MembersOnly}} = MUCState,
- From, To, Fs) ->
- case not MembersOnly orelse
- mod_muc_room:is_occupant_or_admin(From, MUCState) of
+muc_process_iq(#iq{lang = Lang, sub_el = SubEl} = IQ, MUCState, From, To, Fs) ->
+ case may_enter_room(From, MUCState) of
true ->
LServer = MUCState#state.server_host,
Role = mod_muc_room:get_role(From, MUCState),
@@ -548,13 +490,13 @@ muc_process_iq(#iq{lang = Lang, sub_el = SubEl} = IQ,
parse_query_v0_2(Query) ->
lists:flatmap(
fun (#xmlel{name = <<"start">>} = El) ->
- [{<<"start">>, [xml:get_tag_cdata(El)]}];
+ [{<<"start">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"end">>} = El) ->
- [{<<"end">>, [xml:get_tag_cdata(El)]}];
+ [{<<"end">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"with">>} = El) ->
- [{<<"with">>, [xml:get_tag_cdata(El)]}];
+ [{<<"with">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"withtext">>} = El) ->
- [{<<"withtext">>, [xml:get_tag_cdata(El)]}];
+ [{<<"withtext">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"set">>}) ->
[{<<"set">>, Query}];
(_) ->
@@ -562,7 +504,7 @@ parse_query_v0_2(Query) ->
end, Query#xmlel.children).
should_archive(#xmlel{name = <<"message">>} = Pkt, LServer) ->
- case xml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
+ case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
<<"error">> ->
false;
<<"groupchat">> ->
@@ -578,7 +520,7 @@ should_archive(#xmlel{name = <<"message">>} = Pkt, LServer) ->
no_store ->
false;
none ->
- case xml:get_subtag_cdata(Pkt, <<"body">>) of
+ case fxml:get_subtag_cdata(Pkt, <<"body">>) of
<<>> ->
%% Empty body
false;
@@ -596,7 +538,7 @@ strip_my_archived_tag(Pkt, LServer) ->
fun(#xmlel{name = Tag, attrs = Attrs})
when Tag == <<"archived">>; Tag == <<"stanza-id">> ->
case catch jid:nameprep(
- xml:get_attr_s(
+ fxml:get_attr_s(
<<"by">>, Attrs)) of
LServer ->
false;
@@ -612,9 +554,9 @@ strip_x_jid_tags(Pkt) ->
NewEls = lists:filter(
fun(#xmlel{name = <<"x">>} = XEl) ->
not lists:any(fun(ItemEl) ->
- xml:get_tag_attr(<<"jid">>, ItemEl)
+ fxml:get_tag_attr(<<"jid">>, ItemEl)
/= false
- end, xml:get_subtags(XEl, <<"item">>));
+ end, fxml:get_subtags(XEl, <<"item">>));
(_) ->
true
end, Pkt#xmlel.children),
@@ -650,7 +592,7 @@ should_archive_peer(C2SState,
end.
should_archive_muc(Pkt) ->
- case xml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
+ case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
<<"groupchat">> ->
case check_store_hint(Pkt) of
store ->
@@ -658,9 +600,9 @@ should_archive_muc(Pkt) ->
no_store ->
false;
none ->
- case xml:get_subtag_cdata(Pkt, <<"body">>) of
+ case fxml:get_subtag_cdata(Pkt, <<"body">>) of
<<>> ->
- case xml:get_subtag_cdata(Pkt, <<"subject">>) of
+ case fxml:get_subtag_cdata(Pkt, <<"subject">>) of
<<>> ->
false;
_ ->
@@ -688,23 +630,23 @@ check_store_hint(Pkt) ->
end.
has_store_hint(Message) ->
- xml:get_subtag_with_xmlns(Message, <<"store">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Message, <<"store">>, ?NS_HINTS)
/= false.
has_no_store_hint(Message) ->
- xml:get_subtag_with_xmlns(Message, <<"no-store">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Message, <<"no-store">>, ?NS_HINTS)
/= false orelse
- xml:get_subtag_with_xmlns(Message, <<"no-storage">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Message, <<"no-storage">>, ?NS_HINTS)
/= false orelse
- xml:get_subtag_with_xmlns(Message, <<"no-permanent-store">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Message, <<"no-permanent-store">>, ?NS_HINTS)
/= false orelse
- xml:get_subtag_with_xmlns(Message, <<"no-permanent-storage">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Message, <<"no-permanent-storage">>, ?NS_HINTS)
/= false.
is_resent(Pkt, LServer) ->
- case xml:get_subtag_with_xmlns(Pkt, <<"stanza-id">>, ?NS_SID_0) of
+ case fxml:get_subtag_with_xmlns(Pkt, <<"stanza-id">>, ?NS_SID_0) of
#xmlel{attrs = Attrs} ->
- case xml:get_attr(<<"by">>, Attrs) of
+ case fxml:get_attr(<<"by">>, Attrs) of
{value, LServer} ->
true;
_ ->
@@ -714,13 +656,19 @@ is_resent(Pkt, LServer) ->
false
end.
+may_enter_room(From,
+ #state{config = #config{members_only = false}} = MUCState) ->
+ mod_muc_room:get_affiliation(From, MUCState) /= outcast;
+may_enter_room(From, MUCState) ->
+ mod_muc_room:is_occupant_or_admin(From, MUCState).
+
store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) ->
Prefs = get_prefs(LUser, LServer),
case should_archive_peer(C2SState, Prefs, Peer) of
true ->
US = {LUser, LServer},
- store(Pkt, LServer, US, chat, Peer, <<"">>, Dir,
- gen_mod:db_type(LServer, ?MODULE));
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:store(Pkt, LServer, US, chat, Peer, <<"">>, Dir);
false ->
pass
end.
@@ -730,101 +678,26 @@ store_muc(MUCState, Pkt, RoomJID, Peer, Nick) ->
true ->
LServer = MUCState#state.server_host,
{U, S, _} = jid:tolower(RoomJID),
- store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv,
- gen_mod:db_type(LServer, ?MODULE));
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv);
false ->
pass
end.
-store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir, mnesia) ->
- LPeer = {PUser, PServer, _} = jid:tolower(Peer),
- TS = p1_time_compat:timestamp(),
- ID = jlib:integer_to_binary(now_to_usec(TS)),
- case mnesia:dirty_write(
- #archive_msg{us = {LUser, LServer},
- id = ID,
- timestamp = TS,
- peer = LPeer,
- bare_peer = {PUser, PServer, <<>>},
- type = Type,
- nick = Nick,
- packet = Pkt}) of
- ok ->
- {ok, ID};
- Err ->
- Err
- end;
-store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, odbc) ->
- TSinteger = p1_time_compat:system_time(micro_seconds),
- ID = TS = jlib:integer_to_binary(TSinteger),
- SUser = case Type of
- chat -> LUser;
- groupchat -> jid:to_string({LUser, LHost, <<>>})
- end,
- BarePeer = jid:to_string(
- jid:tolower(
- jid:remove_resource(Peer))),
- LPeer = jid:to_string(
- jid:tolower(Peer)),
- XML = xml:element_to_binary(Pkt),
- Body = xml:get_subtag_cdata(Pkt, <<"body">>),
- case ejabberd_odbc:sql_query(
- LServer,
- [<<"insert into archive (username, timestamp, "
- "peer, bare_peer, xml, txt, kind, nick) values (">>,
- <<"'">>, ejabberd_odbc:escape(SUser), <<"', ">>,
- <<"'">>, TS, <<"', ">>,
- <<"'">>, ejabberd_odbc:escape(LPeer), <<"', ">>,
- <<"'">>, ejabberd_odbc:escape(BarePeer), <<"', ">>,
- <<"'">>, ejabberd_odbc:escape(XML), <<"', ">>,
- <<"'">>, ejabberd_odbc:escape(Body), <<"', ">>,
- <<"'">>, jlib:atom_to_binary(Type), <<"', ">>,
- <<"'">>, ejabberd_odbc:escape(Nick), <<"');">>]) of
- {updated, _} ->
- {ok, ID};
- Err ->
- Err
- end.
-
write_prefs(LUser, LServer, Host, Default, Always, Never) ->
- DBType = case gen_mod:db_type(Host, ?MODULE) of
- odbc -> {odbc, Host};
- DB -> DB
- end,
Prefs = #archive_prefs{us = {LUser, LServer},
default = Default,
always = Always,
never = Never},
+ Mod = gen_mod:db_mod(Host, ?MODULE),
cache_tab:dirty_insert(
archive_prefs, {LUser, LServer}, Prefs,
- fun() -> write_prefs(LUser, LServer, Prefs, DBType) end).
-
-write_prefs(_LUser, _LServer, Prefs, mnesia) ->
- mnesia:dirty_write(Prefs);
-write_prefs(LUser, _LServer, #archive_prefs{default = Default,
- never = Never,
- always = Always},
- {odbc, Host}) ->
- SUser = ejabberd_odbc:escape(LUser),
- SDefault = erlang:atom_to_binary(Default, utf8),
- SAlways = ejabberd_odbc:encode_term(Always),
- SNever = ejabberd_odbc:encode_term(Never),
- case update(Host, <<"archive_prefs">>,
- [<<"username">>, <<"def">>, <<"always">>, <<"never">>],
- [SUser, SDefault, SAlways, SNever],
- [<<"username='">>, SUser, <<"'">>]) of
- {updated, _} ->
- ok;
- Err ->
- Err
- end.
+ fun() -> Mod:write_prefs(LUser, LServer, Prefs, Host) end).
get_prefs(LUser, LServer) ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
Res = cache_tab:lookup(archive_prefs, {LUser, LServer},
- fun() -> get_prefs(LUser, LServer,
- DBType)
- end),
+ fun() -> Mod:get_prefs(LUser, LServer) end),
case Res of
{ok, Prefs} ->
Prefs;
@@ -846,30 +719,21 @@ get_prefs(LUser, LServer) ->
end
end.
-get_prefs(LUser, LServer, mnesia) ->
- case mnesia:dirty_read(archive_prefs, {LUser, LServer}) of
- [Prefs] ->
- {ok, Prefs};
- _ ->
- error
- end;
-get_prefs(LUser, LServer, odbc) ->
- case ejabberd_odbc:sql_query(
- LServer,
- [<<"select def, always, never from archive_prefs ">>,
- <<"where username='">>,
- ejabberd_odbc:escape(LUser), <<"';">>]) of
- {selected, _, [[SDefault, SAlways, SNever]]} ->
- Default = erlang:binary_to_existing_atom(SDefault, utf8),
- Always = ejabberd_odbc:decode_term(SAlways),
- Never = ejabberd_odbc:decode_term(SNever),
- {ok, #archive_prefs{us = {LUser, LServer},
- default = Default,
- always = Always,
- never = Never}};
- _ ->
- error
- end.
+prefs_el(Default, Always, Never, NS) ->
+ Default1 = jlib:atom_to_binary(Default),
+ JFun = fun(L) ->
+ [#xmlel{name = <<"jid">>,
+ children = [{xmlcdata, jid:to_string(J)}]}
+ || J <- L]
+ end,
+ Always1 = #xmlel{name = <<"always">>,
+ children = JFun(Always)},
+ Never1 = #xmlel{name = <<"never">>,
+ children = JFun(Never)},
+ #xmlel{name = <<"prefs">>,
+ attrs = [{<<"xmlns">>, NS},
+ {<<"default">>, Default1}],
+ children = [Always1, Never1]}.
maybe_activate_mam(LUser, LServer) ->
ActivateOpt = gen_mod:get_module_opt(LServer, ?MODULE,
@@ -878,11 +742,10 @@ maybe_activate_mam(LUser, LServer) ->
false),
case ActivateOpt of
true ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
Res = cache_tab:lookup(archive_prefs, {LUser, LServer},
fun() ->
- get_prefs(LUser, LServer,
- gen_mod:db_type(LServer,
- ?MODULE))
+ Mod:get_prefs(LUser, LServer)
end),
case Res of
{ok, _Prefs} ->
@@ -900,31 +763,22 @@ maybe_activate_mam(LUser, LServer) ->
end.
select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType) ->
- DBType = case gen_mod:db_type(LServer, ?MODULE) of
- odbc -> {odbc, LServer};
- DB -> DB
- end,
- select_and_send(LServer, From, To, Start, End, With, RSM, IQ,
- MsgType, DBType).
-
-select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType, DBType) ->
{Msgs, IsComplete, Count} = select_and_start(LServer, From, To, Start, End,
- With, RSM, MsgType, DBType),
+ With, RSM, MsgType),
SortedMsgs = lists:keysort(2, Msgs),
send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ).
-select_and_start(LServer, From, To, Start, End, With, RSM, MsgType, DBType) ->
+select_and_start(LServer, From, To, Start, End, With, RSM, MsgType) ->
case MsgType of
chat ->
- select(LServer, From, Start, End, With, RSM, MsgType, DBType);
+ select(LServer, From, From, Start, End, With, RSM, MsgType);
{groupchat, _Role, _MUCState} ->
- select(LServer, To, Start, End, With, RSM, MsgType, DBType)
+ select(LServer, From, To, Start, End, With, RSM, MsgType)
end.
-select(_LServer, JidRequestor, Start, End, _With, RSM,
+select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
{groupchat, _Role, #state{config = #config{mam = false},
- history = History}} = MsgType,
- _DBType) ->
+ history = History}} = MsgType) ->
#lqueue{len = L, queue = Q} = History,
{Msgs0, _} =
lists:mapfoldl(
@@ -941,8 +795,8 @@ select(_LServer, JidRequestor, Start, End, _With, RSM,
peer = undefined,
nick = Nick,
packet = Pkt},
- MsgType,
- JidRequestor)}], I+1};
+ MsgType, JidRequestor, JidArchive)}],
+ I+1};
false ->
{[], I+1}
end
@@ -958,108 +812,49 @@ select(_LServer, JidRequestor, Start, End, _With, RSM,
_ ->
{Msgs, true, L}
end;
-select(_LServer, #jid{luser = LUser, lserver = LServer} = JidRequestor,
- Start, End, With, RSM, MsgType, mnesia) ->
- MS = make_matchspec(LUser, LServer, Start, End, With),
- Msgs = mnesia:dirty_select(archive_msg, MS),
- SortedMsgs = lists:keysort(#archive_msg.timestamp, Msgs),
- {FilteredMsgs, IsComplete} = filter_by_rsm(SortedMsgs, RSM),
- Count = length(Msgs),
- {lists:map(
- fun(Msg) ->
- {Msg#archive_msg.id,
- jlib:binary_to_integer(Msg#archive_msg.id),
- msg_to_el(Msg, MsgType, JidRequestor)}
- end, FilteredMsgs), IsComplete, Count};
-select(LServer, #jid{luser = LUser} = JidRequestor,
- Start, End, With, RSM, MsgType, {odbc, Host}) ->
- User = case MsgType of
- chat -> LUser;
- {groupchat, _Role, _MUCState} -> jid:to_string(JidRequestor)
- end,
- {Query, CountQuery} = make_sql_query(User, LServer,
- Start, End, With, RSM),
- % TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a
- % reasonable limit on how many stanzas may be pushed to a client in one
- % request. If a query returns a number of stanzas greater than this limit
- % and the client did not specify a limit using RSM then the server should
- % return a policy-violation error to the client." We currently don't do this
- % for v0.2 requests, but we do limit #rsm_in.max for v0.3 and newer.
- case {ejabberd_odbc:sql_query(Host, Query),
- ejabberd_odbc:sql_query(Host, CountQuery)} of
- {{selected, _, Res}, {selected, _, [[Count]]}} ->
- {Max, Direction} = case RSM of
- #rsm_in{max = M, direction = D} -> {M, D};
- _ -> {undefined, undefined}
- end,
- {Res1, IsComplete} =
- if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
- if Direction == before ->
- {lists:nthtail(1, Res), false};
- true ->
- {lists:sublist(Res, Max), false}
- end;
- true ->
- {Res, true}
- end,
- {lists:flatmap(
- fun([TS, XML, PeerBin, Kind, Nick]) ->
- try
- #xmlel{} = El = xml_stream:parse_element(XML),
- Now = usec_to_now(jlib:binary_to_integer(TS)),
- PeerJid = jid:tolower(jid:from_string(PeerBin)),
- T = case Kind of
- <<"">> -> chat;
- null -> chat;
- _ -> jlib:binary_to_atom(Kind)
- end,
- [{TS, jlib:binary_to_integer(TS),
- msg_to_el(#archive_msg{timestamp = Now,
- packet = El,
- type = T,
- nick = Nick,
- peer = PeerJid},
- MsgType,
- JidRequestor)}]
- catch _:Err ->
- ?ERROR_MSG("failed to parse data from SQL: ~p. "
- "The data was: "
- "timestamp = ~s, xml = ~s, "
- "peer = ~s, kind = ~s, nick = ~s",
- [Err, TS, XML, PeerBin, Kind, Nick]),
- []
- end
- end, Res1), IsComplete, jlib:binary_to_integer(Count)};
- _ ->
- {[], false, 0}
- end.
+select(LServer, From, From, Start, End, With, RSM, MsgType) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:select(LServer, From, From, Start, End, With, RSM, MsgType).
msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer},
- MsgType, #jid{lserver = LServer} = JidRequestor) ->
- Pkt2 = maybe_update_from_to(Pkt1, JidRequestor, Peer, MsgType, Nick),
+ MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) ->
+ Pkt2 = maybe_update_from_to(Pkt1, JidRequestor, JidArchive, Peer, MsgType,
+ Nick),
Pkt3 = #xmlel{name = <<"forwarded">>,
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
- children = [xml:replace_tag_attr(
+ children = [fxml:replace_tag_attr(
<<"xmlns">>, <<"jabber:client">>, Pkt2)]},
jlib:add_delay_info(Pkt3, LServer, TS).
-maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor,
- Peer, {groupchat, Role, _MUCState}, Nick) ->
- Items = case Role of
- moderator when Peer /= undefined ->
+maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, JidArchive,
+ Peer, {groupchat, Role,
+ #state{config = #config{anonymous = Anon}}},
+ Nick) ->
+ ExposeJID = case {Peer, JidRequestor} of
+ {undefined, _JidRequestor} ->
+ false;
+ {{U, S, _R}, #jid{luser = U, lserver = S}} ->
+ true;
+ {_Peer, _JidRequestor} when not Anon; Role == moderator ->
+ true;
+ {_Peer, _JidRequestor} ->
+ false
+ end,
+ Items = case ExposeJID of
+ true ->
[#xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
children =
[#xmlel{name = <<"item">>,
attrs = [{<<"jid">>,
jid:to_string(Peer)}]}]}];
- _ ->
+ false ->
[]
end,
Pkt1 = Pkt#xmlel{children = Items ++ Els},
- Pkt2 = jlib:replace_from(jid:replace_resource(JidRequestor, Nick), Pkt1),
+ Pkt2 = jlib:replace_from(jid:replace_resource(JidArchive, Nick), Pkt1),
jlib:remove_attr(<<"to">>, Pkt2);
-maybe_update_from_to(Pkt, _JidRequestor, _Peer, chat, _Nick) ->
+maybe_update_from_to(Pkt, _JidRequestor, _JidArchive, _Peer, chat, _Nick) ->
Pkt.
is_bare_copy(#jid{luser = U, lserver = S, lresource = R}, To) ->
@@ -1095,8 +890,8 @@ is_bare_copy(#jid{luser = U, lserver = S, lresource = R}, To) ->
end.
send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) ->
- QID = xml:get_tag_attr_s(<<"queryid">>, SubEl),
- NS = xml:get_tag_attr_s(<<"xmlns">>, SubEl),
+ QID = fxml:get_tag_attr_s(<<"queryid">>, SubEl),
+ NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl),
QIDAttr = if QID /= <<>> ->
[{<<"queryid">>, QID}];
true ->
@@ -1135,7 +930,6 @@ send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) ->
ignore
end.
-
make_rsm_out([], _, Count, Attrs, NS) ->
Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
true -> <<"fin">>
@@ -1152,32 +946,6 @@ make_rsm_out([{FirstID, _, _}|_] = Msgs, _, Count, Attrs, NS) ->
#rsm_out{first = FirstID, count = Count,
last = LastID})}].
-filter_by_rsm(Msgs, none) ->
- {Msgs, true};
-filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 ->
- {[], true};
-filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) ->
- NewMsgs = case Direction of
- aft when ID /= <<"">> ->
- lists:filter(
- fun(#archive_msg{id = I}) ->
- ?BIN_GREATER_THAN(I, ID)
- end, Msgs);
- before when ID /= <<"">> ->
- lists:foldl(
- fun(#archive_msg{id = I} = Msg, Acc)
- when ?BIN_LESS_THAN(I, ID) ->
- [Msg|Acc];
- (_, Acc) ->
- Acc
- end, [], Msgs);
- before when ID == <<"">> ->
- lists:reverse(Msgs);
- _ ->
- Msgs
- end,
- filter_by_max(NewMsgs, Max).
-
filter_by_max(Msgs, undefined) ->
{Msgs, true};
filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 ->
@@ -1206,118 +974,6 @@ match_rsm(Now, #rsm_in{id = ID, direction = before}) when ID /= <<"">> ->
match_rsm(_Now, _) ->
true.
-make_matchspec(LUser, LServer, Start, End, {_, _, <<>>} = With) ->
- ets:fun2ms(
- fun(#archive_msg{timestamp = TS,
- us = US,
- bare_peer = BPeer} = Msg)
- when Start =< TS, End >= TS,
- US == {LUser, LServer},
- BPeer == With ->
- Msg
- end);
-make_matchspec(LUser, LServer, Start, End, {_, _, _} = With) ->
- ets:fun2ms(
- fun(#archive_msg{timestamp = TS,
- us = US,
- peer = Peer} = Msg)
- when Start =< TS, End >= TS,
- US == {LUser, LServer},
- Peer == With ->
- Msg
- end);
-make_matchspec(LUser, LServer, Start, End, none) ->
- ets:fun2ms(
- fun(#archive_msg{timestamp = TS,
- us = US,
- peer = Peer} = Msg)
- when Start =< TS, End >= TS,
- US == {LUser, LServer} ->
- Msg
- end).
-
-make_sql_query(User, _LServer, Start, End, With, RSM) ->
- {Max, Direction, ID} = case RSM of
- #rsm_in{} ->
- {RSM#rsm_in.max,
- RSM#rsm_in.direction,
- RSM#rsm_in.id};
- none ->
- {none, none, <<>>}
- end,
- LimitClause = if is_integer(Max), Max >= 0 ->
- [<<" limit ">>, jlib:integer_to_binary(Max+1)];
- true ->
- []
- end,
- WithClause = case With of
- {text, <<>>} ->
- [];
- {text, Txt} ->
- [<<" and match (txt) against ('">>,
- ejabberd_odbc:escape(Txt), <<"')">>];
- {_, _, <<>>} ->
- [<<" and bare_peer='">>,
- ejabberd_odbc:escape(jid:to_string(With)),
- <<"'">>];
- {_, _, _} ->
- [<<" and peer='">>,
- ejabberd_odbc:escape(jid:to_string(With)),
- <<"'">>];
- none ->
- []
- end,
- PageClause = case catch jlib:binary_to_integer(ID) of
- I when is_integer(I), I >= 0 ->
- case Direction of
- before ->
- [<<" AND timestamp < ">>, ID];
- aft ->
- [<<" AND timestamp > ">>, ID];
- _ ->
- []
- end;
- _ ->
- []
- end,
- StartClause = case Start of
- {_, _, _} ->
- [<<" and timestamp >= ">>,
- jlib:integer_to_binary(now_to_usec(Start))];
- _ ->
- []
- end,
- EndClause = case End of
- {_, _, _} ->
- [<<" and timestamp <= ">>,
- jlib:integer_to_binary(now_to_usec(End))];
- _ ->
- []
- end,
- SUser = ejabberd_odbc:escape(User),
-
- Query = [<<"SELECT timestamp, xml, peer, kind, nick"
- " FROM archive WHERE username='">>,
- SUser, <<"'">>, WithClause, StartClause, EndClause,
- PageClause],
-
- QueryPage =
- case Direction of
- before ->
- % ID can be empty because of
- % XEP-0059: Result Set Management
- % 2.5 Requesting the Last Page in a Result Set
- [<<"SELECT timestamp, xml, peer, kind, nick FROM (">>, Query,
- <<" ORDER BY timestamp DESC ">>,
- LimitClause, <<") AS t ORDER BY timestamp ASC;">>];
- _ ->
- [Query, <<" ORDER BY timestamp ASC ">>,
- LimitClause, <<";">>]
- end,
- {QueryPage,
- [<<"SELECT COUNT(*) FROM archive WHERE username='">>,
- SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}.
-
now_to_usec({MSec, Sec, USec}) ->
(MSec*1000000 + Sec)*1000000 + USec.
@@ -1336,35 +992,13 @@ datetime_to_now(DateTime, USecs) ->
get_jids(Els) ->
lists:flatmap(
fun(#xmlel{name = <<"jid">>} = El) ->
- J = jid:from_string(xml:get_tag_cdata(El)),
+ J = jid:from_string(fxml:get_tag_cdata(El)),
[jid:tolower(jid:remove_resource(J)),
jid:tolower(J)];
(_) ->
[]
end, Els).
-update(LServer, Table, Fields, Vals, Where) ->
- UPairs = lists:zipwith(fun (A, B) ->
- <<A/binary, "='", B/binary, "'">>
- end,
- Fields, Vals),
- case ejabberd_odbc:sql_query(LServer,
- [<<"update ">>, Table, <<" set ">>,
- join(UPairs, <<", ">>), <<" where ">>, Where,
- <<";">>])
- of
- {updated, 1} -> {updated, 1};
- _ ->
- ejabberd_odbc:sql_query(LServer,
- [<<"insert into ">>, Table, <<"(">>,
- join(Fields, <<", ">>), <<") values ('">>,
- join(Vals, <<"', '">>), <<"');">>])
- end.
-
-%% Almost a copy of string:join/2.
-join([], _Sep) -> [];
-join([H | T], Sep) -> [H, [[Sep, X] || X <- T]].
-
get_commands_spec() ->
[#ejabberd_commands{name = delete_old_mam_messages, tags = [purge],
desc = "Delete MAM messages older than DAYS",
@@ -1383,7 +1017,12 @@ mod_opt_type(cache_life_time) ->
fun (I) when is_integer(I), I > 0 -> I end;
mod_opt_type(cache_size) ->
fun (I) when is_integer(I), I > 0 -> I end;
-mod_opt_type(db_type) -> fun gen_mod:v_db/1;
+mod_opt_type(db_type) ->
+ fun(sql) -> sql;
+ (odbc) -> sql;
+ (internal) -> mnesia;
+ (mnesia) -> mnesia
+ end;
mod_opt_type(default) ->
fun (always) -> always;
(never) -> never;
diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl
new file mode 100644
index 00000000..007ef5eb
--- /dev/null
+++ b/src/mod_mam_mnesia.erl
@@ -0,0 +1,178 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_mam_mnesia).
+
+-behaviour(mod_mam).
+
+%% API
+-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
+ extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]).
+
+-include_lib("stdlib/include/ms_transform.hrl").
+-include("jlib.hrl").
+-include("mod_mam.hrl").
+
+-define(BIN_GREATER_THAN(A, B),
+ ((A > B andalso byte_size(A) == byte_size(B))
+ orelse byte_size(A) > byte_size(B))).
+-define(BIN_LESS_THAN(A, B),
+ ((A < B andalso byte_size(A) == byte_size(B))
+ orelse byte_size(A) < byte_size(B))).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(archive_msg,
+ [{disc_only_copies, [node()]},
+ {type, bag},
+ {attributes, record_info(fields, archive_msg)}]),
+ mnesia:create_table(archive_prefs,
+ [{disc_only_copies, [node()]},
+ {attributes, record_info(fields, archive_prefs)}]).
+
+remove_user(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ mnesia:delete({archive_msg, US}),
+ mnesia:delete({archive_prefs, US})
+ end,
+ mnesia:transaction(F).
+
+remove_room(_LServer, LName, LHost) ->
+ remove_user(LName, LHost).
+
+delete_old_messages(global, TimeStamp, Type) ->
+ MS = ets:fun2ms(fun(#archive_msg{timestamp = MsgTS,
+ type = MsgType} = Msg)
+ when MsgTS < TimeStamp,
+ MsgType == Type orelse Type == all ->
+ Msg
+ end),
+ OldMsgs = mnesia:dirty_select(archive_msg, MS),
+ lists:foreach(fun(Rec) ->
+ ok = mnesia:dirty_delete_object(Rec)
+ end, OldMsgs).
+
+extended_fields() ->
+ [].
+
+store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir) ->
+ LPeer = {PUser, PServer, _} = jid:tolower(Peer),
+ TS = p1_time_compat:timestamp(),
+ ID = jlib:integer_to_binary(now_to_usec(TS)),
+ case mnesia:dirty_write(
+ #archive_msg{us = {LUser, LServer},
+ id = ID,
+ timestamp = TS,
+ peer = LPeer,
+ bare_peer = {PUser, PServer, <<>>},
+ type = Type,
+ nick = Nick,
+ packet = Pkt}) of
+ ok ->
+ {ok, ID};
+ Err ->
+ Err
+ end.
+
+write_prefs(_LUser, _LServer, Prefs, _ServerHost) ->
+ mnesia:dirty_write(Prefs).
+
+get_prefs(LUser, LServer) ->
+ case mnesia:dirty_read(archive_prefs, {LUser, LServer}) of
+ [Prefs] ->
+ {ok, Prefs};
+ _ ->
+ error
+ end.
+
+select(_LServer, JidRequestor,
+ #jid{luser = LUser, lserver = LServer} = JidArchive,
+ Start, End, With, RSM, MsgType) ->
+ MS = make_matchspec(LUser, LServer, Start, End, With),
+ Msgs = mnesia:dirty_select(archive_msg, MS),
+ SortedMsgs = lists:keysort(#archive_msg.timestamp, Msgs),
+ {FilteredMsgs, IsComplete} = filter_by_rsm(SortedMsgs, RSM),
+ Count = length(Msgs),
+ {lists:map(
+ fun(Msg) ->
+ {Msg#archive_msg.id,
+ jlib:binary_to_integer(Msg#archive_msg.id),
+ mod_mam:msg_to_el(Msg, MsgType, JidRequestor, JidArchive)}
+ end, FilteredMsgs), IsComplete, Count}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+now_to_usec({MSec, Sec, USec}) ->
+ (MSec*1000000 + Sec)*1000000 + USec.
+
+make_matchspec(LUser, LServer, Start, End, {_, _, <<>>} = With) ->
+ ets:fun2ms(
+ fun(#archive_msg{timestamp = TS,
+ us = US,
+ bare_peer = BPeer} = Msg)
+ when Start =< TS, End >= TS,
+ US == {LUser, LServer},
+ BPeer == With ->
+ Msg
+ end);
+make_matchspec(LUser, LServer, Start, End, {_, _, _} = With) ->
+ ets:fun2ms(
+ fun(#archive_msg{timestamp = TS,
+ us = US,
+ peer = Peer} = Msg)
+ when Start =< TS, End >= TS,
+ US == {LUser, LServer},
+ Peer == With ->
+ Msg
+ end);
+make_matchspec(LUser, LServer, Start, End, none) ->
+ ets:fun2ms(
+ fun(#archive_msg{timestamp = TS,
+ us = US,
+ peer = Peer} = Msg)
+ when Start =< TS, End >= TS,
+ US == {LUser, LServer} ->
+ Msg
+ end).
+
+filter_by_rsm(Msgs, none) ->
+ {Msgs, true};
+filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 ->
+ {[], true};
+filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) ->
+ NewMsgs = case Direction of
+ aft when ID /= <<"">> ->
+ lists:filter(
+ fun(#archive_msg{id = I}) ->
+ ?BIN_GREATER_THAN(I, ID)
+ end, Msgs);
+ before when ID /= <<"">> ->
+ lists:foldl(
+ fun(#archive_msg{id = I} = Msg, Acc)
+ when ?BIN_LESS_THAN(I, ID) ->
+ [Msg|Acc];
+ (_, Acc) ->
+ Acc
+ end, [], Msgs);
+ before when ID == <<"">> ->
+ lists:reverse(Msgs);
+ _ ->
+ Msgs
+ end,
+ filter_by_max(NewMsgs, Max).
+
+filter_by_max(Msgs, undefined) ->
+ {Msgs, true};
+filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 ->
+ {lists:sublist(Msgs, Len), length(Msgs) =< Len};
+filter_by_max(_Msgs, _Junk) ->
+ {[], true}.
diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl
new file mode 100644
index 00000000..69fdf323
--- /dev/null
+++ b/src/mod_mam_sql.erl
@@ -0,0 +1,309 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_mam_sql).
+
+-behaviour(mod_mam).
+
+%% API
+-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
+ extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]).
+
+-include_lib("stdlib/include/ms_transform.hrl").
+-include("jlib.hrl").
+-include("mod_mam.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+remove_user(LUser, LServer) ->
+ SUser = ejabberd_sql:escape(LUser),
+ ejabberd_sql:sql_query(
+ LServer,
+ [<<"delete from archive where username='">>, SUser, <<"';">>]),
+ ejabberd_sql:sql_query(
+ LServer,
+ [<<"delete from archive_prefs where username='">>, SUser, <<"';">>]).
+
+remove_room(LServer, LName, LHost) ->
+ LUser = jid:to_string({LName, LHost, <<>>}),
+ remove_user(LUser, LServer).
+
+delete_old_messages(ServerHost, TimeStamp, Type) ->
+ TypeClause = if Type == all -> <<"">>;
+ true -> [<<" and kind='">>, jlib:atom_to_binary(Type), <<"'">>]
+ end,
+ TS = integer_to_binary(now_to_usec(TimeStamp)),
+ ejabberd_sql:sql_query(
+ ServerHost, [<<"delete from archive where timestamp<">>,
+ TS, TypeClause, <<";">>]),
+ ok.
+
+extended_fields() ->
+ [#xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, <<"text-single">>},
+ {<<"var">>, <<"withtext">>}]}].
+
+store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) ->
+ TSinteger = p1_time_compat:system_time(micro_seconds),
+ ID = TS = jlib:integer_to_binary(TSinteger),
+ SUser = case Type of
+ chat -> LUser;
+ groupchat -> jid:to_string({LUser, LHost, <<>>})
+ end,
+ BarePeer = jid:to_string(
+ jid:tolower(
+ jid:remove_resource(Peer))),
+ LPeer = jid:to_string(
+ jid:tolower(Peer)),
+ XML = fxml:element_to_binary(Pkt),
+ Body = fxml:get_subtag_cdata(Pkt, <<"body">>),
+ case ejabberd_sql:sql_query(
+ LServer,
+ [<<"insert into archive (username, timestamp, "
+ "peer, bare_peer, xml, txt, kind, nick) values (">>,
+ <<"'">>, ejabberd_sql:escape(SUser), <<"', ">>,
+ <<"'">>, TS, <<"', ">>,
+ <<"'">>, ejabberd_sql:escape(LPeer), <<"', ">>,
+ <<"'">>, ejabberd_sql:escape(BarePeer), <<"', ">>,
+ <<"'">>, ejabberd_sql:escape(XML), <<"', ">>,
+ <<"'">>, ejabberd_sql:escape(Body), <<"', ">>,
+ <<"'">>, jlib:atom_to_binary(Type), <<"', ">>,
+ <<"'">>, ejabberd_sql:escape(Nick), <<"');">>]) of
+ {updated, _} ->
+ {ok, ID};
+ Err ->
+ Err
+ end.
+
+write_prefs(LUser, _LServer, #archive_prefs{default = Default,
+ never = Never,
+ always = Always},
+ ServerHost) ->
+ SUser = ejabberd_sql:escape(LUser),
+ SDefault = erlang:atom_to_binary(Default, utf8),
+ SAlways = ejabberd_sql:encode_term(Always),
+ SNever = ejabberd_sql:encode_term(Never),
+ case update(ServerHost, <<"archive_prefs">>,
+ [<<"username">>, <<"def">>, <<"always">>, <<"never">>],
+ [SUser, SDefault, SAlways, SNever],
+ [<<"username='">>, SUser, <<"'">>]) of
+ {updated, _} ->
+ ok;
+ Err ->
+ Err
+ end.
+
+get_prefs(LUser, LServer) ->
+ case ejabberd_sql:sql_query(
+ LServer,
+ [<<"select def, always, never from archive_prefs ">>,
+ <<"where username='">>,
+ ejabberd_sql:escape(LUser), <<"';">>]) of
+ {selected, _, [[SDefault, SAlways, SNever]]} ->
+ Default = erlang:binary_to_existing_atom(SDefault, utf8),
+ Always = ejabberd_sql:decode_term(SAlways),
+ Never = ejabberd_sql:decode_term(SNever),
+ {ok, #archive_prefs{us = {LUser, LServer},
+ default = Default,
+ always = Always,
+ never = Never}};
+ _ ->
+ error
+ end.
+
+select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
+ Start, End, With, RSM, MsgType) ->
+ User = case MsgType of
+ chat -> LUser;
+ {groupchat, _Role, _MUCState} -> jid:to_string(JidArchive)
+ end,
+ {Query, CountQuery} = make_sql_query(User, LServer,
+ Start, End, With, RSM),
+ % TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a
+ % reasonable limit on how many stanzas may be pushed to a client in one
+ % request. If a query returns a number of stanzas greater than this limit
+ % and the client did not specify a limit using RSM then the server should
+ % return a policy-violation error to the client." We currently don't do this
+ % for v0.2 requests, but we do limit #rsm_in.max for v0.3 and newer.
+ case {ejabberd_sql:sql_query(LServer, Query),
+ ejabberd_sql:sql_query(LServer, CountQuery)} of
+ {{selected, _, Res}, {selected, _, [[Count]]}} ->
+ {Max, Direction} = case RSM of
+ #rsm_in{max = M, direction = D} -> {M, D};
+ _ -> {undefined, undefined}
+ end,
+ {Res1, IsComplete} =
+ if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
+ if Direction == before ->
+ {lists:nthtail(1, Res), false};
+ true ->
+ {lists:sublist(Res, Max), false}
+ end;
+ true ->
+ {Res, true}
+ end,
+ {lists:flatmap(
+ fun([TS, XML, PeerBin, Kind, Nick]) ->
+ try
+ #xmlel{} = El = fxml_stream:parse_element(XML),
+ Now = usec_to_now(jlib:binary_to_integer(TS)),
+ PeerJid = jid:tolower(jid:from_string(PeerBin)),
+ T = case Kind of
+ <<"">> -> chat;
+ null -> chat;
+ _ -> jlib:binary_to_atom(Kind)
+ end,
+ [{TS, jlib:binary_to_integer(TS),
+ mod_mam:msg_to_el(#archive_msg{timestamp = Now,
+ packet = El,
+ type = T,
+ nick = Nick,
+ peer = PeerJid},
+ MsgType, JidRequestor, JidArchive)}]
+ catch _:Err ->
+ ?ERROR_MSG("failed to parse data from SQL: ~p. "
+ "The data was: "
+ "timestamp = ~s, xml = ~s, "
+ "peer = ~s, kind = ~s, nick = ~s",
+ [Err, TS, XML, PeerBin, Kind, Nick]),
+ []
+ end
+ end, Res1), IsComplete, jlib:binary_to_integer(Count)};
+ _ ->
+ {[], false, 0}
+ end.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+now_to_usec({MSec, Sec, USec}) ->
+ (MSec*1000000 + Sec)*1000000 + USec.
+
+usec_to_now(Int) ->
+ Secs = Int div 1000000,
+ USec = Int rem 1000000,
+ MSec = Secs div 1000000,
+ Sec = Secs rem 1000000,
+ {MSec, Sec, USec}.
+
+make_sql_query(User, LServer, Start, End, With, RSM) ->
+ {Max, Direction, ID} = case RSM of
+ #rsm_in{} ->
+ {RSM#rsm_in.max,
+ RSM#rsm_in.direction,
+ RSM#rsm_in.id};
+ none ->
+ {none, none, <<>>}
+ end,
+ ODBCType = ejabberd_config:get_option(
+ {sql_type, LServer},
+ ejabberd_sql:opt_type(sql_type)),
+ LimitClause = if is_integer(Max), Max >= 0, ODBCType /= mssql ->
+ [<<" limit ">>, jlib:integer_to_binary(Max+1)];
+ true ->
+ []
+ end,
+ TopClause = if is_integer(Max), Max >= 0, ODBCType == mssql ->
+ [<<" TOP ">>, jlib:integer_to_binary(Max+1)];
+ true ->
+ []
+ end,
+ WithClause = case With of
+ {text, <<>>} ->
+ [];
+ {text, Txt} ->
+ [<<" and match (txt) against ('">>,
+ ejabberd_sql:escape(Txt), <<"')">>];
+ {_, _, <<>>} ->
+ [<<" and bare_peer='">>,
+ ejabberd_sql:escape(jid:to_string(With)),
+ <<"'">>];
+ {_, _, _} ->
+ [<<" and peer='">>,
+ ejabberd_sql:escape(jid:to_string(With)),
+ <<"'">>];
+ none ->
+ []
+ end,
+ PageClause = case catch jlib:binary_to_integer(ID) of
+ I when is_integer(I), I >= 0 ->
+ case Direction of
+ before ->
+ [<<" AND timestamp < ">>, ID];
+ aft ->
+ [<<" AND timestamp > ">>, ID];
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end,
+ StartClause = case Start of
+ {_, _, _} ->
+ [<<" and timestamp >= ">>,
+ jlib:integer_to_binary(now_to_usec(Start))];
+ _ ->
+ []
+ end,
+ EndClause = case End of
+ {_, _, _} ->
+ [<<" and timestamp <= ">>,
+ jlib:integer_to_binary(now_to_usec(End))];
+ _ ->
+ []
+ end,
+ SUser = ejabberd_sql:escape(User),
+
+ Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick"
+ " FROM archive WHERE username='">>,
+ SUser, <<"'">>, WithClause, StartClause, EndClause,
+ PageClause],
+
+ QueryPage =
+ case Direction of
+ before ->
+ % ID can be empty because of
+ % XEP-0059: Result Set Management
+ % 2.5 Requesting the Last Page in a Result Set
+ [<<"SELECT timestamp, xml, peer, kind, nick FROM (">>, Query,
+ <<" ORDER BY timestamp DESC ">>,
+ LimitClause, <<") AS t ORDER BY timestamp ASC;">>];
+ _ ->
+ [Query, <<" ORDER BY timestamp ASC ">>,
+ LimitClause, <<";">>]
+ end,
+ {QueryPage,
+ [<<"SELECT COUNT(*) FROM archive WHERE username='">>,
+ SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}.
+
+update(LServer, Table, Fields, Vals, Where) ->
+ UPairs = lists:zipwith(fun (A, B) ->
+ <<A/binary, "='", B/binary, "'">>
+ end,
+ Fields, Vals),
+ case ejabberd_sql:sql_query(LServer,
+ [<<"update ">>, Table, <<" set ">>,
+ join(UPairs, <<", ">>), <<" where ">>, Where,
+ <<";">>])
+ of
+ {updated, 1} -> {updated, 1};
+ _ ->
+ ejabberd_sql:sql_query(LServer,
+ [<<"insert into ">>, Table, <<"(">>,
+ join(Fields, <<", ">>), <<") values ('">>,
+ join(Vals, <<"', '">>), <<"');">>])
+ end.
+
+%% Almost a copy of string:join/2.
+join([], _Sep) -> [];
+join([H | T], Sep) -> [H, [[Sep, X] || X <- T]].
diff --git a/src/mod_metrics.erl b/src/mod_metrics.erl
index 3e64da95..c175fcb8 100644
--- a/src/mod_metrics.erl
+++ b/src/mod_metrics.erl
@@ -39,7 +39,7 @@
s2s_send_packet, s2s_receive_packet,
remove_user, register_user]).
--export([start/2, stop/1, send_metrics/4]).
+-export([start/2, stop/1, send_metrics/4, opt_type/1]).
-export([offline_message_hook/3,
sm_register_connection_hook/3, sm_remove_connection_hook/3,
@@ -123,3 +123,6 @@ send_metrics(Host, Probe, Peer, Port) ->
Error ->
?WARNING_MSG("can not open udp socket to grapherl: ~p", [Error])
end.
+
+opt_type(_) ->
+ [].
diff --git a/src/mod_mix.erl b/src/mod_mix.erl
new file mode 100644
index 00000000..c0835b74
--- /dev/null
+++ b/src/mod_mix.erl
@@ -0,0 +1,348 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 2 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_mix).
+
+-behaviour(gen_server).
+-behaviour(gen_mod).
+
+%% API
+-export([start_link/2, start/2, stop/1, process_iq/3,
+ disco_items/5, disco_identity/5, disco_info/5,
+ disco_features/5, mod_opt_type/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-include("logger.hrl").
+-include("jlib.hrl").
+-include("pubsub.hrl").
+
+-define(PROCNAME, ejabberd_mod_mix).
+-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(),
+ host :: binary()}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+start_link(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+start(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ temporary, 5000, worker, [?MODULE]},
+ supervisor:start_child(ejabberd_sup, ChildSpec).
+
+stop(Host) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ supervisor:terminate_child(ejabberd_sup, Proc),
+ supervisor:delete_child(ejabberd_sup, Proc),
+ ok.
+
+disco_features(_Acc, _From, _To, _Node, _Lang) ->
+ {result, [?NS_MIX_0]}.
+
+disco_items(_Acc, _From, To, _Node, _Lang) when To#jid.luser /= <<"">> ->
+ To_s = jid:to_string(jid:remove_resource(To)),
+ {result, [#xmlel{name = <<"item">>,
+ attrs = [{<<"jid">>, To_s},
+ {<<"node">>, Node}]} || Node <- ?NODES]};
+disco_items(_Acc, _From, _To, _Node, _Lang) ->
+ {result, []}.
+
+disco_identity(Acc, _From, To, _Node, _Lang) when To#jid.luser == <<"">> ->
+ Acc ++ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"conference">>},
+ {<<"name">>, <<"MIX service">>},
+ {<<"type">>, <<"text">>}]}];
+disco_identity(Acc, _From, _To, _Node, _Lang) ->
+ Acc ++ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"conference">>},
+ {<<"type">>, <<"mix">>}]}].
+
+disco_info(_Acc, _From, To, _Node, _Lang) when is_atom(To) ->
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"result">>}],
+ children = [#xmlel{name = <<"field">>,
+ attrs = [{<<"var">>, <<"FORM_TYPE">>},
+ {<<"type">>, <<"hidden">>}],
+ children = [#xmlel{name = <<"value">>,
+ children = [{xmlcdata,
+ ?NS_MIX_SERVICEINFO_0}]}]}]}];
+disco_info(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+process_iq(From, To,
+ #iq{type = set, sub_el = #xmlel{name = <<"join">>} = SubEl} = IQ) ->
+ Nodes = lists:flatmap(
+ fun(#xmlel{name = <<"subscribe">>, attrs = Attrs}) ->
+ Node = fxml:get_attr_s(<<"node">>, Attrs),
+ case lists:member(Node, ?NODES) of
+ true -> [Node];
+ false -> []
+ end;
+ (_) ->
+ []
+ end, SubEl#xmlel.children),
+ case subscribe_nodes(From, To, Nodes) of
+ {result, _} ->
+ case publish_participant(From, To) of
+ {result, _} ->
+ LFrom_s = jid:to_string(jid:tolower(jid:remove_resource(From))),
+ Subscribe = [#xmlel{name = <<"subscribe">>,
+ attrs = [{<<"node">>, Node}]} || Node <- Nodes],
+ IQ#iq{type = result,
+ sub_el = [#xmlel{name = <<"join">>,
+ attrs = [{<<"jid">>, LFrom_s},
+ {<<"xmlns">>, ?NS_MIX_0}],
+ children = Subscribe}]};
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+process_iq(From, To,
+ #iq{type = set, sub_el = #xmlel{name = <<"leave">>} = SubEl} = IQ) ->
+ case delete_participant(From, To) of
+ {result, _} ->
+ case unsubscribe_nodes(From, To, ?NODES) of
+ {result, _} ->
+ IQ#iq{type = result, sub_el = []};
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+process_iq(_From, _To, #iq{sub_el = SubEl, lang = Lang} = IQ) ->
+ Txt = <<"Unsupported MIX query">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+init([ServerHost, Opts]) ->
+ Host = gen_mod:get_opt_host(ServerHost, Opts, <<"mix.@HOST@">>),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ one_queue),
+ 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, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_DISCO_INFO, mod_disco,
+ process_local_iq_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_DISCO_ITEMS, mod_disco,
+ process_local_iq_items, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_DISCO_INFO, mod_disco,
+ process_local_iq_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_MIX_0, ?MODULE, process_iq, IQDisc),
+ ejabberd_router:register_route(Host, ServerHost),
+ {ok, #state{server_host = ServerHost, host = Host}}.
+
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info({route, From, To, Packet}, State) ->
+ case catch do_route(State, From, To, Packet) of
+ {'EXIT', _} = Err ->
+ try
+ ?ERROR_MSG("failed to route packet ~p from '~s' to '~s': ~p",
+ [Packet, jid:to_string(From), jid:to_string(To), Err]),
+ ErrPkt = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR),
+ ejabberd_router:route_error(To, From, ErrPkt, Packet)
+ catch _:_ ->
+ ok
+ end;
+ _ ->
+ ok
+ end,
+ {noreply, State};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, #state{host = 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),
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+do_route(_State, From, To, #xmlel{name = <<"iq">>} = Packet) ->
+ if To#jid.luser == <<"">> ->
+ ejabberd_local:process_iq(From, To, Packet);
+ true ->
+ ejabberd_sm:process_iq(From, To, Packet)
+ end;
+do_route(_State, From, To, #xmlel{name = <<"presence">>} = Packet)
+ when To#jid.luser /= <<"">> ->
+ case fxml:get_tag_attr_s(<<"type">>, Packet) of
+ <<"unavailable">> ->
+ delete_presence(From, To);
+ _ ->
+ ok
+ end;
+do_route(_State, _From, _To, _Packet) ->
+ ok.
+
+subscribe_nodes(From, To, Nodes) ->
+ LTo = jid:tolower(jid:remove_resource(To)),
+ LFrom = jid:tolower(jid:remove_resource(From)),
+ From_s = jid:to_string(LFrom),
+ lists:foldl(
+ fun(_Node, {error, _} = Err) ->
+ Err;
+ (Node, {result, _}) ->
+ case mod_pubsub:subscribe_node(LTo, Node, From, From_s, []) 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_s, []);
+ 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)),
+ LFrom = jid:tolower(jid:remove_resource(From)),
+ From_s = jid:to_string(LFrom),
+ lists:foldl(
+ fun(_Node, {error, _} = Err) ->
+ Err;
+ (Node, {result, _} = Result) ->
+ case mod_pubsub:unsubscribe_node(LTo, Node, From, From_s, <<"">>) of
+ {error, _} = Err ->
+ case is_not_subscribed(Err) of
+ true -> Result;
+ _ -> Err
+ end;
+ {result, _} = Res ->
+ Res
+ end
+ end, {result, []}, Nodes).
+
+publish_participant(From, To) ->
+ LFrom = jid:tolower(jid:remove_resource(From)),
+ LTo = jid:tolower(jid:remove_resource(To)),
+ Participant = #xmlel{name = <<"participant">>,
+ attrs = [{<<"xmlns">>, ?NS_MIX_0},
+ {<<"jid">>, jid:to_string(LFrom)}]},
+ ItemID = p1_sha:sha(jid:to_string(LFrom)),
+ mod_pubsub:publish_item(
+ LTo, To#jid.lserver, ?NS_MIX_NODES_PARTICIPANTS,
+ From, ItemID, [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{modification = {_, LJID},
+ itemid = {ItemID, _}}) 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 = p1_sha:sha(jid:to_string(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.
+
+is_item_not_found({error, ErrEl}) ->
+ case fxml:get_subtag_with_xmlns(
+ ErrEl, <<"item-not-found">>, ?NS_STANZAS) of
+ #xmlel{} -> true;
+ _ -> false
+ end.
+
+is_not_subscribed({error, ErrEl}) ->
+ case fxml:get_subtag_with_xmlns(
+ ErrEl, <<"not-subscribed">>, ?NS_PUBSUB_ERRORS) of
+ #xmlel{} -> true;
+ _ -> false
+ end.
+
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(_) -> [host, iqdisc].
diff --git a/src/mod_muc.erl b/src/mod_muc.erl
index a64a0032..6aa18631 100644
--- a/src/mod_muc.erl
+++ b/src/mod_muc.erl
@@ -48,6 +48,7 @@
export/1,
import/1,
import/3,
+ opts_to_binary/1,
can_use_nick/4]).
-export([init/1, handle_call/3, handle_cast/2,
@@ -72,6 +73,17 @@
-define(MAX_ROOMS_DISCOITEMS, 100).
+-type muc_room_opts() :: [{atom(), any()}].
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #muc_room{} | #muc_registered{}) -> ok | pass.
+-callback store_room(binary(), binary(), binary(), list()) -> {atomic, any()}.
+-callback restore_room(binary(), binary(), binary()) -> muc_room_opts() | error.
+-callback forget_room(binary(), binary(), binary()) -> {atomic, any()}.
+-callback can_use_nick(binary(), binary(), jid(), binary()) -> boolean().
+-callback get_rooms(binary(), binary()) -> [#muc_room{}].
+-callback get_nick(binary(), binary(), jid()) -> binary() | error.
+-callback set_nick(binary(), binary(), jid(), binary()) -> {atomic, ok | false}.
+
%%====================================================================
%% API
%%====================================================================
@@ -125,96 +137,24 @@ create_room(Host, Name, From, Nick, Opts) ->
store_room(ServerHost, Host, Name, Opts) ->
LServer = jid:nameprep(ServerHost),
- store_room(LServer, Host, Name, Opts,
- gen_mod:db_type(LServer, ?MODULE)).
-
-store_room(_LServer, Host, Name, Opts, mnesia) ->
- F = fun () ->
- mnesia:write(#muc_room{name_host = {Name, Host},
- opts = Opts})
- end,
- mnesia:transaction(F);
-store_room(_LServer, Host, Name, Opts, riak) ->
- {atomic, ejabberd_riak:put(#muc_room{name_host = {Name, Host},
- opts = Opts},
- muc_room_schema())};
-store_room(LServer, Host, Name, Opts, odbc) ->
- SName = ejabberd_odbc:escape(Name),
- SHost = ejabberd_odbc:escape(Host),
- SOpts = ejabberd_odbc:encode_term(Opts),
- F = fun () ->
- odbc_queries:update_t(<<"muc_room">>,
- [<<"name">>, <<"host">>, <<"opts">>],
- [SName, SHost, SOpts],
- [<<"name='">>, SName, <<"' and host='">>,
- SHost, <<"'">>])
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:store_room(LServer, Host, Name, Opts).
restore_room(ServerHost, Host, Name) ->
LServer = jid:nameprep(ServerHost),
- restore_room(LServer, Host, Name,
- gen_mod:db_type(LServer, ?MODULE)).
-
-restore_room(_LServer, Host, Name, mnesia) ->
- case catch mnesia:dirty_read(muc_room, {Name, Host}) of
- [#muc_room{opts = Opts}] -> Opts;
- _ -> error
- end;
-restore_room(_LServer, Host, Name, riak) ->
- case ejabberd_riak:get(muc_room, muc_room_schema(), {Name, Host}) of
- {ok, #muc_room{opts = Opts}} -> Opts;
- _ -> error
- end;
-restore_room(LServer, Host, Name, odbc) ->
- SName = ejabberd_odbc:escape(Name),
- SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select opts from muc_room where name='">>,
- SName, <<"' and host='">>, SHost,
- <<"';">>])
- of
- {selected, [<<"opts">>], [[Opts]]} ->
- opts_to_binary(ejabberd_odbc:decode_term(Opts));
- _ -> error
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:restore_room(LServer, Host, Name).
forget_room(ServerHost, Host, Name) ->
LServer = jid:nameprep(ServerHost),
- forget_room(LServer, Host, Name,
- gen_mod:db_type(LServer, ?MODULE)).
-
-forget_room(LServer, Host, Name, mnesia) ->
remove_room_mam(LServer, Host, Name),
- F = fun () -> mnesia:delete({muc_room, {Name, Host}})
- end,
- mnesia:transaction(F);
-forget_room(LServer, Host, Name, riak) ->
- remove_room_mam(LServer, Host, Name),
- {atomic, ejabberd_riak:delete(muc_room, {Name, Host})};
-forget_room(LServer, Host, Name, odbc) ->
- remove_room_mam(LServer, Host, Name),
- SName = ejabberd_odbc:escape(Name),
- SHost = ejabberd_odbc:escape(Host),
- F = fun () ->
- ejabberd_odbc:sql_query_t([<<"delete from muc_room where name='">>,
- SName, <<"' and host='">>, SHost,
- <<"';">>])
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:forget_room(LServer, Host, Name).
remove_room_mam(LServer, Host, Name) ->
case gen_mod:is_loaded(LServer, mod_mam) of
true ->
- U = jid:nodeprep(Name),
- S = jid:nameprep(Host),
- DBType = gen_mod:db_type(LServer, mod_mam),
- if DBType == odbc ->
- mod_mam:remove_user(jid:to_string({U, S, <<>>}),
- LServer, DBType);
- true ->
- mod_mam:remove_user(U, S, DBType)
- end;
+ mod_mam:remove_room(LServer, Name, Host);
false ->
ok
end.
@@ -222,7 +162,7 @@ remove_room_mam(LServer, Host, Name) ->
process_iq_disco_items(Host, From, To,
#iq{lang = Lang} = IQ) ->
Rsm = jlib:rsm_decode(IQ),
- DiscoNode = xml:get_tag_attr_s(<<"node">>, IQ#iq.sub_el),
+ DiscoNode = fxml:get_tag_attr_s(<<"node">>, IQ#iq.sub_el),
Res = IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
@@ -233,48 +173,8 @@ process_iq_disco_items(Host, From, To,
can_use_nick(_ServerHost, _Host, _JID, <<"">>) -> false;
can_use_nick(ServerHost, Host, JID, Nick) ->
LServer = jid:nameprep(ServerHost),
- can_use_nick(LServer, Host, JID, Nick,
- gen_mod:db_type(LServer, ?MODULE)).
-
-can_use_nick(_LServer, Host, JID, Nick, mnesia) ->
- {LUser, LServer, _} = jid:tolower(JID),
- LUS = {LUser, LServer},
- case catch mnesia:dirty_select(muc_registered,
- [{#muc_registered{us_host = '$1',
- nick = Nick, _ = '_'},
- [{'==', {element, 2, '$1'}, Host}],
- ['$_']}])
- of
- {'EXIT', _Reason} -> true;
- [] -> true;
- [#muc_registered{us_host = {U, _Host}}] -> U == LUS
- end;
-can_use_nick(LServer, Host, JID, Nick, riak) ->
- {LUser, LServer, _} = jid:tolower(JID),
- LUS = {LUser, LServer},
- case ejabberd_riak:get_by_index(muc_registered,
- muc_registered_schema(),
- <<"nick_host">>, {Nick, Host}) of
- {ok, []} ->
- true;
- {ok, [#muc_registered{us_host = {U, _Host}}]} ->
- U == LUS;
- {error, _} ->
- true
- end;
-can_use_nick(LServer, Host, JID, Nick, odbc) ->
- SJID =
- jid:to_string(jid:tolower(jid:remove_resource(JID))),
- SNick = ejabberd_odbc:escape(Nick),
- SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select jid from muc_registered ">>,
- <<"where nick='">>, SNick,
- <<"' and host='">>, SHost, <<"';">>])
- of
- {selected, [<<"jid">>], [[SJID1]]} -> SJID == SJID1;
- _ -> true
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:can_use_nick(LServer, Host, JID, Nick).
%%====================================================================
%% gen_server callbacks
@@ -283,21 +183,8 @@ can_use_nick(LServer, Host, JID, Nick, odbc) ->
init([Host, Opts]) ->
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"conference.@HOST@">>),
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(muc_room,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, muc_room)}]),
- mnesia:create_table(muc_registered,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, muc_registered)}]),
- update_tables(MyHost),
- mnesia:add_table_index(muc_registered, nick);
- _ ->
- ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, [{host, MyHost}|Opts]),
mnesia:create_table(muc_online_room,
[{ram_copies, [node()]},
{attributes, record_info(fields, muc_online_room)}]),
@@ -373,7 +260,7 @@ init([Host, Opts]) ->
RoomShaper = gen_mod:get_opt(room_shaper, Opts,
fun(A) when is_atom(A) -> A end,
none),
- ejabberd_router:register_route(MyHost),
+ ejabberd_router:register_route(MyHost, Host),
load_permanent_rooms(MyHost, Host,
{Access, AccessCreate, AccessAdmin, AccessPersistent},
HistorySize, RoomShaper),
@@ -451,7 +338,7 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts);
_ ->
#xmlel{attrs = Attrs} = Packet,
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText = <<"Access denied by service policy">>,
Err = jlib:make_error_reply(Packet,
?ERRT_FORBIDDEN(Lang, ErrText)),
@@ -557,18 +444,18 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
_ -> ok
end;
<<"message">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
_ ->
case acl:match_rule(ServerHost, AccessAdmin, From)
of
allow ->
- Msg = xml:get_path_s(Packet,
+ Msg = fxml:get_path_s(Packet,
[{elem, <<"body">>},
cdata]),
broadcast_service_message(Host, Msg);
_ ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText =
<<"Only service administrators are allowed "
"to send service messages">>,
@@ -581,7 +468,7 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
<<"presence">> -> ok
end;
_ ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
@@ -593,11 +480,12 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
_ ->
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
[] ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
case {Name, Type} of
{<<"presence">>, <<"">>} ->
case check_user_can_create_room(ServerHost,
- AccessCreate, From, Room) of
+ AccessCreate, From, Room) and
+ check_create_roomid(ServerHost, Room) of
true ->
{ok, Pid} = start_new_room(Host, ServerHost, Access,
Room, HistorySize,
@@ -606,14 +494,14 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
mod_muc_room:route(Pid, From, Nick, Packet),
ok;
false ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText = <<"Room creation is denied by service policy">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
ejabberd_router:route(To, From, Err)
end;
_ ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText = <<"Conference room does not exist">>,
Err = jlib:make_error_reply(Packet,
?ERRT_ITEM_NOT_FOUND(Lang, ErrText)),
@@ -628,56 +516,26 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
end.
check_user_can_create_room(ServerHost, AccessCreate,
- From, RoomID) ->
+ From, _RoomID) ->
case acl:match_rule(ServerHost, AccessCreate, From) of
- allow ->
- byte_size(RoomID) =<
- gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id,
- fun(infinity) -> infinity;
- (I) when is_integer(I), I>0 -> I
- end, infinity);
+ allow -> true;
_ -> false
end.
+check_create_roomid(ServerHost, RoomID) ->
+ Max = gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id,
+ fun(infinity) -> infinity;
+ (I) when is_integer(I), I>0 -> I
+ end, infinity),
+ Regexp = gen_mod:get_module_opt(ServerHost, ?MODULE, regexp_room_id,
+ fun iolist_to_binary/1, ""),
+ (byte_size(RoomID) =< Max) and
+ (re:run(RoomID, Regexp, [unicode, {capture, none}]) == match).
+
get_rooms(ServerHost, Host) ->
LServer = jid:nameprep(ServerHost),
- get_rooms(LServer, Host,
- gen_mod:db_type(LServer, ?MODULE)).
-
-get_rooms(_LServer, Host, mnesia) ->
- case catch mnesia:dirty_select(muc_room,
- [{#muc_room{name_host = {'_', Host},
- _ = '_'},
- [], ['$_']}])
- of
- {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), [];
- Rs -> Rs
- end;
-get_rooms(_LServer, Host, riak) ->
- case ejabberd_riak:get(muc_room, muc_room_schema()) of
- {ok, Rs} ->
- lists:filter(
- fun(#muc_room{name_host = {_, H}}) ->
- Host == H
- end, Rs);
- _Err ->
- []
- end;
-get_rooms(LServer, Host, odbc) ->
- SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select name, opts from muc_room ">>,
- <<"where host='">>, SHost, <<"';">>])
- of
- {selected, [<<"name">>, <<"opts">>], RoomOpts} ->
- lists:map(fun ([Room, Opts]) ->
- #muc_room{name_host = {Room, Host},
- opts = opts_to_binary(
- ejabberd_odbc:decode_term(Opts))}
- end,
- RoomOpts);
- Err -> ?ERROR_MSG("failed to get rooms: ~p", [Err]), []
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_rooms(LServer, Host).
load_permanent_rooms(Host, ServerHost, Access,
HistorySize, RoomShaper) ->
@@ -867,41 +725,8 @@ iq_get_unique(From) ->
get_nick(ServerHost, Host, From) ->
LServer = jid:nameprep(ServerHost),
- get_nick(LServer, Host, From,
- gen_mod:db_type(LServer, ?MODULE)).
-
-get_nick(_LServer, Host, From, mnesia) ->
- {LUser, LServer, _} = jid:tolower(From),
- LUS = {LUser, LServer},
- case catch mnesia:dirty_read(muc_registered,
- {LUS, Host})
- of
- {'EXIT', _Reason} -> error;
- [] -> error;
- [#muc_registered{nick = Nick}] -> Nick
- end;
-get_nick(LServer, Host, From, riak) ->
- {LUser, LServer, _} = jid:tolower(From),
- US = {LUser, LServer},
- case ejabberd_riak:get(muc_registered,
- muc_registered_schema(),
- {US, Host}) of
- {ok, #muc_registered{nick = Nick}} -> Nick;
- {error, _} -> error
- end;
-get_nick(LServer, Host, From, odbc) ->
- SJID =
- ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
- SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select nick from muc_registered where "
- "jid='">>,
- SJID, <<"' and host='">>, SHost,
- <<"';">>])
- of
- {selected, [<<"nick">>], [[Nick]]} -> Nick;
- _ -> error
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_nick(LServer, Host, From).
iq_get_register_info(ServerHost, Host, From, Lang) ->
{Nick, Registered} = case get_nick(ServerHost, Host,
@@ -940,107 +765,8 @@ iq_get_register_info(ServerHost, Host, From, Lang) ->
set_nick(ServerHost, Host, From, Nick) ->
LServer = jid:nameprep(ServerHost),
- set_nick(LServer, Host, From, Nick,
- gen_mod:db_type(LServer, ?MODULE)).
-
-set_nick(_LServer, Host, From, Nick, mnesia) ->
- {LUser, LServer, _} = jid:tolower(From),
- LUS = {LUser, LServer},
- F = fun () ->
- case Nick of
- <<"">> ->
- mnesia:delete({muc_registered, {LUS, Host}}), ok;
- _ ->
- Allow = case mnesia:select(muc_registered,
- [{#muc_registered{us_host =
- '$1',
- nick = Nick,
- _ = '_'},
- [{'==', {element, 2, '$1'},
- Host}],
- ['$_']}])
- of
- [] -> true;
- [#muc_registered{us_host = {U, _Host}}] ->
- U == LUS
- end,
- if Allow ->
- mnesia:write(#muc_registered{us_host = {LUS, Host},
- nick = Nick}),
- ok;
- true -> false
- end
- end
- end,
- mnesia:transaction(F);
-set_nick(LServer, Host, From, Nick, riak) ->
- {LUser, LServer, _} = jid:tolower(From),
- LUS = {LUser, LServer},
- {atomic,
- case Nick of
- <<"">> ->
- ejabberd_riak:delete(muc_registered, {LUS, Host});
- _ ->
- Allow = case ejabberd_riak:get_by_index(
- muc_registered,
- muc_registered_schema(),
- <<"nick_host">>, {Nick, Host}) of
- {ok, []} ->
- true;
- {ok, [#muc_registered{us_host = {U, _Host}}]} ->
- U == LUS;
- {error, _} ->
- false
- end,
- if Allow ->
- ejabberd_riak:put(#muc_registered{us_host = {LUS, Host},
- nick = Nick},
- muc_registered_schema(),
- [{'2i', [{<<"nick_host">>,
- {Nick, Host}}]}]);
- true ->
- false
- end
- end};
-set_nick(LServer, Host, From, Nick, odbc) ->
- JID =
- jid:to_string(jid:tolower(jid:remove_resource(From))),
- SJID = ejabberd_odbc:escape(JID),
- SNick = ejabberd_odbc:escape(Nick),
- SHost = ejabberd_odbc:escape(Host),
- F = fun () ->
- case Nick of
- <<"">> ->
- ejabberd_odbc:sql_query_t([<<"delete from muc_registered where ">>,
- <<"jid='">>, SJID,
- <<"' and host='">>, Host,
- <<"';">>]),
- ok;
- _ ->
- Allow = case
- ejabberd_odbc:sql_query_t([<<"select jid from muc_registered ">>,
- <<"where nick='">>,
- SNick,
- <<"' and host='">>,
- SHost, <<"';">>])
- of
- {selected, [<<"jid">>], [[J]]} -> J == JID;
- _ -> true
- end,
- if Allow ->
- odbc_queries:update_t(<<"muc_registered">>,
- [<<"jid">>, <<"host">>,
- <<"nick">>],
- [SJID, SHost, SNick],
- [<<"jid='">>, SJID,
- <<"' and host='">>, SHost,
- <<"'">>]),
- ok;
- true -> false
- end
- end
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:set_nick(LServer, Host, From, Nick).
iq_set_register_info(ServerHost, Host, From, Nick,
Lang) ->
@@ -1050,24 +776,28 @@ iq_set_register_info(ServerHost, Host, From, Nick,
ErrText = <<"That nickname is registered by another "
"person">>,
{error, ?ERRT_CONFLICT(Lang, ErrText)};
- _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
+ _ ->
+ Txt = <<"Database failure">>,
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)}
end.
process_iq_register_set(ServerHost, Host, From, SubEl,
Lang) ->
#xmlel{children = Els} = SubEl,
- case xml:get_subtag(SubEl, <<"remove">>) of
+ case fxml:get_subtag(SubEl, <<"remove">>) of
false ->
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = <<"x">>} = XEl] ->
- case {xml:get_tag_attr_s(<<"xmlns">>, XEl),
- xml:get_tag_attr_s(<<"type">>, XEl)}
+ case {fxml:get_tag_attr_s(<<"xmlns">>, XEl),
+ fxml:get_tag_attr_s(<<"type">>, XEl)}
of
{?NS_XDATA, <<"cancel">>} -> {result, []};
{?NS_XDATA, <<"submit">>} ->
XData = jlib:parse_xdata_submit(XEl),
case XData of
- invalid -> {error, ?ERR_BAD_REQUEST};
+ invalid ->
+ Txt = <<"Incorrect data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
_ ->
case lists:keysearch(<<"nick">>, 1, XData) of
{value, {_, [Nick]}} when Nick /= <<"">> ->
@@ -1182,118 +912,17 @@ opts_to_binary(Opts) ->
Opt
end, Opts).
-update_tables(Host) ->
- update_muc_room_table(Host),
- update_muc_registered_table(Host).
-
-muc_room_schema() ->
- {record_info(fields, muc_room), #muc_room{}}.
-
-muc_registered_schema() ->
- {record_info(fields, muc_registered), #muc_registered{}}.
-
-update_muc_room_table(_Host) ->
- Fields = record_info(fields, muc_room),
- case mnesia:table_info(muc_room, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- muc_room, Fields, set,
- fun(#muc_room{name_host = {N, _}}) -> N end,
- fun(#muc_room{name_host = {N, H},
- opts = Opts} = R) ->
- R#muc_room{name_host = {iolist_to_binary(N),
- iolist_to_binary(H)},
- opts = opts_to_binary(Opts)}
- end);
- _ ->
- ?INFO_MSG("Recreating muc_room table", []),
- mnesia:transform_table(muc_room, ignore, Fields)
- end.
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
-update_muc_registered_table(_Host) ->
- Fields = record_info(fields, muc_registered),
- case mnesia:table_info(muc_registered, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- muc_registered, Fields, set,
- fun(#muc_registered{us_host = {_, H}}) -> H end,
- fun(#muc_registered{us_host = {{U, S}, H},
- nick = Nick} = R) ->
- R#muc_registered{us_host = {{iolist_to_binary(U),
- iolist_to_binary(S)},
- iolist_to_binary(H)},
- nick = iolist_to_binary(Nick)}
- end);
- _ ->
- ?INFO_MSG("Recreating muc_registered table", []),
- mnesia:transform_table(muc_registered, ignore, Fields)
- end.
+import(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
-export(_Server) ->
- [{muc_room,
- fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->
- case str:suffix(Host, RoomHost) of
- true ->
- SName = ejabberd_odbc:escape(Name),
- SRoomHost = ejabberd_odbc:escape(RoomHost),
- SOpts = ejabberd_odbc:encode_term(Opts),
- [[<<"delete from muc_room where name='">>, SName,
- <<"' and host='">>, SRoomHost, <<"';">>],
- [<<"insert into muc_room(name, host, opts) ",
- "values (">>,
- <<"'">>, SName, <<"', '">>, SRoomHost,
- <<"', '">>, SOpts, <<"');">>]];
- false ->
- []
- end
- end},
- {muc_registered,
- fun(Host, #muc_registered{us_host = {{U, S}, RoomHost},
- nick = Nick}) ->
- case str:suffix(Host, RoomHost) of
- true ->
- SJID = ejabberd_odbc:escape(
- jid:to_string(
- jid:make(U, S, <<"">>))),
- SNick = ejabberd_odbc:escape(Nick),
- SRoomHost = ejabberd_odbc:escape(RoomHost),
- [[<<"delete from muc_registered where jid='">>,
- SJID, <<"' and host='">>, SRoomHost, <<"';">>],
- [<<"insert into muc_registered(jid, host, "
- "nick) values ('">>,
- SJID, <<"', '">>, SRoomHost, <<"', '">>, SNick,
- <<"');">>]];
- false ->
- []
- end
- end}].
-
-import(_LServer) ->
- [{<<"select name, host, opts from muc_room;">>,
- fun([Name, RoomHost, SOpts]) ->
- Opts = opts_to_binary(ejabberd_odbc:decode_term(SOpts)),
- #muc_room{name_host = {Name, RoomHost}, opts = Opts}
- end},
- {<<"select jid, host, nick from muc_registered;">>,
- fun([J, RoomHost, Nick]) ->
- #jid{user = U, server = S} =
- jid:from_string(J),
- #muc_registered{us_host = {{U, S}, RoomHost},
- nick = Nick}
- end}].
-
-import(_LServer, mnesia, #muc_room{} = R) ->
- mnesia:dirty_write(R);
-import(_LServer, mnesia, #muc_registered{} = R) ->
- mnesia:dirty_write(R);
-import(_LServer, riak, #muc_room{} = R) ->
- ejabberd_riak:put(R, muc_room_schema());
-import(_LServer, riak,
- #muc_registered{us_host = {_, Host}, nick = Nick} = R) ->
- ejabberd_riak:put(R, muc_registered_schema(),
- [{'2i', [{<<"nick_host">>, {Nick, Host}}]}]);
-import(_, _, _) ->
- pass.
+import(LServer, DBType, Data) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, Data).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
@@ -1317,6 +946,8 @@ mod_opt_type(max_room_id) ->
fun (infinity) -> infinity;
(I) when is_integer(I), I > 0 -> I
end;
+mod_opt_type(regexp_room_id) ->
+ fun iolist_to_binary/1;
mod_opt_type(max_room_name) ->
fun (infinity) -> infinity;
(I) when is_integer(I), I > 0 -> I
@@ -1342,7 +973,7 @@ mod_opt_type(user_presence_shaper) ->
mod_opt_type(_) ->
[access, access_admin, access_create, access_persistent,
db_type, default_room_options, history_size, host,
- max_room_desc, max_room_id, max_room_name,
+ max_room_desc, max_room_id, max_room_name, regexp_room_id,
max_user_conferences, max_users,
max_users_admin_threshold, max_users_presence,
min_message_interval, min_presence_interval,
diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl
index c0875737..5fbda4f2 100644
--- a/src/mod_muc_admin.erl
+++ b/src/mod_muc_admin.erl
@@ -178,7 +178,8 @@ muc_online_rooms(ServerHost) ->
MUCHost = find_host(ServerHost),
Rooms = ets:tab2list(muc_online_room),
lists:foldl(
- fun({_, {Roomname, Host}, _}, Results) ->
+ fun(Room, Results) ->
+ {Roomname, Host} = Room#muc_online_room.name_host,
case MUCHost of
global ->
[<<Roomname/binary, "@", Host/binary>> | Results];
@@ -279,7 +280,7 @@ get_sort_query(Q) ->
get_sort_query2(Q) ->
{value, {_, String}} = lists:keysearch(<<"sort">>, 1, Q),
- Integer = list_to_integer(binary_to_list(String)),
+ Integer = jlib:binary_to_integer(String),
case Integer >= 0 of
true -> {ok, {normal, Integer}};
false -> {ok, {reverse, abs(Integer)}}
@@ -395,7 +396,9 @@ prepare_room_info(Room_info) ->
%% @spec (Name::binary(), Host::binary(), ServerHost::binary()) ->
%% ok | error
%% @doc Create a room immediately with the default options.
-create_room(Name, Host, ServerHost) ->
+create_room(Name1, Host1, ServerHost) ->
+ Name = jid:nodeprep(Name1),
+ Host = jid:nodeprep(Host1),
%% Get the default room options from the muc configuration
DefRoomOpts = gen_mod:get_module_opt(ServerHost, mod_muc,
@@ -471,7 +474,7 @@ destroy_room({N, H, SH}) ->
%% The file encoding must be UTF-8
destroy_rooms_file(Filename) ->
- {ok, F} = file:open(Filename, [read]),
+ {ok, F} = file:open(Filename, [read, binary]),
RJID = read_room(F),
Rooms = read_rooms(F, RJID, []),
file:close(F),
@@ -499,23 +502,16 @@ read_room(F) ->
%% This function is quite rudimentary
%% and may not be accurate
split_roomjid(RoomJID) ->
- [Name, Host] = string:tokens(RoomJID, "@"),
- [_MUC_service_name | ServerHostList] = string:tokens(Host, "."),
- ServerHost = join(ServerHostList, "."),
- {list_to_binary(Name), list_to_binary(Host), list_to_binary(ServerHost)}.
-
-%% This function is copied from string:join/2 in Erlang/OTP R12B-1
-%% Note that string:join/2 is not implemented in Erlang/OTP R11B
-join([H|T], Sep) ->
- H ++ lists:concat([Sep ++ X || X <- T]).
-
+ [Name, Host] = binary:split(RoomJID, <<"@">>),
+ [_MUC_service_name, ServerHost] = binary:split(Host, <<".">>),
+ {Name, Host, ServerHost}.
%%----------------------------
%% Create Rooms in File
%%----------------------------
create_rooms_file(Filename) ->
- {ok, F} = file:open(Filename, [read]),
+ {ok, F} = file:open(Filename, [read, binary]),
RJID = read_room(F),
Rooms = read_rooms(F, RJID, []),
file:close(F),
@@ -692,29 +688,32 @@ send_direct_invitation(RoomName, RoomService, Password, Reason, UsersString) ->
RoomJid = jid:make(RoomName, RoomService, <<"">>),
RoomString = jid:to_string(RoomJid),
XmlEl = build_invitation(Password, Reason, RoomString),
- UsersStrings = get_users_to_invite(RoomJid, binary_to_list(UsersString)),
- [send_direct_invitation(RoomJid, jid:from_string(list_to_binary(UserStrings)), XmlEl)
+ UsersStrings = get_users_to_invite(RoomJid, UsersString),
+ [send_direct_invitation(RoomJid, UserStrings, XmlEl)
|| UserStrings <- UsersStrings],
timer:sleep(1000),
ok.
get_users_to_invite(RoomJid, UsersString) ->
- UsersStrings = string:tokens(UsersString, ":"),
+ UsersStrings = binary:split(UsersString, <<":">>, [global]),
OccupantsTuples = get_room_occupants(RoomJid#jid.luser,
RoomJid#jid.lserver),
OccupantsJids = [jid:from_string(JidString)
|| {JidString, _Nick, _} <- OccupantsTuples],
- lists:filter(
- fun(UserString) ->
- UserJid = jid:from_string(list_to_binary(UserString)),
- %% [{"badlop@localhost/work","badlop","moderator"}]
- lists:all(fun(OccupantJid) ->
- UserJid#jid.luser /= OccupantJid#jid.luser
- orelse UserJid#jid.lserver /= OccupantJid#jid.lserver
- end,
- OccupantsJids)
- end,
- UsersStrings).
+ lists:filtermap(
+ fun(UserString) ->
+ UserJid = jid:from_string(UserString),
+ Val = lists:all(fun(OccupantJid) ->
+ UserJid#jid.luser /= OccupantJid#jid.luser
+ orelse UserJid#jid.lserver /= OccupantJid#jid.lserver
+ end,
+ OccupantsJids),
+ case Val of
+ true -> {true, UserJid};
+ _ -> false
+ end
+ end,
+ UsersStrings).
build_invitation(Password, Reason, RoomString) ->
PasswordAttrList = case Password of
@@ -825,8 +824,12 @@ get_room_options(Pid) ->
get_options(Config).
get_options(Config) ->
- Fields = record_info(fields, config),
- [config | Values] = tuple_to_list(Config),
+ Fields = [jlib:atom_to_binary(Field) || Field <- record_info(fields, config)],
+ [config | ValuesRaw] = tuple_to_list(Config),
+ Values = lists:map(fun(V) when is_atom(V) -> jlib:atom_to_binary(V);
+ (V) when is_integer(V) -> jlib:integer_to_binary(V);
+ (V) when is_tuple(V); is_list(V) -> list_to_binary(hd(io_lib:format("~w", [V])));
+ (V) -> V end, ValuesRaw),
lists:zip(Fields, Values).
%%----------------------------
@@ -882,12 +885,19 @@ make_opts(StateData) ->
Config = StateData#state.config,
[
{title, Config#config.title},
+ {vcard, Config#config.vcard},
+ {voice_request_min_interval, Config#config.voice_request_min_interval},
{allow_change_subj, Config#config.allow_change_subj},
{allow_query_users, Config#config.allow_query_users},
{allow_private_messages, Config#config.allow_private_messages},
+ {allow_private_messages_from_visitors, Config#config.allow_private_messages_from_visitors},
+ {allow_visitor_status, Config#config.allow_visitor_status},
+ {allow_visitor_nickchange, Config#config.allow_visitor_nickchange},
+ {allow_voice_requests, Config#config.allow_voice_requests},
{public, Config#config.public},
{public_list, Config#config.public_list},
{persistent, Config#config.persistent},
+ {mam, Config#config.mam},
{moderated, Config#config.moderated},
{members_by_default, Config#config.members_by_default},
{members_only, Config#config.members_only},
@@ -895,6 +905,8 @@ make_opts(StateData) ->
{password_protected, Config#config.password_protected},
{password, Config#config.password},
{anonymous, Config#config.anonymous},
+ {captcha_protected, Config#config.captcha_protected},
+ {description, Config#config.description},
{logging, Config#config.logging},
{max_users, Config#config.max_users},
{affiliations, ?DICT:to_list(StateData#state.affiliations)},
diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl
index 4d8e3965..1f32f6f9 100644
--- a/src/mod_muc_log.erl
+++ b/src/mod_muc_log.erl
@@ -193,15 +193,15 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
add_to_log2(text, {Nick, Packet}, Room, Opts, State) ->
case has_no_permanent_store_hint(Packet) of
false ->
- case {xml:get_subtag(Packet, <<"subject">>),
- xml:get_subtag(Packet, <<"body">>)}
+ case {fxml:get_subtag(Packet, <<"subject">>),
+ fxml:get_subtag(Packet, <<"body">>)}
of
{false, false} -> ok;
{false, SubEl} ->
- Message = {body, xml:get_tag_cdata(SubEl)},
+ Message = {body, fxml:get_tag_cdata(SubEl)},
add_message_to_log(Nick, Message, Room, Opts, State);
{SubEl, _} ->
- Message = {subject, xml:get_tag_cdata(SubEl)},
+ Message = {subject, fxml:get_tag_cdata(SubEl)},
add_message_to_log(Nick, Message, Room, Opts, State)
end;
true -> ok
@@ -1201,13 +1201,13 @@ fjoin(FileList) ->
list_to_binary(filename:join([binary_to_list(File) || File <- FileList])).
has_no_permanent_store_hint(Packet) ->
- xml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS)
=/= false orelse
- xml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS)
=/= false orelse
- xml:get_subtag_with_xmlns(Packet, <<"no-permanent-store">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-store">>, ?NS_HINTS)
=/= false orelse
- xml:get_subtag_with_xmlns(Packet, <<"no-permanent-storage">>, ?NS_HINTS)
+ fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-storage">>, ?NS_HINTS)
=/= false.
mod_opt_type(access_log) ->
diff --git a/src/mod_muc_mnesia.erl b/src/mod_muc_mnesia.erl
new file mode 100644
index 00000000..e3ae3697
--- /dev/null
+++ b/src/mod_muc_mnesia.erl
@@ -0,0 +1,163 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_muc_mnesia).
+
+-behaviour(mod_muc).
+
+%% API
+-export([init/2, import/2, store_room/4, restore_room/3, forget_room/3,
+ can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4]).
+
+-include("mod_muc.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, Opts) ->
+ MyHost = proplists:get_value(host, Opts),
+ mnesia:create_table(muc_room,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, muc_room)}]),
+ mnesia:create_table(muc_registered,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, muc_registered)}]),
+ update_tables(MyHost),
+ mnesia:add_table_index(muc_registered, nick).
+
+store_room(_LServer, Host, Name, Opts) ->
+ F = fun () ->
+ mnesia:write(#muc_room{name_host = {Name, Host},
+ opts = Opts})
+ end,
+ mnesia:transaction(F).
+
+restore_room(_LServer, Host, Name) ->
+ case catch mnesia:dirty_read(muc_room, {Name, Host}) of
+ [#muc_room{opts = Opts}] -> Opts;
+ _ -> error
+ end.
+
+forget_room(_LServer, Host, Name) ->
+ F = fun () -> mnesia:delete({muc_room, {Name, Host}})
+ end,
+ mnesia:transaction(F).
+
+can_use_nick(_LServer, Host, JID, Nick) ->
+ {LUser, LServer, _} = jid:tolower(JID),
+ LUS = {LUser, LServer},
+ case catch mnesia:dirty_select(muc_registered,
+ [{#muc_registered{us_host = '$1',
+ nick = Nick, _ = '_'},
+ [{'==', {element, 2, '$1'}, Host}],
+ ['$_']}])
+ of
+ {'EXIT', _Reason} -> true;
+ [] -> true;
+ [#muc_registered{us_host = {U, _Host}}] -> U == LUS
+ end.
+
+get_rooms(_LServer, Host) ->
+ mnesia:dirty_select(muc_room,
+ [{#muc_room{name_host = {'_', Host},
+ _ = '_'},
+ [], ['$_']}]).
+
+get_nick(_LServer, Host, From) ->
+ {LUser, LServer, _} = jid:tolower(From),
+ LUS = {LUser, LServer},
+ case mnesia:dirty_read(muc_registered, {LUS, Host}) of
+ [] -> error;
+ [#muc_registered{nick = Nick}] -> Nick
+ end.
+
+set_nick(_LServer, Host, From, Nick) ->
+ {LUser, LServer, _} = jid:tolower(From),
+ LUS = {LUser, LServer},
+ F = fun () ->
+ case Nick of
+ <<"">> ->
+ mnesia:delete({muc_registered, {LUS, Host}}),
+ ok;
+ _ ->
+ Allow = case mnesia:select(
+ muc_registered,
+ [{#muc_registered{us_host =
+ '$1',
+ nick = Nick,
+ _ = '_'},
+ [{'==', {element, 2, '$1'},
+ Host}],
+ ['$_']}]) of
+ [] -> true;
+ [#muc_registered{us_host = {U, _Host}}] ->
+ U == LUS
+ end,
+ if Allow ->
+ mnesia:write(#muc_registered{
+ us_host = {LUS, Host},
+ nick = Nick}),
+ ok;
+ true ->
+ false
+ end
+ end
+ end,
+ mnesia:transaction(F).
+
+import(_LServer, #muc_room{} = R) ->
+ mnesia:dirty_write(R);
+import(_LServer, #muc_registered{} = R) ->
+ mnesia:dirty_write(R).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_tables(Host) ->
+ update_muc_room_table(Host),
+ update_muc_registered_table(Host).
+
+update_muc_room_table(_Host) ->
+ Fields = record_info(fields, muc_room),
+ case mnesia:table_info(muc_room, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ muc_room, Fields, set,
+ fun(#muc_room{name_host = {N, _}}) -> N end,
+ fun(#muc_room{name_host = {N, H},
+ opts = Opts} = R) ->
+ R#muc_room{name_host = {iolist_to_binary(N),
+ iolist_to_binary(H)},
+ opts = mod_muc:opts_to_binary(Opts)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating muc_room table", []),
+ mnesia:transform_table(muc_room, ignore, Fields)
+ end.
+
+update_muc_registered_table(_Host) ->
+ Fields = record_info(fields, muc_registered),
+ case mnesia:table_info(muc_registered, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ muc_registered, Fields, set,
+ fun(#muc_registered{us_host = {_, H}}) -> H end,
+ fun(#muc_registered{us_host = {{U, S}, H},
+ nick = Nick} = R) ->
+ R#muc_registered{us_host = {{iolist_to_binary(U),
+ iolist_to_binary(S)},
+ iolist_to_binary(H)},
+ nick = iolist_to_binary(Nick)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating muc_registered table", []),
+ mnesia:transform_table(muc_registered, ignore, Fields)
+ end.
diff --git a/src/mod_muc_riak.erl b/src/mod_muc_riak.erl
new file mode 100644
index 00000000..bc6e5959
--- /dev/null
+++ b/src/mod_muc_riak.erl
@@ -0,0 +1,117 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_muc_riak).
+
+-behaviour(mod_muc).
+
+%% API
+-export([init/2, import/2, store_room/4, restore_room/3, forget_room/3,
+ can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4]).
+
+-include("mod_muc.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+store_room(_LServer, Host, Name, Opts) ->
+ {atomic, ejabberd_riak:put(#muc_room{name_host = {Name, Host},
+ opts = Opts},
+ muc_room_schema())}.
+
+restore_room(_LServer, Host, Name) ->
+ case ejabberd_riak:get(muc_room, muc_room_schema(), {Name, Host}) of
+ {ok, #muc_room{opts = Opts}} -> Opts;
+ _ -> error
+ end.
+
+forget_room(_LServer, Host, Name) ->
+ {atomic, ejabberd_riak:delete(muc_room, {Name, Host})}.
+
+can_use_nick(LServer, Host, JID, Nick) ->
+ {LUser, LServer, _} = jid:tolower(JID),
+ LUS = {LUser, LServer},
+ case ejabberd_riak:get_by_index(muc_registered,
+ muc_registered_schema(),
+ <<"nick_host">>, {Nick, Host}) of
+ {ok, []} ->
+ true;
+ {ok, [#muc_registered{us_host = {U, _Host}}]} ->
+ U == LUS;
+ {error, _} ->
+ true
+ end.
+
+get_rooms(_LServer, Host) ->
+ case ejabberd_riak:get(muc_room, muc_room_schema()) of
+ {ok, Rs} ->
+ lists:filter(
+ fun(#muc_room{name_host = {_, H}}) ->
+ Host == H
+ end, Rs);
+ _Err ->
+ []
+ end.
+
+get_nick(LServer, Host, From) ->
+ {LUser, LServer, _} = jid:tolower(From),
+ US = {LUser, LServer},
+ case ejabberd_riak:get(muc_registered,
+ muc_registered_schema(),
+ {US, Host}) of
+ {ok, #muc_registered{nick = Nick}} -> Nick;
+ {error, _} -> error
+ end.
+
+set_nick(LServer, Host, From, Nick) ->
+ {LUser, LServer, _} = jid:tolower(From),
+ LUS = {LUser, LServer},
+ {atomic,
+ case Nick of
+ <<"">> ->
+ ejabberd_riak:delete(muc_registered, {LUS, Host});
+ _ ->
+ Allow = case ejabberd_riak:get_by_index(
+ muc_registered,
+ muc_registered_schema(),
+ <<"nick_host">>, {Nick, Host}) of
+ {ok, []} ->
+ true;
+ {ok, [#muc_registered{us_host = {U, _Host}}]} ->
+ U == LUS;
+ {error, _} ->
+ false
+ end,
+ if Allow ->
+ ejabberd_riak:put(#muc_registered{us_host = {LUS, Host},
+ nick = Nick},
+ muc_registered_schema(),
+ [{'2i', [{<<"nick_host">>,
+ {Nick, Host}}]}]);
+ true ->
+ false
+ end
+ end}.
+
+import(_LServer, #muc_room{} = R) ->
+ ejabberd_riak:put(R, muc_room_schema());
+import(_LServer, #muc_registered{us_host = {_, Host}, nick = Nick} = R) ->
+ ejabberd_riak:put(R, muc_registered_schema(),
+ [{'2i', [{<<"nick_host">>, {Nick, Host}}]}]).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+muc_room_schema() ->
+ {record_info(fields, muc_room), #muc_room{}}.
+
+muc_registered_schema() ->
+ {record_info(fields, muc_registered), #muc_registered{}}.
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
index d19bc5f3..c9c78575 100644
--- a/src/mod_muc_room.erl
+++ b/src/mod_muc_room.erl
@@ -143,12 +143,12 @@ normal_state({route, From, <<"">>,
children = Els} =
Packet},
StateData) ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case is_user_online(From, StateData) orelse
is_user_allowed_message_nonparticipant(From, StateData)
of
true ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"groupchat">> ->
Activity = get_user_activity(From, StateData),
Now = p1_time_compat:system_time(micro_seconds),
@@ -252,7 +252,7 @@ normal_state({route, From, <<"">>,
IsVoiceApprovement = is_voice_approvement(Els) and
not is_visitor(From, StateData),
if IsInvitation ->
- case catch check_invitation(From, Els, Lang, StateData)
+ case catch check_invitation(From, Packet, Lang, StateData)
of
{error, Error} ->
Err = jlib:make_error_reply(Packet, Error),
@@ -394,7 +394,7 @@ normal_state({route, From, <<"">>,
{next_state, normal_state, StateData}
end;
_ ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
_ ->
handle_roommessage_from_nonparticipant(Packet, Lang,
@@ -432,10 +432,12 @@ normal_state({route, From, <<"">>,
?NS_MUC_OWNER ->
process_iq_owner(From, Type, Lang, SubEl, StateData);
?NS_DISCO_INFO ->
- case xml:get_attr(<<"node">>, Attrs) of
- false -> process_iq_disco_info(From, Type, Lang, StateData);
- {value, _} -> {error, ?ERR_SERVICE_UNAVAILABLE}
- end;
+ case fxml:get_attr(<<"node">>, Attrs) of
+ false -> process_iq_disco_info(From, Type, Lang, StateData);
+ {value, _} ->
+ Txt = <<"Disco info is not available for this node">>,
+ {error, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)}
+ end;
?NS_DISCO_ITEMS ->
process_iq_disco_items(From, Type, Lang, StateData);
?NS_VCARD ->
@@ -509,8 +511,8 @@ normal_state({route, From, Nick,
normal_state({route, From, ToNick,
#xmlel{name = <<"message">>, attrs = Attrs} = Packet},
StateData) ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case decide_fate_message(Type, Packet, From, StateData)
of
{expulse_sender, Reason} ->
@@ -568,7 +570,7 @@ normal_state({route, From, ToNick,
FromNick),
X = #xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_MUC_USER}]},
- PrivMsg = xml:append_subtags(Packet, [X]),
+ PrivMsg = fxml:append_subtags(Packet, [X]),
[ejabberd_router:route(FromNickJID, ToJID, PrivMsg)
|| ToJID <- ToJIDs];
true ->
@@ -607,8 +609,8 @@ normal_state({route, From, ToNick,
normal_state({route, From, ToNick,
#xmlel{name = <<"iq">>, attrs = Attrs} = Packet},
StateData) ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
- StanzaId = xml:get_attr_s(<<"id">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+ StanzaId = fxml:get_attr_s(<<"id">>, Attrs),
case {(StateData#state.config)#config.allow_query_users,
is_user_online_iq(StanzaId, From, StateData)}
of
@@ -817,8 +819,9 @@ handle_info({captcha_failed, From}, normal_state,
of
{ok, {Nick, Packet}} ->
Robots = (?DICT):erase(From, StateData#state.robots),
- Err = jlib:make_error_reply(Packet,
- ?ERR_NOT_AUTHORIZED),
+ Txt = <<"The CAPTCHA verification has failed">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_NOT_AUTHORIZED(?MYLANG, Txt)),
ejabberd_router:route % TODO: s/Nick/""/
(jid:replace_resource(StateData#state.jid,
Nick),
@@ -884,7 +887,7 @@ route(Pid, From, ToNick, Packet) ->
process_groupchat_message(From,
#xmlel{name = <<"message">>, attrs = Attrs} = Packet,
StateData) ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case is_user_online(From, StateData) orelse
is_user_allowed_message_nonparticipant(From, StateData)
of
@@ -936,7 +939,7 @@ process_groupchat_message(From,
drop ->
{next_state, normal_state, StateData};
NewPacket1 ->
- NewPacket = xml:remove_subtags(NewPacket1, <<"nick">>, {<<"xmlns">>, ?NS_NICK}),
+ NewPacket = fxml:remove_subtags(NewPacket1, <<"nick">>, {<<"xmlns">>, ?NS_NICK}),
send_multiple(jid:replace_resource(StateData#state.jid,
FromNick),
StateData#state.server_host,
@@ -1016,7 +1019,7 @@ get_participant_data(From, StateData) ->
process_presence(From, Nick,
#xmlel{name = <<"presence">>, attrs = Attrs0} = Packet0,
StateData) ->
- Type0 = xml:get_attr_s(<<"type">>, Attrs0),
+ Type0 = fxml:get_attr_s(<<"type">>, Attrs0),
IsOnline = is_user_online(From, StateData),
if Type0 == <<"">>;
IsOnline and ((Type0 == <<"unavailable">>) or (Type0 == <<"error">>)) ->
@@ -1029,8 +1032,8 @@ process_presence(From, Nick,
drop ->
{next_state, normal_state, StateData};
#xmlel{attrs = Attrs} = Packet ->
- Type = xml:get_attr_s(<<"type">>, Attrs),
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Type = fxml:get_attr_s(<<"type">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
StateData1 = case Type of
<<"unavailable">> ->
NewPacket = case
@@ -1047,12 +1050,12 @@ process_presence(From, Nick,
{ok, [_, _ | _]} -> ok;
_ -> send_new_presence(From, NewState, StateData)
end,
- Reason = case xml:get_subtag(NewPacket,
+ Reason = case fxml:get_subtag(NewPacket,
<<"status">>)
of
false -> <<"">>;
Status_el ->
- xml:get_tag_cdata(Status_el)
+ fxml:get_tag_cdata(Status_el)
end,
remove_online_user(From, NewState, Reason);
<<"error">> ->
@@ -1087,7 +1090,7 @@ process_presence(From, Nick,
From, Err),
StateData;
{true, _, _} ->
- Lang = xml:get_attr_s(<<"xml:lang">>,
+ Lang = fxml:get_attr_s(<<"xml:lang">>,
Attrs),
ErrText =
<<"That nickname is already in use by another "
@@ -1304,7 +1307,7 @@ get_error_condition(Packet) ->
end.
get_error_condition2(Packet) ->
- #xmlel{children = EEls} = xml:get_subtag(Packet,
+ #xmlel{children = EEls} = fxml:get_subtag(Packet,
<<"error">>),
[Condition] = [Name
|| #xmlel{name = Name,
@@ -1655,7 +1658,7 @@ filter_presence(#xmlel{name = <<"presence">>,
case El of
{xmlcdata, _} -> false;
#xmlel{attrs = Attrs1} ->
- XMLNS = xml:get_attr_s(<<"xmlns">>,
+ XMLNS = fxml:get_attr_s(<<"xmlns">>,
Attrs1),
NS_MUC = ?NS_MUC,
Size = byte_size(NS_MUC),
@@ -1743,11 +1746,11 @@ higher_presence(Pres1, Pres2) ->
Pri1 > Pri2.
get_priority_from_presence(PresencePacket) ->
- case xml:get_subtag(PresencePacket, <<"priority">>) of
+ case fxml:get_subtag(PresencePacket, <<"priority">>) of
false -> 0;
SubEl ->
case catch
- jlib:binary_to_integer(xml:get_tag_cdata(SubEl))
+ jlib:binary_to_integer(fxml:get_tag_cdata(SubEl))
of
P when is_integer(P) -> P;
_ -> 0
@@ -1781,7 +1784,7 @@ nick_collision(User, Nick, StateData) ->
add_new_user(From, Nick,
#xmlel{attrs = Attrs, children = Els} = Packet,
StateData) ->
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
MaxUsers = get_max_users(StateData),
MaxAdminUsers = MaxUsers +
get_max_users_admin_threshold(StateData),
@@ -1807,6 +1810,22 @@ add_new_user(From, Nick,
StateData#state.host, From, Nick),
get_default_role(Affiliation, StateData)}
of
+ {false, _, _, _} when NUsers >= MaxUsers orelse NUsers >= MaxAdminUsers ->
+ Txt = <<"Too many users in this conference">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt)),
+ ejabberd_router:route % TODO: s/Nick/""/
+ (jid:replace_resource(StateData#state.jid, Nick),
+ From, Err),
+ StateData;
+ {false, _, _, _} when NConferences >= MaxConferences ->
+ Txt = <<"You have joined too many conferences">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt)),
+ ejabberd_router:route % TODO: s/Nick/""/
+ (jid:replace_resource(StateData#state.jid, Nick),
+ From, Err),
+ StateData;
{false, _, _, _} ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
@@ -1856,7 +1875,7 @@ add_new_user(From, Nick,
add_online_user(From, Nick, Role,
StateData)),
send_existing_presences(From, NewState),
- send_new_presence(From, NewState, StateData),
+ send_initial_presence(From, NewState, StateData),
Shift = count_stanza_shift(Nick, Els, NewState),
case send_history(From, Shift, NewState) of
true -> ok;
@@ -1879,7 +1898,7 @@ add_new_user(From, Nick,
From, Err),
StateData;
captcha_required ->
- SID = xml:get_attr_s(<<"id">>, Attrs),
+ SID = fxml:get_attr_s(<<"id">>, Attrs),
RoomJID = StateData#state.jid,
To = jid:replace_resource(RoomJID, Nick),
Limiter = {From#jid.luser, From#jid.lserver},
@@ -1979,11 +1998,11 @@ check_captcha(Affiliation, From, StateData) ->
extract_password([]) -> false;
extract_password([#xmlel{attrs = Attrs} = El | Els]) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC ->
- case xml:get_subtag(El, <<"password">>) of
+ case fxml:get_subtag(El, <<"password">>) of
false -> false;
- SubEl -> xml:get_tag_cdata(SubEl)
+ SubEl -> fxml:get_tag_cdata(SubEl)
end;
_ -> extract_password(Els)
end;
@@ -2057,9 +2076,9 @@ calc_shift(MaxSize, Size, Shift, [S | TSizes]) ->
extract_history([], _Type) -> false;
extract_history([#xmlel{attrs = Attrs} = El | Els],
Type) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC ->
- AttrVal = xml:get_path_s(El,
+ AttrVal = fxml:get_path_s(El,
[{elem, <<"history">>}, {attr, Type}]),
case Type of
<<"since">> ->
@@ -2090,6 +2109,9 @@ presence_broadcast_allowed(JID, StateData) ->
Role = get_role(JID, StateData),
lists:member(Role, (StateData#state.config)#config.presence_broadcast).
+send_initial_presence(NJID, StateData, OldStateData) ->
+ send_new_presence1(NJID, <<"">>, true, StateData, OldStateData).
+
send_update_presence(JID, StateData, OldStateData) ->
send_update_presence(JID, <<"">>, StateData, OldStateData).
@@ -2117,20 +2139,25 @@ send_update_presence1(JID, Reason, StateData, OldStateData) ->
end
end,
lists:foreach(fun (J) ->
- send_new_presence(J, Reason, StateData, OldStateData)
+ send_new_presence1(J, Reason, false, StateData,
+ OldStateData)
end,
LJIDs).
send_new_presence(NJID, StateData, OldStateData) ->
- send_new_presence(NJID, <<"">>, StateData, OldStateData).
+ send_new_presence(NJID, <<"">>, false, StateData, OldStateData).
send_new_presence(NJID, Reason, StateData, OldStateData) ->
+ send_new_presence(NJID, Reason, false, StateData, OldStateData).
+
+send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
case is_room_overcrowded(StateData) of
true -> ok;
- false -> send_new_presence1(NJID, Reason, StateData, OldStateData)
+ false -> send_new_presence1(NJID, Reason, IsInitialPresence, StateData,
+ OldStateData)
end.
-send_new_presence1(NJID, Reason, StateData, OldStateData) ->
+send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
LNJID = jid:tolower(NJID),
#user{nick = Nick} = (?DICT):fetch(LNJID, StateData#state.users),
LJID = find_jid_by_nick(Nick, StateData),
@@ -2187,46 +2214,9 @@ send_new_presence1(NJID, Reason, StateData, OldStateData) ->
children =
[{xmlcdata, Reason}]}]
end,
- Status = case StateData#state.just_created of
- true ->
- [#xmlel{name = <<"status">>,
- attrs =
- [{<<"code">>, <<"201">>}],
- children = []}];
- false -> []
- end,
- Status2 = case
- (StateData#state.config)#config.anonymous
- == false
- andalso NJID == Info#user.jid
- of
- true ->
- [#xmlel{name = <<"status">>,
- attrs =
- [{<<"code">>, <<"100">>}],
- children = []}
- | Status];
- false -> Status
- end,
- Status3 = case NJID == Info#user.jid of
- true ->
- [#xmlel{name = <<"status">>,
- attrs =
- [{<<"code">>, <<"110">>}],
- children = []}
- | Status2];
- false -> Status2
- end,
- Status4 = case (StateData#state.config)#config.logging of
- true ->
- [#xmlel{name = <<"status">>,
- attrs =
- [{<<"code">>, <<"170">>}],
- children = []}
- | Status3];
- false -> Status3
- end,
- Packet = xml:append_subtags(Presence,
+ StatusEls = status_els(IsInitialPresence, NJID, Info,
+ StateData),
+ Packet = fxml:append_subtags(Presence,
[#xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>,
@@ -2240,7 +2230,7 @@ send_new_presence1(NJID, Reason, StateData, OldStateData) ->
children
=
ItemEls}
- | Status4]}]),
+ | StatusEls]}]),
ejabberd_router:route(jid:replace_resource(StateData#state.jid,
Nick),
Info#user.jid, Packet)
@@ -2289,7 +2279,7 @@ send_existing_presences1(ToJID, StateData) ->
{<<"role">>,
role_to_list(FromRole)}]
end,
- Packet = xml:append_subtags(Presence,
+ Packet = fxml:append_subtags(Presence,
[#xmlel{name =
<<"x">>,
attrs =
@@ -2420,7 +2410,7 @@ send_nick_changing(JID, OldNick, StateData,
<<"303">>}],
children =
[]}|Status110]}]},
- Packet2 = xml:append_subtags(Presence,
+ Packet2 = fxml:append_subtags(Presence,
[#xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>,
@@ -2450,6 +2440,40 @@ send_nick_changing(JID, OldNick, StateData,
end,
(?DICT):to_list(StateData#state.users)).
+status_els(IsInitialPresence, JID, #user{jid = JID}, StateData) ->
+ Status = case IsInitialPresence of
+ true ->
+ S1 = case StateData#state.just_created of
+ true ->
+ [#xmlel{name = <<"status">>,
+ attrs = [{<<"code">>, <<"201">>}],
+ children = []}];
+ false -> []
+ end,
+ S2 = case (StateData#state.config)#config.anonymous of
+ true -> S1;
+ false ->
+ [#xmlel{name = <<"status">>,
+ attrs = [{<<"code">>, <<"100">>}],
+ children = []} | S1]
+ end,
+ S3 = case (StateData#state.config)#config.logging of
+ true ->
+ [#xmlel{name = <<"status">>,
+ attrs = [{<<"code">>, <<"170">>}],
+ children = []} | S2];
+ false -> S2
+ end,
+ S3;
+ false -> []
+ end,
+ [#xmlel{name = <<"status">>,
+ attrs =
+ [{<<"code">>,
+ <<"110">>}],
+ children = []} | Status];
+status_els(_IsInitialPresence, _JID, _Info, _StateData) -> [].
+
lqueue_new(Max) ->
#lqueue{queue = queue:new(), len = 0, max = Max}.
@@ -2474,7 +2498,7 @@ lqueue_to_list(#lqueue{queue = Q1}) ->
add_message_to_history(FromNick, FromJID, Packet, StateData) ->
- HaveSubject = case xml:get_subtag(Packet, <<"subject">>)
+ HaveSubject = case fxml:get_subtag(Packet, <<"subject">>)
of
false -> false;
_ -> true
@@ -2491,7 +2515,7 @@ add_message_to_history(FromNick, FromJID, Packet, StateData) ->
Addresses = #xmlel{name = <<"addresses">>,
attrs = [{<<"xmlns">>, ?NS_ADDRESS}],
children = [Address]},
- xml:append_subtags(Packet, [Addresses])
+ fxml:append_subtags(Packet, [Addresses])
end,
TSPacket = jlib:add_delay_info(AddrPacket, StateData#state.jid, TimeStamp),
SPacket =
@@ -2530,9 +2554,9 @@ send_subject(JID, #state{subject_author = Nick} = StateData) ->
Packet).
check_subject(Packet) ->
- case xml:get_subtag(Packet, <<"subject">>) of
+ case fxml:get_subtag(Packet, <<"subject">>) of
false -> false;
- SubjEl -> xml:get_tag_cdata(SubjEl)
+ SubjEl -> fxml:get_tag_cdata(SubjEl)
end.
can_change_subject(Role, StateData) ->
@@ -2549,22 +2573,27 @@ process_iq_admin(From, set, Lang, SubEl, StateData) ->
#xmlel{children = Items} = SubEl,
process_admin_items_set(From, Items, Lang, StateData);
process_iq_admin(From, get, Lang, SubEl, StateData) ->
- case xml:get_subtag(SubEl, <<"item">>) of
- false -> {error, ?ERR_BAD_REQUEST};
+ case fxml:get_subtag(SubEl, <<"item">>) of
+ false ->
+ Txt = <<"No 'item' element found">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
Item ->
FAffiliation = get_affiliation(From, StateData),
FRole = get_role(From, StateData),
- case xml:get_tag_attr(<<"role">>, Item) of
+ case fxml:get_tag_attr(<<"role">>, Item) of
false ->
- case xml:get_tag_attr(<<"affiliation">>, Item) of
- false -> {error, ?ERR_BAD_REQUEST};
+ case fxml:get_tag_attr(<<"affiliation">>, Item) of
+ false ->
+ Txt = <<"No 'affiliation' attribute found">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{value, StrAffiliation} ->
case catch list_to_affiliation(StrAffiliation) of
{'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
SAffiliation ->
if (FAffiliation == owner) or
(FAffiliation == admin) or
- ((FAffiliation == member) and (SAffiliation == member)) ->
+ ((FAffiliation == member) and not
+ (StateData#state.config)#config.anonymous) ->
Items = items_with_affiliation(SAffiliation,
StateData),
{result, Items, StateData};
@@ -2577,7 +2606,9 @@ process_iq_admin(From, get, Lang, SubEl, StateData) ->
end;
{value, StrRole} ->
case catch list_to_role(StrRole) of
- {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
+ {'EXIT', _} ->
+ Txt = <<"Incorrect value of 'role' attribute">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
SRole ->
if FRole == moderator ->
Items = items_with_role(SRole, StateData),
@@ -2748,7 +2779,7 @@ find_changed_items(UJID, UAffiliation, URole,
[#xmlel{name = <<"item">>, attrs = Attrs} = Item
| Items],
Lang, StateData, Res) ->
- TJID = case xml:get_attr(<<"jid">>, Attrs) of
+ TJID = case fxml:get_attr(<<"jid">>, Attrs) of
{value, S} ->
case jid:from_string(S) of
error ->
@@ -2761,7 +2792,7 @@ find_changed_items(UJID, UAffiliation, URole,
J -> {value, [J]}
end;
_ ->
- case xml:get_attr(<<"nick">>, Attrs) of
+ case fxml:get_attr(<<"nick">>, Attrs) of
{value, N} ->
case find_jids_by_nick(N, StateData) of
false ->
@@ -2774,17 +2805,21 @@ find_changed_items(UJID, UAffiliation, URole,
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
J -> {value, J}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt1 = <<"No 'nick' attribute found">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt1)}
end
end,
case TJID of
{value, [JID | _] = JIDs} ->
TAffiliation = get_affiliation(JID, StateData),
TRole = get_role(JID, StateData),
- case xml:get_attr(<<"role">>, Attrs) of
+ case fxml:get_attr(<<"role">>, Attrs) of
false ->
- case xml:get_attr(<<"affiliation">>, Attrs) of
- false -> {error, ?ERR_BAD_REQUEST};
+ case fxml:get_attr(<<"affiliation">>, Attrs) of
+ false ->
+ Txt2 = <<"No 'affiliation' attribute found">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt2)};
{value, StrAffiliation} ->
case catch list_to_affiliation(StrAffiliation) of
{'EXIT', _} ->
@@ -2824,7 +2859,7 @@ find_changed_items(UJID, UAffiliation, URole,
Items, Lang, StateData,
Res);
true ->
- Reason = xml:get_path_s(Item,
+ Reason = fxml:get_path_s(Item,
[{elem, <<"reason">>},
cdata]),
MoreRes = [{jid:remove_resource(Jidx),
@@ -2833,7 +2868,9 @@ find_changed_items(UJID, UAffiliation, URole,
find_changed_items(UJID, UAffiliation, URole,
Items, Lang, StateData,
[MoreRes | Res]);
- false -> {error, ?ERR_NOT_ALLOWED}
+ false ->
+ Txt3 = <<"Changing role/affiliation is not allowed">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt3)}
end
end
end;
@@ -2871,7 +2908,7 @@ find_changed_items(UJID, UAffiliation, URole,
find_changed_items(UJID, UAffiliation, URole, Items,
Lang, StateData, Res);
true ->
- Reason = xml:get_path_s(Item,
+ Reason = fxml:get_path_s(Item,
[{elem, <<"reason">>},
cdata]),
MoreRes = [{Jidx, role, SRole, Reason}
@@ -2879,7 +2916,9 @@ find_changed_items(UJID, UAffiliation, URole,
find_changed_items(UJID, UAffiliation, URole, Items,
Lang, StateData,
[MoreRes | Res]);
- _ -> {error, ?ERR_NOT_ALLOWED}
+ _ ->
+ Txt4 = <<"Changing role/affiliation is not allowed">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt4)}
end
end
end;
@@ -3123,10 +3162,10 @@ process_iq_owner(From, set, Lang, SubEl, StateData) ->
case FAffiliation of
owner ->
#xmlel{children = Els} = SubEl,
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = <<"x">>} = XEl] ->
- case {xml:get_tag_attr_s(<<"xmlns">>, XEl),
- xml:get_tag_attr_s(<<"type">>, XEl)}
+ case {fxml:get_tag_attr_s(<<"xmlns">>, XEl),
+ fxml:get_tag_attr_s(<<"type">>, XEl)}
of
{?NS_XDATA, <<"cancel">>} -> {result, [], StateData};
{?NS_XDATA, <<"submit">>} ->
@@ -3137,10 +3176,12 @@ process_iq_owner(From, set, Lang, SubEl, StateData) ->
andalso
is_password_settings_correct(XEl, StateData)
of
- true -> set_config(XEl, StateData);
+ true -> set_config(XEl, StateData, Lang);
false -> {error, ?ERR_NOT_ACCEPTABLE}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Incorrect data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end;
[#xmlel{name = <<"destroy">>} = SubEl1] ->
?INFO_MSG("Destroyed MUC room ~s by the owner ~s",
@@ -3160,11 +3201,13 @@ process_iq_owner(From, get, Lang, SubEl, StateData) ->
case FAffiliation of
owner ->
#xmlel{children = Els} = SubEl,
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[] -> get_config(Lang, StateData, From);
[Item] ->
- case xml:get_tag_attr(<<"affiliation">>, Item) of
- false -> {error, ?ERR_BAD_REQUEST};
+ case fxml:get_tag_attr(<<"affiliation">>, Item) of
+ false ->
+ Txt = <<"No 'affiliation' attribute found">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{value, StrAffiliation} ->
case catch list_to_affiliation(StrAffiliation) of
{'EXIT', _} ->
@@ -3636,10 +3679,10 @@ get_config(Lang, StateData, From) ->
children = Res}],
StateData}.
-set_config(XEl, StateData) ->
+set_config(XEl, StateData, Lang) ->
XData = jlib:parse_xdata_submit(XEl),
case XData of
- invalid -> {error, ?ERR_BAD_REQUEST};
+ invalid -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)};
_ ->
case set_xoption(XData, StateData#state.config) of
#config{} = Config ->
@@ -3669,14 +3712,20 @@ set_config(XEl, StateData) ->
<<"1">> -> set_xoption(Opts, Config#config{Opt = true});
<<"true">> ->
set_xoption(Opts, Config#config{Opt = true});
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Value of '~s' should be boolean">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, ErrTxt)}
end).
-define(SET_NAT_XOPT(Opt, Val),
case catch jlib:binary_to_integer(Val) of
I when is_integer(I), I > 0 ->
set_xoption(Opts, Config#config{Opt = I});
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Value of '~s' should be integer">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, ErrTxt)}
end).
-define(SET_STRING_XOPT(Opt, Val),
@@ -3729,7 +3778,10 @@ set_xoption([{<<"allow_private_messages_from_visitors">>,
<<"nobody">> ->
?SET_STRING_XOPT(allow_private_messages_from_visitors,
nobody);
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Value of 'allow_private_messages_from_visitors' "
+ "should be anyone|moderators|nobody">>,
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)}
end;
set_xoption([{<<"muc#roomconfig_allowvisitorstatus">>,
[Val]}
@@ -3797,7 +3849,10 @@ set_xoption([{<<"muc#roomconfig_presencebroadcast">>, Vals} | Opts],
end
end, {false, false, false}, Vals),
case Roles of
- error -> {error, ?ERR_BAD_REQUEST};
+ error ->
+ Txt = <<"Value of 'muc#roomconfig_presencebroadcast' should "
+ "be moderator|participant|visitor">>,
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)};
{M, P, V} ->
Res =
if M -> [moderator]; true -> [] end ++
@@ -3825,7 +3880,10 @@ set_xoption([{<<"muc#roomconfig_whois">>, [Val]}
<<"anyone">> ->
?SET_BOOL_XOPT(anonymous,
(iolist_to_binary(integer_to_list(0))));
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ ->
+ Txt = <<"Value of 'muc#roomconfig_whois' should be "
+ "moderators|anyone">>,
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)}
end;
set_xoption([{<<"muc#roomconfig_maxusers">>, [Val]}
| Opts],
@@ -3848,8 +3906,10 @@ set_xoption([{<<"muc#roomconfig_captcha_whitelist">>,
?SET_JIDMULTI_XOPT(captcha_whitelist, JIDs);
set_xoption([{<<"FORM_TYPE">>, _} | Opts], Config) ->
set_xoption(Opts, Config);
-set_xoption([_ | _Opts], _Config) ->
- {error, ?ERR_BAD_REQUEST}.
+set_xoption([{Opt, _Vals} | _Opts], _Config) ->
+ Txt = <<"Unknown option '~s'">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, ErrTxt)}.
change_config(Config, StateData) ->
send_config_change_info(Config, StateData),
@@ -4122,8 +4182,9 @@ destroy_room(DEl, StateData) ->
false -> ?FEATURE(Fiffalse)
end).
-process_iq_disco_info(_From, set, _Lang, _StateData) ->
- {error, ?ERR_NOT_ALLOWED};
+process_iq_disco_info(_From, set, Lang, _StateData) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt)};
process_iq_disco_info(_From, get, Lang, StateData) ->
Config = StateData#state.config,
{result,
@@ -4193,9 +4254,10 @@ iq_disco_info_extras(Lang, StateData) ->
<<"muc#roominfo_occupants">>,
(iolist_to_binary(integer_to_list(Len))))]}].
-process_iq_disco_items(_From, set, _Lang, _StateData) ->
- {error, ?ERR_NOT_ALLOWED};
-process_iq_disco_items(From, get, _Lang, StateData) ->
+process_iq_disco_items(_From, set, Lang, _StateData) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt)};
+process_iq_disco_items(From, get, Lang, StateData) ->
case (StateData#state.config)#config.public_list of
true ->
{result, get_mucroom_disco_items(StateData), StateData};
@@ -4203,23 +4265,31 @@ process_iq_disco_items(From, get, _Lang, StateData) ->
case is_occupant_or_admin(From, StateData) of
true ->
{result, get_mucroom_disco_items(StateData), StateData};
- _ -> {error, ?ERR_FORBIDDEN}
+ _ ->
+ Txt = <<"Only occupants or administrators can perform this query">>,
+ {error, ?ERRT_FORBIDDEN(Lang, Txt)}
end
end.
-process_iq_captcha(_From, get, _Lang, _SubEl,
+process_iq_captcha(_From, get, Lang, _SubEl,
_StateData) ->
- {error, ?ERR_NOT_ALLOWED};
-process_iq_captcha(_From, set, _Lang, SubEl,
+ Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt)};
+process_iq_captcha(_From, set, Lang, SubEl,
StateData) ->
case ejabberd_captcha:process_reply(SubEl) of
ok -> {result, [], StateData};
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ {error, malformed} ->
+ Txt = <<"Incorrect CAPTCHA submit">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
+ _ ->
+ Txt = <<"The CAPTCHA verification has failed">>,
+ {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
end.
process_iq_vcard(_From, get, _Lang, _SubEl, StateData) ->
#state{config = #config{vcard = VCardRaw}} = StateData,
- case xml_stream:parse_element(VCardRaw) of
+ case fxml_stream:parse_element(VCardRaw) of
#xmlel{children = VCardEls} ->
{result, VCardEls, StateData};
{error, _} ->
@@ -4228,7 +4298,7 @@ process_iq_vcard(_From, get, _Lang, _SubEl, StateData) ->
process_iq_vcard(From, set, Lang, SubEl, StateData) ->
case get_affiliation(From, StateData) of
owner ->
- VCardRaw = xml:element_to_binary(SubEl),
+ VCardRaw = fxml:element_to_binary(SubEl),
Config = StateData#state.config,
NewConfig = Config#config{vcard = VCardRaw},
change_config(NewConfig, StateData);
@@ -4287,7 +4357,7 @@ is_voice_request(Els) ->
lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
El,
false) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
case jlib:parse_xdata_submit(El) of
[_ | _] = Fields ->
@@ -4370,7 +4440,7 @@ is_voice_approvement(Els) ->
lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
El,
false) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
case jlib:parse_xdata_submit(El) of
[_ | _] = Fs ->
@@ -4424,9 +4494,9 @@ is_invitation(Els) ->
lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
El,
false) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC_USER ->
- case xml:get_subtag(El, <<"invite">>) of
+ case fxml:get_subtag(El, <<"invite">>) of
false -> false;
_ -> true
end;
@@ -4436,37 +4506,42 @@ is_invitation(Els) ->
end,
false, Els).
-check_invitation(From, Els, Lang, StateData) ->
+check_invitation(From, Packet, Lang, StateData) ->
FAffiliation = get_affiliation(From, StateData),
CanInvite =
(StateData#state.config)#config.allow_user_invites
orelse
FAffiliation == admin orelse FAffiliation == owner,
- InviteEl = case xml:remove_cdata(Els) of
- [#xmlel{name = <<"x">>, children = Els1} = XEl] ->
- case xml:get_tag_attr_s(<<"xmlns">>, XEl) of
- ?NS_MUC_USER -> ok;
- _ -> throw({error, ?ERR_BAD_REQUEST})
- end,
- case xml:remove_cdata(Els1) of
- [#xmlel{name = <<"invite">>} = InviteEl1] -> InviteEl1;
- _ -> throw({error, ?ERR_BAD_REQUEST})
- end;
- _ -> throw({error, ?ERR_BAD_REQUEST})
+ InviteEl = case fxml:get_subtag_with_xmlns(Packet, <<"x">>, ?NS_MUC_USER) of
+ false ->
+ Txt1 = <<"No 'x' element found">>,
+ throw({error, ?ERRT_BAD_REQUEST(Lang, Txt1)});
+ XEl ->
+ case fxml:get_subtag(XEl, <<"invite">>) of
+ false ->
+ Txt2 = <<"No 'invite' element found">>,
+ throw({error, ?ERRT_BAD_REQUEST(Lang, Txt2)});
+ InviteEl1 ->
+ InviteEl1
+ end
end,
JID = case
- jid:from_string(xml:get_tag_attr_s(<<"to">>,
+ jid:from_string(fxml:get_tag_attr_s(<<"to">>,
InviteEl))
of
- error -> throw({error, ?ERR_JID_MALFORMED});
+ error ->
+ Txt = <<"Incorrect value of 'to' attribute">>,
+ throw({error, ?ERRT_JID_MALFORMED(Lang, Txt)});
JID1 -> JID1
end,
case CanInvite of
- false -> throw({error, ?ERR_NOT_ALLOWED});
+ false ->
+ Txt3 = <<"Invitations are not allowed in this conference">>,
+ throw({error, ?ERRT_NOT_ALLOWED(Lang, Txt3)});
true ->
- Reason = xml:get_path_s(InviteEl,
+ Reason = fxml:get_path_s(InviteEl,
[{elem, <<"reason">>}, cdata]),
- ContinueEl = case xml:get_path_s(InviteEl,
+ ContinueEl = case fxml:get_path_s(InviteEl,
[{elem, <<"continue">>}])
of
<<>> -> [];
@@ -4556,10 +4631,10 @@ handle_roommessage_from_nonparticipant(Packet, Lang,
%% because it crashes when the packet is not a decline message.
check_decline_invitation(Packet) ->
#xmlel{name = <<"message">>} = Packet,
- XEl = xml:get_subtag(Packet, <<"x">>),
- (?NS_MUC_USER) = xml:get_tag_attr_s(<<"xmlns">>, XEl),
- DEl = xml:get_subtag(XEl, <<"decline">>),
- ToString = xml:get_tag_attr_s(<<"to">>, DEl),
+ XEl = fxml:get_subtag(Packet, <<"x">>),
+ (?NS_MUC_USER) = fxml:get_tag_attr_s(<<"xmlns">>, XEl),
+ DEl = fxml:get_subtag(XEl, <<"decline">>),
+ ToString = fxml:get_tag_attr_s(<<"to">>, DEl),
ToJID = jid:from_string(ToString),
{true, {Packet, XEl, DEl, ToJID}}.
@@ -4646,7 +4721,7 @@ tab_count_user(JID) ->
end.
element_size(El) ->
- byte_size(xml:element_to_binary(El)).
+ byte_size(fxml:element_to_binary(El)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Multicast
diff --git a/src/mod_muc_sql.erl b/src/mod_muc_sql.erl
new file mode 100644
index 00000000..55628d43
--- /dev/null
+++ b/src/mod_muc_sql.erl
@@ -0,0 +1,202 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_muc_sql).
+
+-behaviour(mod_muc).
+
+%% API
+-export([init/2, store_room/4, restore_room/3, forget_room/3,
+ can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4,
+ import/1, import/2, export/1]).
+
+-include("jlib.hrl").
+-include("mod_muc.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+store_room(LServer, Host, Name, Opts) ->
+ SName = ejabberd_sql:escape(Name),
+ SHost = ejabberd_sql:escape(Host),
+ SOpts = ejabberd_sql:encode_term(Opts),
+ F = fun () ->
+ sql_queries:update_t(<<"muc_room">>,
+ [<<"name">>, <<"host">>, <<"opts">>],
+ [SName, SHost, SOpts],
+ [<<"name='">>, SName, <<"' and host='">>,
+ SHost, <<"'">>])
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+restore_room(LServer, Host, Name) ->
+ SName = ejabberd_sql:escape(Name),
+ SHost = ejabberd_sql:escape(Host),
+ case catch ejabberd_sql:sql_query(LServer,
+ [<<"select opts from muc_room where name='">>,
+ SName, <<"' and host='">>, SHost,
+ <<"';">>]) of
+ {selected, [<<"opts">>], [[Opts]]} ->
+ mod_muc:opts_to_binary(ejabberd_sql:decode_term(Opts));
+ _ ->
+ error
+ end.
+
+forget_room(LServer, Host, Name) ->
+ SName = ejabberd_sql:escape(Name),
+ SHost = ejabberd_sql:escape(Host),
+ F = fun () ->
+ ejabberd_sql:sql_query_t([<<"delete from muc_room where name='">>,
+ SName, <<"' and host='">>, SHost,
+ <<"';">>])
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+can_use_nick(LServer, Host, JID, Nick) ->
+ SJID = jid:to_string(jid:tolower(jid:remove_resource(JID))),
+ SNick = ejabberd_sql:escape(Nick),
+ SHost = ejabberd_sql:escape(Host),
+ case catch ejabberd_sql:sql_query(LServer,
+ [<<"select jid from muc_registered ">>,
+ <<"where nick='">>, SNick,
+ <<"' and host='">>, SHost, <<"';">>]) of
+ {selected, [<<"jid">>], [[SJID1]]} -> SJID == SJID1;
+ _ -> true
+ end.
+
+get_rooms(LServer, Host) ->
+ SHost = ejabberd_sql:escape(Host),
+ case catch ejabberd_sql:sql_query(LServer,
+ [<<"select name, opts from muc_room ">>,
+ <<"where host='">>, SHost, <<"';">>]) of
+ {selected, [<<"name">>, <<"opts">>], RoomOpts} ->
+ lists:map(
+ fun([Room, Opts]) ->
+ #muc_room{name_host = {Room, Host},
+ opts = mod_muc:opts_to_binary(
+ ejabberd_sql:decode_term(Opts))}
+ end, RoomOpts);
+ Err ->
+ ?ERROR_MSG("failed to get rooms: ~p", [Err]),
+ []
+ end.
+
+get_nick(LServer, Host, From) ->
+ SJID = ejabberd_sql:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
+ SHost = ejabberd_sql:escape(Host),
+ case catch ejabberd_sql:sql_query(LServer,
+ [<<"select nick from muc_registered where "
+ "jid='">>,
+ SJID, <<"' and host='">>, SHost,
+ <<"';">>]) of
+ {selected, [<<"nick">>], [[Nick]]} -> Nick;
+ _ -> error
+ end.
+
+set_nick(LServer, Host, From, Nick) ->
+ JID = jid:to_string(jid:tolower(jid:remove_resource(From))),
+ SJID = ejabberd_sql:escape(JID),
+ SNick = ejabberd_sql:escape(Nick),
+ SHost = ejabberd_sql:escape(Host),
+ F = fun () ->
+ case Nick of
+ <<"">> ->
+ ejabberd_sql:sql_query_t(
+ [<<"delete from muc_registered where ">>,
+ <<"jid='">>, SJID,
+ <<"' and host='">>, Host,
+ <<"';">>]),
+ ok;
+ _ ->
+ Allow = case ejabberd_sql:sql_query_t(
+ [<<"select jid from muc_registered ">>,
+ <<"where nick='">>,
+ SNick,
+ <<"' and host='">>,
+ SHost, <<"';">>]) of
+ {selected, [<<"jid">>], [[J]]} -> J == JID;
+ _ -> true
+ end,
+ if Allow ->
+ sql_queries:update_t(<<"muc_registered">>,
+ [<<"jid">>, <<"host">>,
+ <<"nick">>],
+ [SJID, SHost, SNick],
+ [<<"jid='">>, SJID,
+ <<"' and host='">>, SHost,
+ <<"'">>]),
+ ok;
+ true ->
+ false
+ end
+ end
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+export(_Server) ->
+ [{muc_room,
+ fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->
+ case str:suffix(Host, RoomHost) of
+ true ->
+ SName = ejabberd_sql:escape(Name),
+ SRoomHost = ejabberd_sql:escape(RoomHost),
+ SOpts = ejabberd_sql:encode_term(Opts),
+ [[<<"delete from muc_room where name='">>, SName,
+ <<"' and host='">>, SRoomHost, <<"';">>],
+ [<<"insert into muc_room(name, host, opts) ",
+ "values (">>,
+ <<"'">>, SName, <<"', '">>, SRoomHost,
+ <<"', '">>, SOpts, <<"');">>]];
+ false ->
+ []
+ end
+ end},
+ {muc_registered,
+ fun(Host, #muc_registered{us_host = {{U, S}, RoomHost},
+ nick = Nick}) ->
+ case str:suffix(Host, RoomHost) of
+ true ->
+ SJID = ejabberd_sql:escape(
+ jid:to_string(
+ jid:make(U, S, <<"">>))),
+ SNick = ejabberd_sql:escape(Nick),
+ SRoomHost = ejabberd_sql:escape(RoomHost),
+ [[<<"delete from muc_registered where jid='">>,
+ SJID, <<"' and host='">>, SRoomHost, <<"';">>],
+ [<<"insert into muc_registered(jid, host, "
+ "nick) values ('">>,
+ SJID, <<"', '">>, SRoomHost, <<"', '">>, SNick,
+ <<"');">>]];
+ false ->
+ []
+ end
+ end}].
+
+import(_LServer) ->
+ [{<<"select name, host, opts from muc_room;">>,
+ fun([Name, RoomHost, SOpts]) ->
+ Opts = mod_muc:opts_to_binary(ejabberd_sql:decode_term(SOpts)),
+ #muc_room{name_host = {Name, RoomHost}, opts = Opts}
+ end},
+ {<<"select jid, host, nick from muc_registered;">>,
+ fun([J, RoomHost, Nick]) ->
+ #jid{user = U, server = S} = jid:from_string(J),
+ #muc_registered{us_host = {{U, S}, RoomHost},
+ nick = Nick}
+ end}].
+
+import(_, _) ->
+ pass.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_multicast.erl b/src/mod_multicast.erl
index 96ebcb6f..83520c0b 100644
--- a/src/mod_multicast.erl
+++ b/src/mod_multicast.erl
@@ -151,7 +151,7 @@ init([LServerS, Opts]) ->
try_start_loop(),
create_pool(),
ejabberd_router_multicast:register_route(LServerS),
- ejabberd_router:register_route(LServiceS),
+ ejabberd_router:register_route(LServiceS, LServerS),
{ok,
#state{lservice = LServiceS, lserver = LServerS,
access = Access, service_limits = SLimits}}.
@@ -233,7 +233,7 @@ handle_iq(From, To, #xmlel{attrs = Attrs} = Packet, State) ->
ejabberd_router:route(To, From, Err);
reply ->
LServiceS = jts(To),
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"result">> ->
process_iqreply_result(From, LServiceS, Packet, State);
<<"error">> ->
@@ -395,7 +395,7 @@ act_groups(FromJID, Packet_stripped, AAttrs, LServiceS,
perform(From, Packet, AAttrs, _,
{route_single, Group}) ->
[route_packet(From, ToUser, Packet, AAttrs,
- Group#group.addresses)
+ Group#group.others, Group#group.addresses)
|| ToUser <- Group#group.dests];
perform(From, Packet, AAttrs, _,
{{route_multicast, JID, RLimits}, Group}) ->
@@ -436,17 +436,17 @@ check_access(LServerS, Access, From) ->
%%%-------------------------
strip_addresses_element(Packet) ->
- case xml:get_subtag(Packet, <<"addresses">>) of
+ case fxml:get_subtag(Packet, <<"addresses">>) of
#xmlel{name = <<"addresses">>, attrs = AAttrs,
children = Addresses} ->
- case xml:get_attr_s(<<"xmlns">>, AAttrs) of
+ case fxml:get_attr_s(<<"xmlns">>, AAttrs) of
?NS_ADDRESS ->
#xmlel{name = Name, attrs = Attrs, children = Els} =
Packet,
Els_stripped = lists:keydelete(<<"addresses">>, 2, Els),
Packet_stripped = #xmlel{name = Name, attrs = Attrs,
children = Els_stripped},
- {ok, Packet_stripped, AAttrs, xml:remove_cdata(Addresses)};
+ {ok, Packet_stripped, AAttrs, fxml:remove_cdata(Addresses)};
_ -> throw(ewxmlns)
end;
_ -> throw(eadsele)
@@ -460,10 +460,10 @@ split_addresses_todeliver(Addresses) ->
lists:partition(fun (XML) ->
case XML of
#xmlel{name = <<"address">>, attrs = Attrs} ->
- case xml:get_attr_s(<<"delivered">>, Attrs) of
+ case fxml:get_attr_s(<<"delivered">>, Attrs) of
<<"true">> -> false;
_ ->
- Type = xml:get_attr_s(<<"type">>,
+ Type = fxml:get_attr_s(<<"type">>,
Attrs),
case Type of
<<"to">> -> true;
@@ -499,10 +499,10 @@ check_limit_dests(SLimits, FromJID, Packet,
convert_dest_record(XMLs) ->
lists:map(fun (XML) ->
- case xml:get_tag_attr_s(<<"jid">>, XML) of
+ case fxml:get_tag_attr_s(<<"jid">>, XML) of
<<"">> -> #dest{jid_string = none, full_xml = XML};
JIDS ->
- Type = xml:get_tag_attr_s(<<"type">>, XML),
+ Type = fxml:get_tag_attr_s(<<"type">>, XML),
JIDJ = stj(JIDS),
#dest{jid_string = JIDS, jid_jid = JIDJ,
type = Type, full_xml = XML}
@@ -525,7 +525,7 @@ split_dests_jid(Dests) ->
Dests).
report_not_jid(From, Packet, Dests) ->
- Dests2 = [xml:element_to_binary(Dest#dest.full_xml)
+ Dests2 = [fxml:element_to_binary(Dest#dest.full_xml)
|| Dest <- Dests],
[route_error(From, From, Packet, jid_malformed,
<<"This service can not process the address: ",
@@ -634,13 +634,13 @@ decide_action_group(Group) ->
%%% Route packet
%%%-------------------------
-route_packet(From, ToDest, Packet, AAttrs, Addresses) ->
+route_packet(From, ToDest, Packet, AAttrs, Others, Addresses) ->
Dests = case ToDest#dest.type of
<<"bcc">> -> [];
_ -> [ToDest]
end,
route_packet2(From, ToDest#dest.jid_string, Dests,
- Packet, AAttrs, Addresses).
+ Packet, AAttrs, {Others, Addresses}).
route_packet_multicast(From, ToS, Packet, AAttrs, Dests,
Addresses, Limits) ->
@@ -666,6 +666,8 @@ route_packet2(From, ToS, Dests, Packet, AAttrs,
ToJID = stj(ToS),
ejabberd_router:route(From, ToJID, Packet2).
+append_dests(_Dests, {Others, Addresses}) ->
+ Addresses++Others;
append_dests([], Addresses) -> Addresses;
append_dests([Dest | Dests], Addresses) ->
append_dests(Dests, [Dest#dest.full_xml | Addresses]).
@@ -734,8 +736,8 @@ process_iqreply_error(From, LServiceS, _Packet) ->
process_iqreply_result(From, LServiceS, Packet, State) ->
#xmlel{name = <<"query">>, attrs = Attrs2,
children = Els2} =
- xml:get_subtag(Packet, <<"query">>),
- case xml:get_attr_s(<<"xmlns">>, Attrs2) of
+ fxml:get_subtag(Packet, <<"query">>),
+ case fxml:get_attr_s(<<"xmlns">>, Attrs2) of
?NS_DISCO_INFO ->
process_discoinfo_result(From, LServiceS, Els2, State);
?NS_DISCO_ITEMS ->
@@ -763,7 +765,7 @@ process_discoinfo_result2(From, FromS, LServiceS, Els,
fun(XML) ->
case XML of
#xmlel{name = <<"feature">>, attrs = Attrs} ->
- (?NS_ADDRESS) == xml:get_attr_s(<<"var">>, Attrs);
+ (?NS_ADDRESS) == fxml:get_attr_s(<<"var">>, Attrs);
_ -> false
end
end,
@@ -807,10 +809,10 @@ get_limits_els(Els) ->
#xmlel{name = <<"x">>, attrs = Attrs,
children = SubEls} ->
case ((?NS_XDATA) ==
- xml:get_attr_s(<<"xmlns">>, Attrs))
+ fxml:get_attr_s(<<"xmlns">>, Attrs))
and
(<<"result">> ==
- xml:get_attr_s(<<"type">>, Attrs))
+ fxml:get_attr_s(<<"type">>, Attrs))
of
true -> get_limits_fields(SubEls) ++ R;
false -> R
@@ -826,11 +828,11 @@ get_limits_fields(Fields) ->
#xmlel{name = <<"field">>,
attrs = Attrs} ->
(<<"FORM_TYPE">> ==
- xml:get_attr_s(<<"var">>,
+ fxml:get_attr_s(<<"var">>,
Attrs))
and
(<<"hidden">> ==
- xml:get_attr_s(<<"type">>,
+ fxml:get_attr_s(<<"type">>,
Attrs));
_ -> false
end
@@ -848,8 +850,8 @@ get_limits_values(Values) ->
children = SubEls} ->
[#xmlel{name = <<"value">>, children = SubElsV}] =
SubEls,
- Number = xml:get_cdata(SubElsV),
- Name = xml:get_attr_s(<<"var">>, Attrs),
+ Number = fxml:get_cdata(SubElsV),
+ Name = fxml:get_attr_s(<<"var">>, Attrs),
[{jlib:binary_to_atom(Name),
jlib:binary_to_integer(Number)}
| R];
@@ -870,7 +872,7 @@ process_discoitems_result(From, LServiceS, Els) ->
fun(XML, Res) ->
case XML of
#xmlel{name = <<"item">>, attrs = Attrs} ->
- SJID = xml:get_attr_s(<<"jid">>, Attrs),
+ SJID = fxml:get_attr_s(<<"jid">>, Attrs),
case jid:from_string(SJID) of
#jid{luser = <<"">>,
lresource = <<"">>} ->
@@ -912,8 +914,9 @@ received_awaiter(JID, Waiter, LServiceS) ->
From = Waiter#waiter.sender,
Packet = Waiter#waiter.packet,
AAttrs = Waiter#waiter.aattrs,
+ Others = Group#group.others,
Addresses = Waiter#waiter.addresses,
- [route_packet(From, ToUser, Packet, AAttrs, Addresses)
+ [route_packet(From, ToUser, Packet, AAttrs, Others, Addresses)
|| ToUser <- Group#group.dests];
true ->
send_query_info(RServer, LServiceS),
@@ -1196,7 +1199,7 @@ to_binary(A) -> list_to_binary(hd(io_lib:format("~p", [A]))).
route_error(From, To, Packet, ErrType, ErrText) ->
#xmlel{attrs = Attrs} = Packet,
- Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
Reply = make_reply(ErrType, Lang, ErrText),
Err = jlib:make_error_reply(Packet, Reply),
ejabberd_router:route(From, To, Err).
diff --git a/src/mod_offline.erl b/src/mod_offline.erl
index 1b1627e8..356d89a6 100644
--- a/src/mod_offline.erl
+++ b/src/mod_offline.erl
@@ -27,6 +27,7 @@
-author('alexey@process-one.net').
+-protocol({xep, 13, '1.2'}).
-protocol({xep, 22, '1.4'}).
-protocol({xep, 23, '1.3'}).
-protocol({xep, 160, '1.0'}).
@@ -37,15 +38,18 @@
-behaviour(gen_mod).
--export([count_offline_messages/2]).
-
-export([start/2,
start_link/2,
stop/1,
store_packet/3,
+ store_offline_msg/5,
resend_offline_messages/2,
pop_offline_messages/3,
get_sm_features/5,
+ get_sm_identity/5,
+ get_sm_items/5,
+ get_info/5,
+ handle_offline_query/3,
remove_expired_messages/1,
remove_old_messages/2,
remove_user/2,
@@ -53,7 +57,9 @@
import/3,
export/1,
get_queue_length/2,
+ count_offline_messages/2,
get_offline_els/2,
+ find_x_expire/2,
webadmin_page/3,
webadmin_user/4,
webadmin_user_parse_query/5]).
@@ -62,6 +68,8 @@
handle_info/2, terminate/2, code_change/3,
mod_opt_type/1]).
+-deprecated({get_queue_length,2}).
+
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -80,6 +88,25 @@
%% default value for the maximum number of user messages
-define(MAX_USER_MESSAGES, infinity).
+-type us() :: {binary(), binary()}.
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #offline_msg{}) -> ok | pass.
+-callback store_messages(binary(), us(), [#offline_msg{}],
+ non_neg_integer(), non_neg_integer()) ->
+ {atomic, any()}.
+-callback pop_messages(binary(), binary()) ->
+ {atomic, [#offline_msg{}]} | {aborted, any()}.
+-callback remove_expired_messages(binary()) -> {atomic, any()}.
+-callback remove_old_messages(non_neg_integer(), binary()) -> {atomic, any()}.
+-callback remove_user(binary(), binary()) -> {atomic, any()}.
+-callback read_message_headers(binary(), binary()) -> any().
+-callback read_message(binary(), binary(), non_neg_integer()) ->
+ {ok, #offline_msg{}} | error.
+-callback remove_message(binary(), binary(), non_neg_integer()) -> ok.
+-callback read_all_messages(binary(), binary()) -> [#offline_msg{}].
+-callback remove_all_messages(binary(), binary()) -> {atomic, any()}.
+-callback count_messages(binary(), binary()) -> non_neg_integer().
+
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
?GEN_SERVER:start_link({local, Proc}, ?MODULE,
@@ -104,14 +131,10 @@ stop(Host) ->
%%====================================================================
init([Host, Opts]) ->
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(offline_msg,
- [{disc_only_copies, [node()]}, {type, bag},
- {attributes, record_info(fields, offline_msg)}]),
- update_table();
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+ no_queue),
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE,
store_packet, 50),
ejabberd_hooks:add(resend_offline_messages_hook, Host,
@@ -124,12 +147,19 @@ init([Host, Opts]) ->
?MODULE, get_sm_features, 50),
ejabberd_hooks:add(disco_local_features, Host,
?MODULE, get_sm_features, 50),
+ ejabberd_hooks:add(disco_sm_identity, Host,
+ ?MODULE, get_sm_identity, 50),
+ ejabberd_hooks:add(disco_sm_items, Host,
+ ?MODULE, get_sm_items, 50),
+ ejabberd_hooks:add(disco_info, Host, ?MODULE, get_info, 50),
ejabberd_hooks:add(webadmin_page_host, Host,
?MODULE, webadmin_page, 50),
ejabberd_hooks:add(webadmin_user, Host,
?MODULE, webadmin_user, 50),
ejabberd_hooks:add(webadmin_user_parse_query, Host,
?MODULE, webadmin_user_parse_query, 50),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE,
+ ?MODULE, handle_offline_query, IQDisc),
AccessMaxOfflineMsgs =
gen_mod:get_opt(access_max_user_messages, Opts,
fun(A) when is_atom(A) -> A end,
@@ -154,7 +184,7 @@ handle_info(#offline_msg{us = UserServer} = Msg, State) ->
Len = length(Msgs),
MaxOfflineMsgs = get_max_user_messages(AccessMaxOfflineMsgs,
UserServer, Host),
- store_offline_msg(Host, UserServer, Msgs, Len, MaxOfflineMsgs, DBType),
+ store_offline_msg(Host, UserServer, Msgs, Len, MaxOfflineMsgs),
{noreply, State};
handle_info(_Info, State) ->
@@ -174,77 +204,28 @@ terminate(_Reason, State) ->
?MODULE, remove_user, 50),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_sm_features, 50),
+ ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 50),
+ ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_items, 50),
+ ejabberd_hooks:delete(disco_info, Host, ?MODULE, get_info, 50),
ejabberd_hooks:delete(webadmin_page_host, Host,
?MODULE, webadmin_page, 50),
ejabberd_hooks:delete(webadmin_user, Host,
?MODULE, webadmin_user, 50),
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
?MODULE, webadmin_user_parse_query, 50),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE),
ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
-
-store_offline_msg(_Host, US, Msgs, Len, MaxOfflineMsgs,
- mnesia) ->
- F = fun () ->
- Count = if MaxOfflineMsgs =/= infinity ->
- Len + count_mnesia_records(US);
- true -> 0
- end,
- if Count > MaxOfflineMsgs -> discard_warn_sender(Msgs);
- true ->
- if Len >= (?OFFLINE_TABLE_LOCK_THRESHOLD) ->
- mnesia:write_lock_table(offline_msg);
- true -> ok
- end,
- lists:foreach(fun (M) -> mnesia:write(M) end, Msgs)
- end
- end,
- mnesia:transaction(F);
-store_offline_msg(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs, odbc) ->
- Count = if MaxOfflineMsgs =/= infinity ->
- Len + count_offline_messages(User, Host);
- true -> 0
- end,
- if Count > MaxOfflineMsgs -> discard_warn_sender(Msgs);
- true ->
- Query = lists:map(fun (M) ->
- Username =
- ejabberd_odbc:escape((M#offline_msg.to)#jid.luser),
- From = M#offline_msg.from,
- To = M#offline_msg.to,
- Packet =
- jlib:replace_from_to(From, To,
- M#offline_msg.packet),
- NewPacket =
- jlib:add_delay_info(Packet, Host,
- M#offline_msg.timestamp,
- <<"Offline Storage">>),
- XML =
- ejabberd_odbc:escape(xml:element_to_binary(NewPacket)),
- odbc_queries:add_spool_sql(Username, XML)
- end,
- Msgs),
- odbc_queries:add_spool(Host, Query)
- end;
-store_offline_msg(Host, {User, _}, Msgs, Len, MaxOfflineMsgs,
- riak) ->
- Count = if MaxOfflineMsgs =/= infinity ->
- Len + count_offline_messages(User, Host);
- true -> 0
- end,
- if
- Count > MaxOfflineMsgs ->
- discard_warn_sender(Msgs);
- true ->
- lists:foreach(
- fun(#offline_msg{us = US,
- timestamp = TS} = M) ->
- ejabberd_riak:put(M, offline_msg_schema(),
- [{i, TS}, {'2i', [{<<"us">>, US}]}])
- end, Msgs)
+store_offline_msg(Host, US, Msgs, Len, MaxOfflineMsgs) ->
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ case Mod:store_messages(Host, US, Msgs, Len, MaxOfflineMsgs) of
+ {atomic, discard} ->
+ discard_warn_sender(Msgs);
+ _ ->
+ ok
end.
get_max_user_messages(AccessRule, {User, Server}, Host) ->
@@ -262,7 +243,7 @@ receive_all(US, Msgs, DBType) ->
after 0 ->
case DBType of
mnesia -> Msgs;
- odbc -> lists:reverse(Msgs);
+ sql -> lists:reverse(Msgs);
riak -> Msgs
end
end.
@@ -272,38 +253,215 @@ get_sm_features(Acc, _From, _To, <<"">>, _Lang) ->
{result, I} -> I;
_ -> []
end,
- {result, Feats ++ [?NS_FEATURE_MSGOFFLINE]};
+ {result, Feats ++ [?NS_FEATURE_MSGOFFLINE, ?NS_FLEX_OFFLINE]};
get_sm_features(_Acc, _From, _To, ?NS_FEATURE_MSGOFFLINE, _Lang) ->
%% override all lesser features...
{result, []};
+get_sm_features(_Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S},
+ ?NS_FLEX_OFFLINE, _Lang) ->
+ {result, [?NS_FLEX_OFFLINE]};
+
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
+get_sm_identity(_Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S},
+ ?NS_FLEX_OFFLINE, _Lang) ->
+ Identity = #xmlel{name = <<"identity">>,
+ attrs = [{<<"category">>, <<"automation">>},
+ {<<"type">>, <<"message-list">>}]},
+ [Identity];
+get_sm_identity(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+get_sm_items(_Acc, #jid{luser = U, lserver = S, lresource = R} = JID,
+ #jid{luser = U, lserver = S},
+ ?NS_FLEX_OFFLINE, _Lang) ->
+ case ejabberd_sm:get_session_pid(U, S, R) of
+ Pid when is_pid(Pid) ->
+ Hdrs = read_message_headers(U, S),
+ BareJID = jid:to_string(jid:remove_resource(JID)),
+ Pid ! dont_ask_offline,
+ {result, lists:map(
+ fun({Node, From, _To, _El}) ->
+ #xmlel{name = <<"item">>,
+ attrs = [{<<"jid">>, BareJID},
+ {<<"node">>, Node},
+ {<<"name">>, jid:to_string(From)}]}
+ end, Hdrs)};
+ none ->
+ {result, []}
+ end;
+get_sm_items(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+get_info(_Acc, #jid{luser = U, lserver = S, lresource = R},
+ #jid{luser = U, lserver = S}, ?NS_FLEX_OFFLINE, _Lang) ->
+ N = jlib:integer_to_binary(count_offline_messages(U, S)),
+ case ejabberd_sm:get_session_pid(U, S, R) of
+ Pid when is_pid(Pid) ->
+ Pid ! dont_ask_offline;
+ none ->
+ ok
+ end,
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"result">>}],
+ children = [#xmlel{name = <<"field">>,
+ attrs = [{<<"var">>, <<"FORM_TYPE">>},
+ {<<"type">>, <<"hidden">>}],
+ children = [#xmlel{name = <<"value">>,
+ children = [{xmlcdata,
+ ?NS_FLEX_OFFLINE}]}]},
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"var">>, <<"number_of_messages">>}],
+ children = [#xmlel{name = <<"value">>,
+ children = [{xmlcdata, N}]}]}]}];
+get_info(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+handle_offline_query(#jid{luser = U, lserver = S} = From,
+ #jid{luser = U, lserver = S} = _To,
+ #iq{type = Type, sub_el = SubEl} = IQ) ->
+ case Type of
+ get ->
+ case fxml:get_subtag(SubEl, <<"fetch">>) of
+ #xmlel{} ->
+ handle_offline_fetch(From);
+ false ->
+ handle_offline_items_view(From, SubEl)
+ end;
+ set ->
+ case fxml:get_subtag(SubEl, <<"purge">>) of
+ #xmlel{} ->
+ delete_all_msgs(U, S);
+ false ->
+ handle_offline_items_remove(From, SubEl)
+ end
+ end,
+ IQ#iq{type = result, sub_el = []};
+handle_offline_query(_From, _To, #iq{sub_el = SubEl, lang = Lang} = IQ) ->
+ Txt = <<"Query to another users is forbidden">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}.
+
+handle_offline_items_view(JID, #xmlel{children = Items}) ->
+ {U, S, R} = jid:tolower(JID),
+ lists:foreach(
+ fun(Node) ->
+ case fetch_msg_by_node(JID, Node) of
+ {ok, OfflineMsg} ->
+ case offline_msg_to_route(S, OfflineMsg) of
+ {route, From, To, El} ->
+ NewEl = set_offline_tag(El, Node),
+ case ejabberd_sm:get_session_pid(U, S, R) of
+ Pid when is_pid(Pid) ->
+ Pid ! {route, From, To, NewEl};
+ none ->
+ ok
+ end;
+ error ->
+ ok
+ end;
+ error ->
+ ok
+ end
+ end, get_nodes_from_items(Items, <<"view">>)).
+
+handle_offline_items_remove(JID, #xmlel{children = Items}) ->
+ lists:foreach(
+ fun(Node) ->
+ remove_msg_by_node(JID, Node)
+ end, get_nodes_from_items(Items, <<"remove">>)).
+
+get_nodes_from_items(Items, Action) ->
+ lists:flatmap(
+ fun(#xmlel{name = <<"item">>, attrs = Attrs}) ->
+ case fxml:get_attr_s(<<"action">>, Attrs) of
+ Action ->
+ case fxml:get_attr_s(<<"node">>, Attrs) of
+ <<"">> ->
+ [];
+ TS ->
+ [TS]
+ end;
+ _ ->
+ []
+ end;
+ (_) ->
+ []
+ end, Items).
+
+set_offline_tag(#xmlel{children = Els} = El, Node) ->
+ OfflineEl = #xmlel{name = <<"offline">>,
+ attrs = [{<<"xmlns">>, ?NS_FLEX_OFFLINE}],
+ children = [#xmlel{name = <<"item">>,
+ attrs = [{<<"node">>, Node}]}]},
+ El#xmlel{children = [OfflineEl|Els]}.
+
+handle_offline_fetch(#jid{luser = U, lserver = S, lresource = R}) ->
+ case ejabberd_sm:get_session_pid(U, S, R) of
+ none ->
+ ok;
+ Pid when is_pid(Pid) ->
+ Pid ! dont_ask_offline,
+ lists:foreach(
+ fun({Node, From, To, El}) ->
+ NewEl = set_offline_tag(El, Node),
+ Pid ! {route, From, To, NewEl}
+ end, read_message_headers(U, S))
+ end.
+
+fetch_msg_by_node(To, Seq) ->
+ case catch binary_to_integer(Seq) of
+ I when is_integer(I), I >= 0 ->
+ LUser = To#jid.luser,
+ LServer = To#jid.lserver,
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:read_message(LUser, LServer, I);
+ _ ->
+ error
+ end.
+
+remove_msg_by_node(To, Seq) ->
+ case catch binary_to_integer(Seq) of
+ I when is_integer(I), I>= 0 ->
+ LUser = To#jid.luser,
+ LServer = To#jid.lserver,
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_message(LUser, LServer, I);
+ _ ->
+ ok
+ end.
+
need_to_store(LServer, Packet) ->
- Type = xml:get_tag_attr_s(<<"type">>, Packet),
+ Type = fxml:get_tag_attr_s(<<"type">>, Packet),
if (Type /= <<"error">>) and (Type /= <<"groupchat">>)
and (Type /= <<"headline">>) ->
- case check_store_hint(Packet) of
- store ->
- true;
- no_store ->
- false;
- none ->
- case gen_mod:get_module_opt(
- LServer, ?MODULE, store_empty_body,
- fun(V) when is_boolean(V) -> V;
- (unless_chat_state) -> unless_chat_state
- end,
- unless_chat_state) of
- false ->
- xml:get_subtag(Packet, <<"body">>) /= false;
- unless_chat_state ->
- not jlib:is_standalone_chat_state(Packet);
- true ->
- true
- end
+ case has_offline_tag(Packet) of
+ false ->
+ case check_store_hint(Packet) of
+ store ->
+ true;
+ no_store ->
+ false;
+ none ->
+ case gen_mod:get_module_opt(
+ LServer, ?MODULE, store_empty_body,
+ fun(V) when is_boolean(V) -> V;
+ (unless_chat_state) -> unless_chat_state
+ end,
+ unless_chat_state) of
+ false ->
+ fxml:get_subtag(Packet, <<"body">>) /= false;
+ unless_chat_state ->
+ not jlib:is_standalone_chat_state(Packet);
+ true ->
+ true
+ end
+ end;
+ true ->
+ false
end;
true ->
false
@@ -342,12 +500,15 @@ check_store_hint(Packet) ->
end.
has_store_hint(Packet) ->
- xml:get_subtag_with_xmlns(Packet, <<"store">>, ?NS_HINTS) =/= false.
+ fxml:get_subtag_with_xmlns(Packet, <<"store">>, ?NS_HINTS) =/= false.
has_no_store_hint(Packet) ->
- xml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS) =/= false
+ fxml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS) =/= false
orelse
- xml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS) =/= false.
+ fxml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS) =/= false.
+
+has_offline_tag(Packet) ->
+ fxml:get_subtag_with_xmlns(Packet, <<"offline">>, ?NS_FLEX_OFFLINE) =/= false.
%% Check if the packet has any content about XEP-0022
check_event(From, To, Packet) ->
@@ -356,12 +517,12 @@ check_event(From, To, Packet) ->
case find_x_event(Els) of
false -> true;
El ->
- case xml:get_subtag(El, <<"id">>) of
+ case fxml:get_subtag(El, <<"id">>) of
false ->
- case xml:get_subtag(El, <<"offline">>) of
+ case fxml:get_subtag(El, <<"offline">>) of
false -> true;
_ ->
- ID = case xml:get_tag_attr_s(<<"id">>, Packet) of
+ ID = case fxml:get_tag_attr_s(<<"id">>, Packet) of
<<"">> ->
#xmlel{name = <<"id">>, attrs = [],
children = []};
@@ -398,7 +559,7 @@ find_x_event([]) -> false;
find_x_event([{xmlcdata, _} | Els]) ->
find_x_event(Els);
find_x_event([El | Els]) ->
- case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, El) of
?NS_EVENT -> El;
_ -> find_x_event(Els)
end.
@@ -407,9 +568,9 @@ find_x_expire(_, []) -> never;
find_x_expire(TimeStamp, [{xmlcdata, _} | Els]) ->
find_x_expire(TimeStamp, Els);
find_x_expire(TimeStamp, [El | Els]) ->
- case xml:get_tag_attr_s(<<"xmlns">>, El) of
+ case fxml:get_tag_attr_s(<<"xmlns">>, El) of
?NS_EXPIRE ->
- Val = xml:get_tag_attr_s(<<"seconds">>, El),
+ Val = fxml:get_tag_attr_s(<<"seconds">>, El),
case catch jlib:binary_to_integer(Val) of
{'EXIT', _} -> never;
Int when Int > 0 ->
@@ -426,21 +587,11 @@ find_x_expire(TimeStamp, [El | Els]) ->
resend_offline_messages(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- US = {LUser, LServer},
- F = fun () ->
- Rs = mnesia:wread({offline_msg, US}),
- mnesia:delete({offline_msg, US}),
- Rs
- end,
- case mnesia:transaction(F) of
- {atomic, Rs} ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:pop_messages(LUser, LServer) of
+ {ok, Rs} ->
lists:foreach(fun (R) ->
- ejabberd_sm !
- {route, R#offline_msg.from, R#offline_msg.to,
- jlib:add_delay_info(R#offline_msg.packet,
- LServer,
- R#offline_msg.timestamp,
- <<"Offline Storage">>)}
+ ejabberd_sm ! offline_msg_to_route(LServer, R)
end,
lists:keysort(#offline_msg.timestamp, Rs));
_ -> ok
@@ -449,194 +600,47 @@ resend_offline_messages(User, Server) ->
pop_offline_messages(Ls, User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- pop_offline_messages(Ls, LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-pop_offline_messages(Ls, LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () ->
- Rs = mnesia:wread({offline_msg, US}),
- mnesia:delete({offline_msg, US}),
- Rs
- end,
- case mnesia:transaction(F) of
- {atomic, Rs} ->
- TS = p1_time_compat:timestamp(),
- Ls ++
- lists:map(fun (R) ->
- offline_msg_to_route(LServer, R)
- end,
- lists:filter(fun (R) ->
- case R#offline_msg.expire of
- never -> true;
- TimeStamp -> TS < TimeStamp
- end
- end,
- lists:keysort(#offline_msg.timestamp, Rs)));
- _ -> Ls
- end;
-pop_offline_messages(Ls, LUser, LServer, odbc) ->
- EUser = ejabberd_odbc:escape(LUser),
- case odbc_queries:get_and_del_spool_msg_t(LServer,
- EUser)
- of
- {atomic, {selected, [<<"username">>, <<"xml">>], Rs}} ->
- Ls ++
- lists:flatmap(fun ([_, XML]) ->
- case xml_stream:parse_element(XML) of
- {error, _Reason} ->
- [];
- El ->
- case offline_msg_to_route(LServer, El) of
- error ->
- [];
- RouteMsg ->
- [RouteMsg]
- end
- end
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:pop_messages(LUser, LServer) of
+ {ok, Rs} ->
+ TS = p1_time_compat:timestamp(),
+ Ls ++
+ lists:map(fun (R) ->
+ offline_msg_to_route(LServer, R)
end,
- Rs);
- _ -> Ls
- end;
-pop_offline_messages(Ls, LUser, LServer, riak) ->
- case ejabberd_riak:get_by_index(offline_msg, offline_msg_schema(),
- <<"us">>, {LUser, LServer}) of
- {ok, Rs} ->
- try
- lists:foreach(
- fun(#offline_msg{timestamp = T}) ->
- ok = ejabberd_riak:delete(offline_msg, T)
- end, Rs),
- TS = p1_time_compat:timestamp(),
- Ls ++ lists:map(
- fun (R) ->
- offline_msg_to_route(LServer, R)
- end,
- lists:filter(
- fun(R) ->
- case R#offline_msg.expire of
- never -> true;
- TimeStamp -> TS < TimeStamp
- end
- end,
- lists:keysort(#offline_msg.timestamp, Rs)))
- catch _:{badmatch, _} ->
- Ls
- end;
+ lists:filter(
+ fun(#offline_msg{packet = Pkt} = R) ->
+ #xmlel{children = Els} = Pkt,
+ Expire = case R#offline_msg.expire of
+ undefined ->
+ find_x_expire(TS, Els);
+ Exp ->
+ Exp
+ end,
+ case Expire of
+ never -> true;
+ TimeStamp -> TS < TimeStamp
+ end
+ end, Rs));
_ ->
Ls
end.
remove_expired_messages(Server) ->
LServer = jid:nameprep(Server),
- remove_expired_messages(LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-remove_expired_messages(_LServer, mnesia) ->
- TimeStamp = p1_time_compat:timestamp(),
- F = fun () ->
- mnesia:write_lock_table(offline_msg),
- mnesia:foldl(fun (Rec, _Acc) ->
- case Rec#offline_msg.expire of
- never -> ok;
- TS ->
- if TS < TimeStamp ->
- mnesia:delete_object(Rec);
- true -> ok
- end
- end
- end,
- ok, offline_msg)
- end,
- mnesia:transaction(F);
-remove_expired_messages(_LServer, odbc) -> {atomic, ok};
-remove_expired_messages(_LServer, riak) -> {atomic, ok}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_expired_messages(LServer).
remove_old_messages(Days, Server) ->
LServer = jid:nameprep(Server),
- remove_old_messages(Days, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-remove_old_messages(Days, _LServer, mnesia) ->
- S = p1_time_compat:system_time(seconds) - 60 * 60 * 24 * Days,
- MegaSecs1 = S div 1000000,
- Secs1 = S rem 1000000,
- TimeStamp = {MegaSecs1, Secs1, 0},
- F = fun () ->
- mnesia:write_lock_table(offline_msg),
- mnesia:foldl(fun (#offline_msg{timestamp = TS} = Rec,
- _Acc)
- when TS < TimeStamp ->
- mnesia:delete_object(Rec);
- (_Rec, _Acc) -> ok
- end,
- ok, offline_msg)
- end,
- mnesia:transaction(F);
-
-remove_old_messages(Days, LServer, odbc) ->
- case catch ejabberd_odbc:sql_query(
- LServer,
- [<<"DELETE FROM spool"
- " WHERE created_at < "
- "DATE_SUB(CURDATE(), INTERVAL ">>,
- integer_to_list(Days), <<" DAY);">>]) of
- {updated, N} ->
- ?INFO_MSG("~p message(s) deleted from offline spool", [N]);
- _Error ->
- ?ERROR_MSG("Cannot delete message in offline spool: ~p", [_Error])
- end,
- {atomic, ok};
-remove_old_messages(_Days, _LServer, riak) ->
- {atomic, ok}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_old_messages(Days, LServer).
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- remove_user(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-remove_user(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () -> mnesia:delete({offline_msg, US}) end,
- mnesia:transaction(F);
-remove_user(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_spool_msg(LServer, Username);
-remove_user(LUser, LServer, riak) ->
- {atomic, ejabberd_riak:delete_by_index(offline_msg,
- <<"us">>, {LUser, LServer})}.
-
-jid_to_binary(#jid{user = U, server = S, resource = R,
- luser = LU, lserver = LS, lresource = LR}) ->
- #jid{user = iolist_to_binary(U),
- server = iolist_to_binary(S),
- resource = iolist_to_binary(R),
- luser = iolist_to_binary(LU),
- lserver = iolist_to_binary(LS),
- lresource = iolist_to_binary(LR)}.
-
-update_table() ->
- Fields = record_info(fields, offline_msg),
- case mnesia:table_info(offline_msg, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- offline_msg, Fields, bag,
- fun(#offline_msg{us = {U, _}}) -> U end,
- fun(#offline_msg{us = {U, S},
- from = From,
- to = To,
- packet = El} = R) ->
- R#offline_msg{us = {iolist_to_binary(U),
- iolist_to_binary(S)},
- from = jid_to_binary(From),
- to = jid_to_binary(To),
- packet = xml:to_xmlel(El)}
- end);
- _ ->
- ?INFO_MSG("Recreating offline_msg table", []),
- mnesia:transform_table(offline_msg, ignore, Fields)
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_user(LUser, LServer).
%% Helper functions:
@@ -646,7 +650,7 @@ discard_warn_sender(Msgs) ->
packet = Packet}) ->
ErrText = <<"Your contact offline message queue is "
"full. The message has been discarded.">>,
- Lang = xml:get_tag_attr_s(<<"xml:lang">>, Packet),
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Err = jlib:make_error_reply(Packet,
?ERRT_RESOURCE_CONSTRAINT(Lang,
ErrText)),
@@ -662,138 +666,71 @@ webadmin_page(_, Host,
webadmin_page(Acc, _, _) -> Acc.
get_offline_els(LUser, LServer) ->
- get_offline_els(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
-
-get_offline_els(LUser, LServer, DBType)
- when DBType == mnesia; DBType == riak ->
- Msgs = read_all_msgs(LUser, LServer, DBType),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Hdrs = Mod:read_message_headers(LUser, LServer),
lists:map(
- fun(Msg) ->
- {route, From, To, Packet} = offline_msg_to_route(LServer, Msg),
- jlib:replace_from_to(From, To, Packet)
- end, Msgs);
-get_offline_els(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select xml from spool where username='">>,
- Username, <<"' order by seq;">>]) of
- {selected, [<<"xml">>], Rs} ->
- lists:flatmap(
- fun([XML]) ->
- case xml_stream:parse_element(XML) of
- #xmlel{} = El ->
- case offline_msg_to_route(LServer, El) of
- {route, _, _, NewEl} ->
- [NewEl];
- error ->
- []
- end;
- _ ->
- []
- end
- end, Rs);
- _ ->
- []
- end.
+ fun({_Seq, From, To, Packet}) ->
+ jlib:replace_from_to(From, To, Packet)
+ end, Hdrs).
offline_msg_to_route(LServer, #offline_msg{} = R) ->
- {route, R#offline_msg.from, R#offline_msg.to,
- jlib:add_delay_info(R#offline_msg.packet, LServer, R#offline_msg.timestamp,
- <<"Offline Storage">>)};
-offline_msg_to_route(_LServer, #xmlel{} = El) ->
- To = jid:from_string(xml:get_tag_attr_s(<<"to">>, El)),
- From = jid:from_string(xml:get_tag_attr_s(<<"from">>, El)),
- if (To /= error) and (From /= error) ->
- {route, From, To, El};
- true ->
- error
- end.
-
-read_all_msgs(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- lists:keysort(#offline_msg.timestamp,
- mnesia:dirty_read({offline_msg, US}));
-read_all_msgs(LUser, LServer, riak) ->
- case ejabberd_riak:get_by_index(
- offline_msg, offline_msg_schema(),
- <<"us">>, {LUser, LServer}) of
- {ok, Rs} ->
- lists:keysort(#offline_msg.timestamp, Rs);
- _Err ->
- []
- end;
-read_all_msgs(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select xml from spool where username='">>,
- Username, <<"' order by seq;">>])
- of
- {selected, [<<"xml">>], Rs} ->
- lists:flatmap(fun ([XML]) ->
- case xml_stream:parse_element(XML) of
- {error, _Reason} -> [];
- El -> [El]
- end
- end,
- Rs);
- _ -> []
- end.
+ El = case R#offline_msg.timestamp of
+ undefined ->
+ R#offline_msg.packet;
+ TS ->
+ jlib:add_delay_info(R#offline_msg.packet, LServer, TS,
+ <<"Offline Storage">>)
+ end,
+ {route, R#offline_msg.from, R#offline_msg.to, El}.
+
+read_message_headers(LUser, LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ lists:map(
+ fun({Seq, From, To, El}) ->
+ Node = integer_to_binary(Seq),
+ {Node, From, To, El}
+ end, Mod:read_message_headers(LUser, LServer)).
-format_user_queue(Msgs, DBType) when DBType == mnesia; DBType == riak ->
- lists:map(fun (#offline_msg{timestamp = TimeStamp,
- from = From, to = To,
- packet =
- #xmlel{name = Name, attrs = Attrs,
- children = Els}} =
- Msg) ->
- ID = jlib:encode_base64((term_to_binary(Msg))),
- {{Year, Month, Day}, {Hour, Minute, Second}} =
- calendar:now_to_local_time(TimeStamp),
- Time =
- iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
- [Year, Month, Day,
- Hour, Minute,
- Second])),
- SFrom = jid:to_string(From),
- STo = jid:to_string(To),
- Attrs2 = jlib:replace_from_to_attrs(SFrom, STo, Attrs),
- Packet = #xmlel{name = Name, attrs = Attrs2,
- children = Els},
- FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
- ?XE(<<"tr">>,
- [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
- [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
- ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], Time),
- ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], SFrom),
- ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], STo),
- ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
- [?XC(<<"pre">>, FPacket)])])
- end,
- Msgs);
-format_user_queue(Msgs, odbc) ->
- lists:map(fun (#xmlel{} = Msg) ->
- ID = jlib:encode_base64((term_to_binary(Msg))),
- Packet = Msg,
- FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
- ?XE(<<"tr">>,
- [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
- [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
- ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
- [?XC(<<"pre">>, FPacket)])])
- end,
- Msgs).
+format_user_queue(Hdrs) ->
+ lists:map(
+ fun({Seq, From, To, El}) ->
+ ID = integer_to_binary(Seq),
+ FPacket = ejabberd_web_admin:pretty_print_xml(El),
+ SFrom = jid:to_string(From),
+ STo = jid:to_string(To),
+ Stamp = fxml:get_path_s(El, [{elem, <<"delay">>},
+ {attr, <<"stamp">>}]),
+ Time = case jlib:datetime_string_to_timestamp(Stamp) of
+ {_, _, _} = Now ->
+ {{Year, Month, Day}, {Hour, Minute, Second}} =
+ calendar:now_to_local_time(Now),
+ iolist_to_binary(
+ io_lib:format(
+ "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+ [Year, Month, Day, Hour, Minute,
+ Second]));
+ _ ->
+ <<"">>
+ end,
+ ?XE(<<"tr">>,
+ [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
+ [?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
+ ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], Time),
+ ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], SFrom),
+ ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], STo),
+ ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
+ [?XC(<<"pre">>, FPacket)])])
+ end, Hdrs).
user_queue(User, Server, Query, Lang) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
- DBType = gen_mod:db_type(LServer, ?MODULE),
- Res = user_queue_parse_query(LUser, LServer, Query,
- DBType),
- MsgsAll = read_all_msgs(LUser, LServer, DBType),
- Msgs = get_messages_subset(US, Server, MsgsAll,
- DBType),
- FMsgs = format_user_queue(Msgs, DBType),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Res = user_queue_parse_query(LUser, LServer, Query),
+ HdrsAll = Mod:read_message_headers(LUser, LServer),
+ Hdrs = get_messages_subset(US, Server, HdrsAll),
+ FMsgs = format_user_queue(Hdrs),
[?XC(<<"h1">>,
list_to_binary(io_lib:format(?T(<<"~s's Offline Messages Queue">>),
[us_to_list(US)])))]
@@ -823,128 +760,33 @@ user_queue(User, Server, Query, Lang) ->
?INPUTT(<<"submit">>, <<"delete">>,
<<"Delete Selected">>)])].
-user_queue_parse_query(LUser, LServer, Query, mnesia) ->
- US = {LUser, LServer},
- case lists:keysearch(<<"delete">>, 1, Query) of
- {value, _} ->
- Msgs = lists:keysort(#offline_msg.timestamp,
- mnesia:dirty_read({offline_msg, US})),
- F = fun () ->
- lists:foreach(fun (Msg) ->
- ID =
- jlib:encode_base64((term_to_binary(Msg))),
- case lists:member({<<"selected">>,
- ID},
- Query)
- of
- true -> mnesia:delete_object(Msg);
- false -> ok
- end
- end,
- Msgs)
- end,
- mnesia:transaction(F),
- ok;
- false -> nothing
- end;
-user_queue_parse_query(LUser, LServer, Query, riak) ->
- case lists:keysearch(<<"delete">>, 1, Query) of
- {value, _} ->
- Msgs = read_all_msgs(LUser, LServer, riak),
- lists:foreach(
- fun (Msg) ->
- ID = jlib:encode_base64((term_to_binary(Msg))),
- case lists:member({<<"selected">>, ID}, Query) of
- true ->
- ejabberd_riak:delete(offline_msg,
- Msg#offline_msg.timestamp);
- false ->
- ok
- end
- end,
- Msgs),
- ok;
- false ->
- nothing
- end;
-user_queue_parse_query(LUser, LServer, Query, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
+user_queue_parse_query(LUser, LServer, Query) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
case lists:keysearch(<<"delete">>, 1, Query) of
- {value, _} ->
- Msgs = case catch ejabberd_odbc:sql_query(LServer,
- [<<"select xml, seq from spool where username='">>,
- Username,
- <<"' order by seq;">>])
- of
- {selected, [<<"xml">>, <<"seq">>], Rs} ->
- lists:flatmap(fun ([XML, Seq]) ->
- case xml_stream:parse_element(XML)
- of
- {error, _Reason} -> [];
- El -> [{El, Seq}]
- end
- end,
- Rs);
- _ -> []
- end,
- F = fun () ->
- lists:foreach(fun ({Msg, Seq}) ->
- ID =
- jlib:encode_base64((term_to_binary(Msg))),
- case lists:member({<<"selected">>,
- ID},
- Query)
- of
- true ->
- SSeq =
- ejabberd_odbc:escape(Seq),
- catch
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from spool where username='">>,
- Username,
- <<"' and seq='">>,
- SSeq,
- <<"';">>]);
- false -> ok
- end
- end,
- Msgs)
- end,
- mnesia:transaction(F),
- ok;
- false -> nothing
+ {value, _} ->
+ case lists:keyfind(<<"selected">>, 1, Query) of
+ {_, Seq} ->
+ case catch binary_to_integer(Seq) of
+ I when is_integer(I), I>=0 ->
+ Mod:remove_message(LUser, LServer, I),
+ ok;
+ _ ->
+ nothing
+ end;
+ false ->
+ nothing
+ end;
+ _ ->
+ nothing
end.
us_to_list({User, Server}) ->
jid:to_string({User, Server, <<"">>}).
get_queue_length(LUser, LServer) ->
- get_queue_length(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-get_queue_length(LUser, LServer, mnesia) ->
- length(mnesia:dirty_read({offline_msg,
- {LUser, LServer}}));
-get_queue_length(LUser, LServer, riak) ->
- case ejabberd_riak:count_by_index(offline_msg,
- <<"us">>, {LUser, LServer}) of
- {ok, N} ->
- N;
- _ ->
- 0
- end;
-get_queue_length(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select count(*) from spool where username='">>,
- Username, <<"';">>])
- of
- {selected, [_], [[SCount]]} ->
- jlib:binary_to_integer(SCount);
- _ -> 0
- end.
+ count_offline_messages(LUser, LServer).
-get_messages_subset(User, Host, MsgsAll, DBType) ->
+get_messages_subset(User, Host, MsgsAll) ->
Access = gen_mod:get_module_opt(Host, ?MODULE, access_max_user_messages,
fun(A) when is_atom(A) -> A end,
max_user_offline_messages),
@@ -955,36 +797,23 @@ get_messages_subset(User, Host, MsgsAll, DBType) ->
_ -> 100
end,
Length = length(MsgsAll),
- get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll,
- DBType).
+ get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll).
-get_messages_subset2(Max, Length, MsgsAll, _DBType)
- when Length =< Max * 2 ->
+get_messages_subset2(Max, Length, MsgsAll) when Length =< Max * 2 ->
MsgsAll;
-get_messages_subset2(Max, Length, MsgsAll, DBType)
- when DBType == mnesia; DBType == riak ->
+get_messages_subset2(Max, Length, MsgsAll) ->
FirstN = Max,
{MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
MsgsLastN = lists:nthtail(Length - FirstN - FirstN,
Msgs2),
NoJID = jid:make(<<"...">>, <<"...">>, <<"">>),
- IntermediateMsg = #offline_msg{timestamp = p1_time_compat:timestamp(),
- from = NoJID, to = NoJID,
- packet =
- #xmlel{name = <<"...">>, attrs = [],
- children = []}},
- MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN;
-get_messages_subset2(Max, Length, MsgsAll, odbc) ->
- FirstN = Max,
- {MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
- MsgsLastN = lists:nthtail(Length - FirstN - FirstN,
- Msgs2),
+ Seq = <<"0">>,
IntermediateMsg = #xmlel{name = <<"...">>, attrs = [],
children = []},
- MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN.
+ MsgsFirstN ++ [{Seq, NoJID, NoJID, IntermediateMsg}] ++ MsgsLastN.
webadmin_user(Acc, User, Server, Lang) ->
- QueueLen = get_queue_length(jid:nodeprep(User),
+ QueueLen = count_offline_messages(jid:nodeprep(User),
jid:nameprep(Server)),
FQueueLen = [?AC(<<"queue/">>,
(iolist_to_binary(integer_to_list(QueueLen))))],
@@ -998,26 +827,8 @@ webadmin_user(Acc, User, Server, Lang) ->
delete_all_msgs(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- delete_all_msgs(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-delete_all_msgs(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () ->
- mnesia:write_lock_table(offline_msg),
- lists:foreach(fun (Msg) -> mnesia:delete_object(Msg)
- end,
- mnesia:dirty_read({offline_msg, US}))
- end,
- mnesia:transaction(F);
-delete_all_msgs(LUser, LServer, riak) ->
- Res = ejabberd_riak:delete_by_index(offline_msg,
- <<"us">>, {LUser, LServer}),
- {atomic, Res};
-delete_all_msgs(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_spool_msg(LServer, Username),
- {atomic, ok}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_all_messages(LUser, LServer).
webadmin_user_parse_query(_, <<"removealloffline">>,
User, Server, _Query) ->
@@ -1039,114 +850,20 @@ webadmin_user_parse_query(Acc, _Action, _User, _Server,
count_offline_messages(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- DBType = gen_mod:db_type(LServer, ?MODULE),
- count_offline_messages(LUser, LServer, DBType).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:count_messages(LUser, LServer).
-count_offline_messages(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () ->
- count_mnesia_records(US)
- end,
- case catch mnesia:async_dirty(F) of
- I when is_integer(I) -> I;
- _ -> 0
- end;
-count_offline_messages(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:count_records_where(LServer,
- <<"spool">>,
- <<"where username='",
- Username/binary, "'">>)
- of
- {selected, [_], [[Res]]} ->
- jlib:binary_to_integer(Res);
- _ -> 0
- end;
-count_offline_messages(LUser, LServer, riak) ->
- case ejabberd_riak:count_by_index(
- offline_msg, <<"us">>, {LUser, LServer}) of
- {ok, Res} ->
- Res;
- _ ->
- 0
- end.
-
-%% Return the number of records matching a given match expression.
-%% This function is intended to be used inside a Mnesia transaction.
-%% The count has been written to use the fewest possible memory by
-%% getting the record by small increment and by using continuation.
--define(BATCHSIZE, 100).
-
-count_mnesia_records(US) ->
- MatchExpression = #offline_msg{us = US, _ = '_'},
- case mnesia:select(offline_msg, [{MatchExpression, [], [[]]}],
- ?BATCHSIZE, read) of
- {Result, Cont} ->
- Count = length(Result),
- count_records_cont(Cont, Count);
- '$end_of_table' ->
- 0
- end.
-
-count_records_cont(Cont, Count) ->
- case mnesia:select(Cont) of
- {Result, Cont} ->
- NewCount = Count + length(Result),
- count_records_cont(Cont, NewCount);
- '$end_of_table' ->
- Count
- end.
-
-offline_msg_schema() ->
- {record_info(fields, offline_msg), #offline_msg{}}.
-
-export(_Server) ->
- [{offline_msg,
- fun(Host, #offline_msg{us = {LUser, LServer},
- timestamp = TimeStamp, from = From, to = To,
- packet = Packet})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- Packet1 = jlib:replace_from_to(From, To, Packet),
- Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp,
- <<"Offline Storage">>),
- XML = ejabberd_odbc:escape(xml:element_to_binary(Packet2)),
- [[<<"delete from spool where username='">>, Username, <<"';">>],
- [<<"insert into spool(username, xml) values ('">>,
- Username, <<"', '">>, XML, <<"');">>]];
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import(LServer) ->
- [{<<"select username, xml from spool;">>,
- fun([LUser, XML]) ->
- El = #xmlel{} = xml_stream:parse_element(XML),
- From = #jid{} = jid:from_string(
- xml:get_attr_s(<<"from">>, El#xmlel.attrs)),
- To = #jid{} = jid:from_string(
- xml:get_attr_s(<<"to">>, El#xmlel.attrs)),
- Stamp = xml:get_path_s(El, [{elem, <<"delay">>},
- {attr, <<"stamp">>}]),
- TS = case jlib:datetime_string_to_timestamp(Stamp) of
- {_, _, _} = Now ->
- Now;
- undefined ->
- p1_time_compat:timestamp()
- end,
- Expire = find_x_expire(TS, El#xmlel.children),
- #offline_msg{us = {LUser, LServer},
- from = From, to = To,
- timestamp = TS, expire = Expire}
- end}].
-
-import(_LServer, mnesia, #offline_msg{} = Msg) ->
- mnesia:dirty_write(Msg);
-import(_LServer, riak, #offline_msg{us = US, timestamp = TS} = M) ->
- ejabberd_riak:put(M, offline_msg_schema(),
- [{i, TS}, {'2i', [{<<"us">>, US}]}]);
-import(_, _, _) ->
- pass.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
+
+import(LServer, DBType, Data) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, Data).
mod_opt_type(access_max_user_messages) ->
fun (A) -> A end;
diff --git a/src/mod_offline_mnesia.erl b/src/mod_offline_mnesia.erl
new file mode 100644
index 00000000..6a1d9e30
--- /dev/null
+++ b/src/mod_offline_mnesia.erl
@@ -0,0 +1,232 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_offline_mnesia).
+
+-behaviour(mod_offline).
+
+-export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1,
+ remove_old_messages/2, remove_user/2, read_message_headers/2,
+ read_message/3, remove_message/3, read_all_messages/2,
+ remove_all_messages/2, count_messages/2, import/2]).
+
+-include("jlib.hrl").
+-include("mod_offline.hrl").
+-include("logger.hrl").
+
+-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(offline_msg,
+ [{disc_only_copies, [node()]}, {type, bag},
+ {attributes, record_info(fields, offline_msg)}]),
+ update_table().
+
+store_messages(_Host, US, Msgs, Len, MaxOfflineMsgs) ->
+ F = fun () ->
+ Count = if MaxOfflineMsgs =/= infinity ->
+ Len + count_mnesia_records(US);
+ true -> 0
+ end,
+ if Count > MaxOfflineMsgs -> discard;
+ true ->
+ if Len >= (?OFFLINE_TABLE_LOCK_THRESHOLD) ->
+ mnesia:write_lock_table(offline_msg);
+ true -> ok
+ end,
+ lists:foreach(fun (M) -> mnesia:write(M) end, Msgs)
+ end
+ end,
+ mnesia:transaction(F).
+
+pop_messages(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ Rs = mnesia:wread({offline_msg, US}),
+ mnesia:delete({offline_msg, US}),
+ Rs
+ end,
+ case mnesia:transaction(F) of
+ {atomic, L} ->
+ {ok, lists:keysort(#offline_msg.timestamp, L)};
+ {aborted, Reason} ->
+ {error, Reason}
+ end.
+
+remove_expired_messages(_LServer) ->
+ TimeStamp = p1_time_compat:timestamp(),
+ F = fun () ->
+ mnesia:write_lock_table(offline_msg),
+ mnesia:foldl(fun (Rec, _Acc) ->
+ case Rec#offline_msg.expire of
+ never -> ok;
+ TS ->
+ if TS < TimeStamp ->
+ mnesia:delete_object(Rec);
+ true -> ok
+ end
+ end
+ end,
+ ok, offline_msg)
+ end,
+ mnesia:transaction(F).
+
+remove_old_messages(Days, _LServer) ->
+ S = p1_time_compat:system_time(seconds) - 60 * 60 * 24 * Days,
+ MegaSecs1 = S div 1000000,
+ Secs1 = S rem 1000000,
+ TimeStamp = {MegaSecs1, Secs1, 0},
+ F = fun () ->
+ mnesia:write_lock_table(offline_msg),
+ mnesia:foldl(fun (#offline_msg{timestamp = TS} = Rec,
+ _Acc)
+ when TS < TimeStamp ->
+ mnesia:delete_object(Rec);
+ (_Rec, _Acc) -> ok
+ end,
+ ok, offline_msg)
+ end,
+ mnesia:transaction(F).
+
+remove_user(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () -> mnesia:delete({offline_msg, US}) end,
+ mnesia:transaction(F).
+
+read_message_headers(LUser, LServer) ->
+ Msgs = mnesia:dirty_read({offline_msg, {LUser, LServer}}),
+ Hdrs = lists:map(
+ fun(#offline_msg{from = From, to = To, packet = Pkt,
+ timestamp = TS}) ->
+ Seq = now_to_integer(TS),
+ NewPkt = jlib:add_delay_info(Pkt, LServer, TS,
+ <<"Offline Storage">>),
+ {Seq, From, To, NewPkt}
+ end, Msgs),
+ lists:keysort(1, Hdrs).
+
+read_message(LUser, LServer, I) ->
+ US = {LUser, LServer},
+ TS = integer_to_now(I),
+ case mnesia:dirty_match_object(
+ offline_msg, #offline_msg{us = US, timestamp = TS, _ = '_'}) of
+ [Msg|_] ->
+ {ok, Msg};
+ _ ->
+ error
+ end.
+
+remove_message(LUser, LServer, I) ->
+ US = {LUser, LServer},
+ TS = integer_to_now(I),
+ Msgs = mnesia:dirty_match_object(
+ offline_msg, #offline_msg{us = US, timestamp = TS, _ = '_'}),
+ lists:foreach(
+ fun(Msg) ->
+ mnesia:dirty_delete_object(Msg)
+ end, Msgs).
+
+read_all_messages(LUser, LServer) ->
+ US = {LUser, LServer},
+ lists:keysort(#offline_msg.timestamp,
+ mnesia:dirty_read({offline_msg, US})).
+
+remove_all_messages(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ mnesia:write_lock_table(offline_msg),
+ lists:foreach(fun (Msg) -> mnesia:delete_object(Msg) end,
+ mnesia:dirty_read({offline_msg, US}))
+ end,
+ mnesia:transaction(F).
+
+count_messages(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ count_mnesia_records(US)
+ end,
+ case catch mnesia:async_dirty(F) of
+ I when is_integer(I) -> I;
+ _ -> 0
+ end.
+
+import(_LServer, #offline_msg{} = Msg) ->
+ mnesia:dirty_write(Msg).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+%% Return the number of records matching a given match expression.
+%% This function is intended to be used inside a Mnesia transaction.
+%% The count has been written to use the fewest possible memory by
+%% getting the record by small increment and by using continuation.
+-define(BATCHSIZE, 100).
+
+count_mnesia_records(US) ->
+ MatchExpression = #offline_msg{us = US, _ = '_'},
+ case mnesia:select(offline_msg, [{MatchExpression, [], [[]]}],
+ ?BATCHSIZE, read) of
+ {Result, Cont} ->
+ Count = length(Result),
+ count_records_cont(Cont, Count);
+ '$end_of_table' ->
+ 0
+ end.
+
+count_records_cont(Cont, Count) ->
+ case mnesia:select(Cont) of
+ {Result, Cont} ->
+ NewCount = Count + length(Result),
+ count_records_cont(Cont, NewCount);
+ '$end_of_table' ->
+ Count
+ end.
+
+jid_to_binary(#jid{user = U, server = S, resource = R,
+ luser = LU, lserver = LS, lresource = LR}) ->
+ #jid{user = iolist_to_binary(U),
+ server = iolist_to_binary(S),
+ resource = iolist_to_binary(R),
+ luser = iolist_to_binary(LU),
+ lserver = iolist_to_binary(LS),
+ lresource = iolist_to_binary(LR)}.
+
+now_to_integer({MS, S, US}) ->
+ (MS * 1000000 + S) * 1000000 + US.
+
+integer_to_now(Int) ->
+ Secs = Int div 1000000,
+ USec = Int rem 1000000,
+ MSec = Secs div 1000000,
+ Sec = Secs rem 1000000,
+ {MSec, Sec, USec}.
+
+update_table() ->
+ Fields = record_info(fields, offline_msg),
+ case mnesia:table_info(offline_msg, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ offline_msg, Fields, bag,
+ fun(#offline_msg{us = {U, _}}) -> U end,
+ fun(#offline_msg{us = {U, S},
+ from = From,
+ to = To,
+ packet = El} = R) ->
+ R#offline_msg{us = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ from = jid_to_binary(From),
+ to = jid_to_binary(To),
+ packet = fxml:to_xmlel(El)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating offline_msg table", []),
+ mnesia:transform_table(offline_msg, ignore, Fields)
+ end.
diff --git a/src/mod_offline_riak.erl b/src/mod_offline_riak.erl
new file mode 100644
index 00000000..217e8f82
--- /dev/null
+++ b/src/mod_offline_riak.erl
@@ -0,0 +1,153 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_offline_riak).
+
+-behaviour(mod_offline).
+
+-export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1,
+ remove_old_messages/2, remove_user/2, read_message_headers/2,
+ read_message/3, remove_message/3, read_all_messages/2,
+ remove_all_messages/2, count_messages/2, import/2]).
+
+-include("jlib.hrl").
+-include("mod_offline.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+store_messages(Host, {User, _}, Msgs, Len, MaxOfflineMsgs) ->
+ Count = if MaxOfflineMsgs =/= infinity ->
+ Len + count_messages(User, Host);
+ true -> 0
+ end,
+ if
+ Count > MaxOfflineMsgs ->
+ {atomic, discard};
+ true ->
+ try
+ lists:foreach(
+ fun(#offline_msg{us = US,
+ timestamp = TS} = M) ->
+ ok = ejabberd_riak:put(
+ M, offline_msg_schema(),
+ [{i, TS}, {'2i', [{<<"us">>, US}]}])
+ end, Msgs),
+ {atomic, ok}
+ catch _:{badmatch, Err} ->
+ {atomic, Err}
+ end
+ end.
+
+pop_messages(LUser, LServer) ->
+ case ejabberd_riak:get_by_index(offline_msg, offline_msg_schema(),
+ <<"us">>, {LUser, LServer}) of
+ {ok, Rs} ->
+ try
+ lists:foreach(
+ fun(#offline_msg{timestamp = T}) ->
+ ok = ejabberd_riak:delete(offline_msg, T)
+ end, Rs),
+ {ok, lists:keysort(#offline_msg.timestamp, Rs)}
+ catch _:{badmatch, Err} ->
+ Err
+ end;
+ Err ->
+ Err
+ end.
+
+remove_expired_messages(_LServer) ->
+ %% TODO
+ {atomic, ok}.
+
+remove_old_messages(_Days, _LServer) ->
+ %% TODO
+ {atomic, ok}.
+
+remove_user(LUser, LServer) ->
+ {atomic, ejabberd_riak:delete_by_index(offline_msg,
+ <<"us">>, {LUser, LServer})}.
+
+read_message_headers(LUser, LServer) ->
+ case ejabberd_riak:get_by_index(
+ offline_msg, offline_msg_schema(),
+ <<"us">>, {LUser, LServer}) of
+ {ok, Rs} ->
+ Hdrs = lists:map(
+ fun(#offline_msg{from = From, to = To, packet = Pkt,
+ timestamp = TS}) ->
+ Seq = now_to_integer(TS),
+ NewPkt = jlib:add_delay_info(
+ Pkt, LServer, TS, <<"Offline Storage">>),
+ {Seq, From, To, NewPkt}
+ end, Rs),
+ lists:keysort(1, Hdrs);
+ _Err ->
+ []
+ end.
+
+read_message(_LUser, _LServer, I) ->
+ TS = integer_to_now(I),
+ case ejabberd_riak:get(offline_msg, offline_msg_schema(), TS) of
+ {ok, Msg} ->
+ {ok, Msg};
+ _ ->
+ error
+ end.
+
+remove_message(_LUser, _LServer, I) ->
+ TS = integer_to_now(I),
+ ejabberd_riak:delete(offline_msg, TS),
+ ok.
+
+read_all_messages(LUser, LServer) ->
+ case ejabberd_riak:get_by_index(
+ offline_msg, offline_msg_schema(),
+ <<"us">>, {LUser, LServer}) of
+ {ok, Rs} ->
+ lists:keysort(#offline_msg.timestamp, Rs);
+ _Err ->
+ []
+ end.
+
+remove_all_messages(LUser, LServer) ->
+ Res = ejabberd_riak:delete_by_index(offline_msg,
+ <<"us">>, {LUser, LServer}),
+ {atomic, Res}.
+
+count_messages(LUser, LServer) ->
+ case ejabberd_riak:count_by_index(
+ offline_msg, <<"us">>, {LUser, LServer}) of
+ {ok, Res} ->
+ Res;
+ _ ->
+ 0
+ end.
+
+import(_LServer, #offline_msg{us = US, timestamp = TS} = M) ->
+ ejabberd_riak:put(M, offline_msg_schema(),
+ [{i, TS}, {'2i', [{<<"us">>, US}]}]).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+offline_msg_schema() ->
+ {record_info(fields, offline_msg), #offline_msg{}}.
+
+now_to_integer({MS, S, US}) ->
+ (MS * 1000000 + S) * 1000000 + US.
+
+integer_to_now(Int) ->
+ Secs = Int div 1000000,
+ USec = Int rem 1000000,
+ MSec = Secs div 1000000,
+ Sec = Secs rem 1000000,
+ {MSec, Sec, USec}.
diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl
new file mode 100644
index 00000000..4d945557
--- /dev/null
+++ b/src/mod_offline_sql.erl
@@ -0,0 +1,252 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_offline_sql).
+
+-compile([{parse_transform, ejabberd_sql_pt}]).
+
+-behaviour(mod_offline).
+
+-export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1,
+ remove_old_messages/2, remove_user/2, read_message_headers/2,
+ read_message/3, remove_message/3, read_all_messages/2,
+ remove_all_messages/2, count_messages/2, import/1, import/2,
+ export/1]).
+
+-include("jlib.hrl").
+-include("mod_offline.hrl").
+-include("logger.hrl").
+-include("ejabberd_sql_pt.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) ->
+ Count = if MaxOfflineMsgs =/= infinity ->
+ Len + count_messages(User, Host);
+ true -> 0
+ end,
+ if Count > MaxOfflineMsgs -> {atomic, discard};
+ true ->
+ Query = lists:map(
+ fun(M) ->
+ Username =
+ ejabberd_sql:escape((M#offline_msg.to)#jid.luser),
+ From = M#offline_msg.from,
+ To = M#offline_msg.to,
+ Packet =
+ jlib:replace_from_to(From, To,
+ M#offline_msg.packet),
+ NewPacket =
+ jlib:add_delay_info(Packet, Host,
+ M#offline_msg.timestamp,
+ <<"Offline Storage">>),
+ XML =
+ ejabberd_sql:escape(fxml:element_to_binary(NewPacket)),
+ sql_queries:add_spool_sql(Username, XML)
+ end,
+ Msgs),
+ sql_queries:add_spool(Host, Query)
+ end.
+
+pop_messages(LUser, LServer) ->
+ case sql_queries:get_and_del_spool_msg_t(LServer, LUser) of
+ {atomic, {selected, Rs}} ->
+ {ok, lists:flatmap(
+ fun({_, XML}) ->
+ case xml_to_offline_msg(XML) of
+ {ok, Msg} ->
+ [Msg];
+ _Err ->
+ []
+ end
+ end, Rs)};
+ Err ->
+ {error, Err}
+ end.
+
+remove_expired_messages(_LServer) ->
+ %% TODO
+ {atomic, ok}.
+
+remove_old_messages(Days, LServer) ->
+ case catch ejabberd_sql:sql_query(
+ LServer,
+ [<<"DELETE FROM spool"
+ " WHERE created_at < "
+ "DATE_SUB(CURDATE(), INTERVAL ">>,
+ integer_to_list(Days), <<" DAY);">>]) of
+ {updated, N} ->
+ ?INFO_MSG("~p message(s) deleted from offline spool", [N]);
+ _Error ->
+ ?ERROR_MSG("Cannot delete message in offline spool: ~p", [_Error])
+ end,
+ {atomic, ok}.
+
+remove_user(LUser, LServer) ->
+ sql_queries:del_spool_msg(LServer, LUser).
+
+read_message_headers(LUser, LServer) ->
+ Username = ejabberd_sql:escape(LUser),
+ case catch ejabberd_sql:sql_query(
+ LServer, [<<"select xml, seq from spool where username ='">>,
+ Username, <<"' order by seq;">>]) of
+ {selected, [<<"xml">>, <<"seq">>], Rows} ->
+ lists:flatmap(
+ fun([XML, Seq]) ->
+ case xml_to_offline_msg(XML) of
+ {ok, #offline_msg{from = From,
+ to = To,
+ packet = El}} ->
+ Seq0 = binary_to_integer(Seq),
+ [{Seq0, From, To, El}];
+ _ ->
+ []
+ end
+ end, Rows);
+ _Err ->
+ []
+ end.
+
+read_message(LUser, LServer, Seq) ->
+ Username = ejabberd_sql:escape(LUser),
+ SSeq = ejabberd_sql:escape(integer_to_binary(Seq)),
+ case ejabberd_sql:sql_query(
+ LServer,
+ [<<"select xml from spool where username='">>, Username,
+ <<"' and seq='">>, SSeq, <<"';">>]) of
+ {selected, [<<"xml">>], [[RawXML]|_]} ->
+ case xml_to_offline_msg(RawXML) of
+ {ok, Msg} ->
+ {ok, Msg};
+ _ ->
+ error
+ end;
+ _ ->
+ error
+ end.
+
+remove_message(LUser, LServer, Seq) ->
+ Username = ejabberd_sql:escape(LUser),
+ SSeq = ejabberd_sql:escape(integer_to_binary(Seq)),
+ ejabberd_sql:sql_query(
+ LServer,
+ [<<"delete from spool where username='">>, Username,
+ <<"' and seq='">>, SSeq, <<"';">>]),
+ ok.
+
+read_all_messages(LUser, LServer) ->
+ case catch ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(xml)s from spool where "
+ "username=%(LUser)s order by seq")) of
+ {selected, Rs} ->
+ lists:flatmap(
+ fun({XML}) ->
+ case xml_to_offline_msg(XML) of
+ {ok, Msg} -> [Msg];
+ _ -> []
+ end
+ end, Rs);
+ _ ->
+ []
+ end.
+
+remove_all_messages(LUser, LServer) ->
+ sql_queries:del_spool_msg(LServer, LUser),
+ {atomic, ok}.
+
+count_messages(LUser, LServer) ->
+ case catch ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(count(*))d from spool "
+ "where username=%(LUser)s")) of
+ {selected, [{Res}]} ->
+ Res;
+ _ -> 0
+ end.
+
+export(_Server) ->
+ [{offline_msg,
+ fun(Host, #offline_msg{us = {LUser, LServer},
+ timestamp = TimeStamp, from = From, to = To,
+ packet = Packet})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ Packet1 = jlib:replace_from_to(From, To, Packet),
+ Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp,
+ <<"Offline Storage">>),
+ XML = ejabberd_sql:escape(fxml:element_to_binary(Packet2)),
+ [[<<"delete from spool where username='">>, Username, <<"';">>],
+ [<<"insert into spool(username, xml) values ('">>,
+ Username, <<"', '">>, XML, <<"');">>]];
+ (_Host, _R) ->
+ []
+ end}].
+
+import(LServer) ->
+ [{<<"select username, xml from spool;">>,
+ fun([LUser, XML]) ->
+ El = #xmlel{} = fxml_stream:parse_element(XML),
+ From = #jid{} = jid:from_string(
+ fxml:get_attr_s(<<"from">>, El#xmlel.attrs)),
+ To = #jid{} = jid:from_string(
+ fxml:get_attr_s(<<"to">>, El#xmlel.attrs)),
+ Stamp = fxml:get_path_s(El, [{elem, <<"delay">>},
+ {attr, <<"stamp">>}]),
+ TS = case jlib:datetime_string_to_timestamp(Stamp) of
+ {_, _, _} = Now ->
+ Now;
+ undefined ->
+ p1_time_compat:timestamp()
+ end,
+ Expire = mod_offline:find_x_expire(TS, El#xmlel.children),
+ #offline_msg{us = {LUser, LServer},
+ from = From, to = To,
+ packet = El,
+ timestamp = TS, expire = Expire}
+ end}].
+
+import(_, _) ->
+ pass.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+xml_to_offline_msg(XML) ->
+ case fxml_stream:parse_element(XML) of
+ #xmlel{} = El ->
+ el_to_offline_msg(El);
+ Err ->
+ ?ERROR_MSG("got ~p when parsing XML packet ~s",
+ [Err, XML]),
+ Err
+ end.
+
+el_to_offline_msg(El) ->
+ To_s = fxml:get_tag_attr_s(<<"to">>, El),
+ From_s = fxml:get_tag_attr_s(<<"from">>, El),
+ To = jid:from_string(To_s),
+ From = jid:from_string(From_s),
+ if To == error ->
+ ?ERROR_MSG("failed to get 'to' JID from offline XML ~p", [El]),
+ {error, bad_jid_to};
+ From == error ->
+ ?ERROR_MSG("failed to get 'from' JID from offline XML ~p", [El]),
+ {error, bad_jid_from};
+ true ->
+ {ok, #offline_msg{us = {To#jid.luser, To#jid.lserver},
+ from = From,
+ to = To,
+ timestamp = undefined,
+ expire = undefined,
+ packet = El}}
+ end.
diff --git a/src/mod_ping.erl b/src/mod_ping.erl
index 85ff770e..f1c175a9 100644
--- a/src/mod_ping.erl
+++ b/src/mod_ping.erl
@@ -202,13 +202,14 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%% Hook callbacks
%%====================================================================
iq_ping(_From, _To,
- #iq{type = Type, sub_el = SubEl} = IQ) ->
+ #iq{type = Type, sub_el = SubEl, lang = Lang} = IQ) ->
case {Type, SubEl} of
{get, #xmlel{name = <<"ping">>}} ->
IQ#iq{type = result, sub_el = []};
_ ->
+ Txt = <<"Ping query is incorrect">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]}
+ sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
end.
user_online(_SID, JID, _Info) ->
diff --git a/src/mod_pres_counter.erl b/src/mod_pres_counter.erl
index 34fdcdb7..1118b7bb 100644
--- a/src/mod_pres_counter.erl
+++ b/src/mod_pres_counter.erl
@@ -52,7 +52,7 @@ check_packet(_, _User, Server, _PrivacyList,
{From, To, #xmlel{name = Name, attrs = Attrs}}, Dir) ->
case Name of
<<"presence">> ->
- IsSubscription = case xml:get_attr_s(<<"type">>, Attrs)
+ IsSubscription = case fxml:get_attr_s(<<"type">>, Attrs)
of
<<"subscribe">> -> true;
<<"subscribed">> -> true;
diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl
index 9c2af037..413dcb52 100644
--- a/src/mod_privacy.erl
+++ b/src/mod_privacy.erl
@@ -33,18 +33,10 @@
-export([start/2, stop/1, process_iq/3, export/1, import/1,
process_iq_set/4, process_iq_get/5, get_user_list/3,
- check_packet/6, remove_user/2, item_to_raw/1,
- raw_to_item/1, is_list_needdb/1, updated_list/3,
- item_to_xml/1, get_user_lists/2, import/3]).
-
--export([sql_add_privacy_list/2,
- sql_get_default_privacy_list/2,
- sql_get_default_privacy_list_t/1,
- sql_get_privacy_list_data/3,
- sql_get_privacy_list_data_by_id_t/1,
- sql_get_privacy_list_id_t/2,
- sql_set_default_privacy_list/2, sql_set_privacy_list/2,
- privacy_schema/0, mod_opt_type/1]).
+ check_packet/6, remove_user/2,
+ is_list_needdb/1, updated_list/3,
+ item_to_xml/1, get_user_lists/2, import/3,
+ set_privacy_list/1, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -53,20 +45,24 @@
-include("mod_privacy.hrl").
-privacy_schema() ->
- {record_info(fields, privacy), #privacy{}}.
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #privacy{}) -> ok | pass.
+-callback process_lists_get(binary(), binary()) -> {none | binary(), [xmlel()]} | error.
+-callback process_list_get(binary(), binary(), binary()) -> [listitem()] | error | not_found.
+-callback process_default_set(binary(), binary(), {value, binary()} | false) -> {atomic, any()}.
+-callback process_active_set(binary(), binary(), binary()) -> [listitem()] | error.
+-callback remove_privacy_list(binary(), binary(), binary()) -> {atomic, any()}.
+-callback set_privacy_list(#privacy{}) -> any().
+-callback set_privacy_list(binary(), binary(), binary(), [listitem()]) -> {atomic, any()}.
+-callback get_user_list(binary(), binary()) -> {none | binary(), [listitem()]}.
+-callback get_user_lists(binary(), binary()) -> {ok, #privacy{}} | error.
+-callback remove_user(binary(), binary()) -> {atomic, any()}.
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(privacy,
- [{disc_copies, [node()]},
- {attributes, record_info(fields, privacy)}]),
- update_table();
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
mod_disco:register_feature(Host, ?NS_PRIVACY),
ejabberd_hooks:add(privacy_iq_get, Host, ?MODULE,
process_iq_get, 50),
@@ -104,27 +100,28 @@ process_iq(_From, _To, IQ) ->
SubEl = IQ#iq.sub_el,
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
-process_iq_get(_, From, _To, #iq{sub_el = SubEl},
+process_iq_get(_, From, _To, #iq{lang = Lang, sub_el = SubEl},
#userlist{name = Active}) ->
#jid{luser = LUser, lserver = LServer} = From,
#xmlel{children = Els} = SubEl,
- case xml:remove_cdata(Els) of
- [] -> process_lists_get(LUser, LServer, Active);
+ case fxml:remove_cdata(Els) of
+ [] -> process_lists_get(LUser, LServer, Active, Lang);
[#xmlel{name = Name, attrs = Attrs}] ->
case Name of
<<"list">> ->
- ListName = xml:get_attr(<<"name">>, Attrs),
- process_list_get(LUser, LServer, ListName);
- _ -> {error, ?ERR_BAD_REQUEST}
+ ListName = fxml:get_attr(<<"name">>, Attrs),
+ process_list_get(LUser, LServer, ListName, Lang);
+ _ ->
+ Txt = <<"Unsupported tag name">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end;
- _ -> {error, ?ERR_BAD_REQUEST}
+ _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Too many elements">>)}
end.
-process_lists_get(LUser, LServer, Active) ->
- case process_lists_get(LUser, LServer, Active,
- gen_mod:db_type(LServer, ?MODULE))
- of
- error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+process_lists_get(LUser, LServer, Active, Lang) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:process_lists_get(LUser, LServer) of
+ error -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
{_Default, []} ->
{result,
[#xmlel{name = <<"query">>,
@@ -150,60 +147,10 @@ process_lists_get(LUser, LServer, Active) ->
children = ADItems}]}
end.
-process_lists_get(LUser, LServer, _Active, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer})
- of
- {'EXIT', _Reason} -> error;
- [] -> {none, []};
- [#privacy{default = Default, lists = Lists}] ->
- LItems = lists:map(fun ({N, _}) ->
- #xmlel{name = <<"list">>,
- attrs = [{<<"name">>, N}],
- children = []}
- end,
- Lists),
- {Default, LItems}
- end;
-process_lists_get(LUser, LServer, _Active, riak) ->
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, #privacy{default = Default, lists = Lists}} ->
- LItems = lists:map(fun ({N, _}) ->
- #xmlel{name = <<"list">>,
- attrs = [{<<"name">>, N}],
- children = []}
- end,
- Lists),
- {Default, LItems};
- {error, notfound} ->
- {none, []};
- {error, _} ->
- error
- end;
-process_lists_get(LUser, LServer, _Active, odbc) ->
- Default = case catch sql_get_default_privacy_list(LUser,
- LServer)
- of
- {selected, [<<"name">>], []} -> none;
- {selected, [<<"name">>], [[DefName]]} -> DefName;
- _ -> none
- end,
- case catch sql_get_privacy_list_names(LUser, LServer) of
- {selected, [<<"name">>], Names} ->
- LItems = lists:map(fun ([N]) ->
- #xmlel{name = <<"list">>,
- attrs = [{<<"name">>, N}],
- children = []}
- end,
- Names),
- {Default, LItems};
- _ -> error
- end.
-
-process_list_get(LUser, LServer, {value, Name}) ->
- case process_list_get(LUser, LServer, Name,
- gen_mod:db_type(LServer, ?MODULE))
- of
- error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+process_list_get(LUser, LServer, {value, Name}, Lang) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:process_list_get(LUser, LServer, Name) of
+ error -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
not_found -> {error, ?ERR_ITEM_NOT_FOUND};
Items ->
LItems = lists:map(fun item_to_xml/1, Items),
@@ -214,50 +161,9 @@ process_list_get(LUser, LServer, {value, Name}) ->
[#xmlel{name = <<"list">>, attrs = [{<<"name">>, Name}],
children = LItems}]}]}
end;
-process_list_get(_LUser, _LServer, false) ->
+process_list_get(_LUser, _LServer, false, _Lang) ->
{error, ?ERR_BAD_REQUEST}.
-process_list_get(LUser, LServer, Name, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer})
- of
- {'EXIT', _Reason} -> error;
- [] -> not_found;
- [#privacy{lists = Lists}] ->
- case lists:keysearch(Name, 1, Lists) of
- {value, {_, List}} -> List;
- _ -> not_found
- end
- end;
-process_list_get(LUser, LServer, Name, riak) ->
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, #privacy{lists = Lists}} ->
- case lists:keysearch(Name, 1, Lists) of
- {value, {_, List}} -> List;
- _ -> not_found
- end;
- {error, notfound} ->
- not_found;
- {error, _} ->
- error
- end;
-process_list_get(LUser, LServer, Name, odbc) ->
- case catch sql_get_privacy_list_id(LUser, LServer, Name)
- of
- {selected, [<<"id">>], []} -> not_found;
- {selected, [<<"id">>], [[ID]]} ->
- case catch sql_get_privacy_list_data_by_id(ID, LServer)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems} ->
- lists:flatmap(fun raw_to_item/1, RItems);
- _ -> error
- end;
- _ -> error
- end.
-
item_to_xml(Item) ->
Attrs1 = [{<<"action">>,
action_to_list(Item#listitem.action)},
@@ -339,109 +245,41 @@ list_to_action(S) ->
<<"deny">> -> deny
end.
-process_iq_set(_, From, _To, #iq{sub_el = SubEl}) ->
+process_iq_set(_, From, _To, #iq{lang = Lang, sub_el = SubEl}) ->
#jid{luser = LUser, lserver = LServer} = From,
#xmlel{children = Els} = SubEl,
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = Name, attrs = Attrs,
children = SubEls}] ->
- ListName = xml:get_attr(<<"name">>, Attrs),
+ ListName = fxml:get_attr(<<"name">>, Attrs),
case Name of
<<"list">> ->
process_list_set(LUser, LServer, ListName,
- xml:remove_cdata(SubEls));
+ fxml:remove_cdata(SubEls), Lang);
<<"active">> ->
process_active_set(LUser, LServer, ListName);
<<"default">> ->
- process_default_set(LUser, LServer, ListName);
- _ -> {error, ?ERR_BAD_REQUEST}
+ process_default_set(LUser, LServer, ListName, Lang);
+ _ ->
+ Txt = <<"Unsupported tag name">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end;
_ -> {error, ?ERR_BAD_REQUEST}
end.
-process_default_set(LUser, LServer, Value) ->
- case process_default_set(LUser, LServer, Value,
- gen_mod:db_type(LServer, ?MODULE))
- of
- {atomic, error} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
+process_default_set(LUser, LServer, Value, Lang) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:process_default_set(LUser, LServer, Value) of
+ {atomic, error} ->
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
{atomic, not_found} -> {error, ?ERR_ITEM_NOT_FOUND};
{atomic, ok} -> {result, []};
_ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
end.
-process_default_set(LUser, LServer, {value, Name},
- mnesia) ->
- F = fun () ->
- case mnesia:read({privacy, {LUser, LServer}}) of
- [] -> not_found;
- [#privacy{lists = Lists} = P] ->
- case lists:keymember(Name, 1, Lists) of
- true ->
- mnesia:write(P#privacy{default = Name,
- lists = Lists}),
- ok;
- false -> not_found
- end
- end
- end,
- mnesia:transaction(F);
-process_default_set(LUser, LServer, {value, Name}, riak) ->
- {atomic,
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, #privacy{lists = Lists} = P} ->
- case lists:keymember(Name, 1, Lists) of
- true ->
- ejabberd_riak:put(P#privacy{default = Name,
- lists = Lists},
- privacy_schema());
- false ->
- not_found
- end;
- {error, _} ->
- not_found
- end};
-process_default_set(LUser, LServer, {value, Name},
- odbc) ->
- F = fun () ->
- case sql_get_privacy_list_names_t(LUser) of
- {selected, [<<"name">>], []} -> not_found;
- {selected, [<<"name">>], Names} ->
- case lists:member([Name], Names) of
- true -> sql_set_default_privacy_list(LUser, Name), ok;
- false -> not_found
- end
- end
- end,
- odbc_queries:sql_transaction(LServer, F);
-process_default_set(LUser, LServer, false, mnesia) ->
- F = fun () ->
- case mnesia:read({privacy, {LUser, LServer}}) of
- [] -> ok;
- [R] -> mnesia:write(R#privacy{default = none})
- end
- end,
- mnesia:transaction(F);
-process_default_set(LUser, LServer, false, riak) ->
- {atomic,
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, R} ->
- ejabberd_riak:put(R#privacy{default = none}, privacy_schema());
- {error, _} ->
- ok
- end};
-process_default_set(LUser, LServer, false, odbc) ->
- case catch sql_unset_default_privacy_list(LUser,
- LServer)
- of
- {'EXIT', _Reason} -> {atomic, error};
- {error, _Reason} -> {atomic, error};
- _ -> {atomic, ok}
- end.
-
process_active_set(LUser, LServer, {value, Name}) ->
- case process_active_set(LUser, LServer, Name,
- gen_mod:db_type(LServer, ?MODULE))
- of
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:process_active_set(LUser, LServer, Name) of
error -> {error, ?ERR_ITEM_NOT_FOUND};
Items ->
NeedDb = is_list_needdb(Items),
@@ -451,135 +289,19 @@ process_active_set(LUser, LServer, {value, Name}) ->
process_active_set(_LUser, _LServer, false) ->
{result, [], #userlist{}}.
-process_active_set(LUser, LServer, Name, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer})
- of
- [] -> error;
- [#privacy{lists = Lists}] ->
- case lists:keysearch(Name, 1, Lists) of
- {value, {_, List}} -> List;
- false -> error
- end
- end;
-process_active_set(LUser, LServer, Name, riak) ->
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, #privacy{lists = Lists}} ->
- case lists:keysearch(Name, 1, Lists) of
- {value, {_, List}} -> List;
- false -> error
- end;
- {error, _} ->
- error
- end;
-process_active_set(LUser, LServer, Name, odbc) ->
- case catch sql_get_privacy_list_id(LUser, LServer, Name)
- of
- {selected, [<<"id">>], []} -> error;
- {selected, [<<"id">>], [[ID]]} ->
- case catch sql_get_privacy_list_data_by_id(ID, LServer)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems} ->
- lists:flatmap(fun raw_to_item/1, RItems);
- _ -> error
- end;
- _ -> error
- end.
+set_privacy_list(#privacy{us = {_, LServer}} = Privacy) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:set_privacy_list(Privacy).
-remove_privacy_list(LUser, LServer, Name, mnesia) ->
- F = fun () ->
- case mnesia:read({privacy, {LUser, LServer}}) of
- [] -> ok;
- [#privacy{default = Default, lists = Lists} = P] ->
- if Name == Default -> conflict;
- true ->
- NewLists = lists:keydelete(Name, 1, Lists),
- mnesia:write(P#privacy{lists = NewLists})
- end
- end
- end,
- mnesia:transaction(F);
-remove_privacy_list(LUser, LServer, Name, riak) ->
- {atomic,
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, #privacy{default = Default, lists = Lists} = P} ->
- if Name == Default ->
- conflict;
- true ->
- NewLists = lists:keydelete(Name, 1, Lists),
- ejabberd_riak:put(P#privacy{lists = NewLists},
- privacy_schema())
- end;
- {error, _} ->
- ok
- end};
-remove_privacy_list(LUser, LServer, Name, odbc) ->
- F = fun () ->
- case sql_get_default_privacy_list_t(LUser) of
- {selected, [<<"name">>], []} ->
- sql_remove_privacy_list(LUser, Name), ok;
- {selected, [<<"name">>], [[Default]]} ->
- if Name == Default -> conflict;
- true -> sql_remove_privacy_list(LUser, Name), ok
- end
- end
- end,
- odbc_queries:sql_transaction(LServer, F).
-
-set_privacy_list(LUser, LServer, Name, List, mnesia) ->
- F = fun () ->
- case mnesia:wread({privacy, {LUser, LServer}}) of
- [] ->
- NewLists = [{Name, List}],
- mnesia:write(#privacy{us = {LUser, LServer},
- lists = NewLists});
- [#privacy{lists = Lists} = P] ->
- NewLists1 = lists:keydelete(Name, 1, Lists),
- NewLists = [{Name, List} | NewLists1],
- mnesia:write(P#privacy{lists = NewLists})
- end
- end,
- mnesia:transaction(F);
-set_privacy_list(LUser, LServer, Name, List, riak) ->
- {atomic,
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, #privacy{lists = Lists} = P} ->
- NewLists1 = lists:keydelete(Name, 1, Lists),
- NewLists = [{Name, List} | NewLists1],
- ejabberd_riak:put(P#privacy{lists = NewLists}, privacy_schema());
- {error, _} ->
- NewLists = [{Name, List}],
- ejabberd_riak:put(#privacy{us = {LUser, LServer},
- lists = NewLists},
- privacy_schema())
- end};
-set_privacy_list(LUser, LServer, Name, List, odbc) ->
- RItems = lists:map(fun item_to_raw/1, List),
- F = fun () ->
- ID = case sql_get_privacy_list_id_t(LUser, Name) of
- {selected, [<<"id">>], []} ->
- sql_add_privacy_list(LUser, Name),
- {selected, [<<"id">>], [[I]]} =
- sql_get_privacy_list_id_t(LUser, Name),
- I;
- {selected, [<<"id">>], [[I]]} -> I
- end,
- sql_set_privacy_list(ID, RItems),
- ok
- end,
- odbc_queries:sql_transaction(LServer, F).
-
-process_list_set(LUser, LServer, {value, Name}, Els) ->
+process_list_set(LUser, LServer, {value, Name}, Els, Lang) ->
case parse_items(Els) of
false -> {error, ?ERR_BAD_REQUEST};
remove ->
- case remove_privacy_list(LUser, LServer, Name,
- gen_mod:db_type(LServer, ?MODULE))
- of
- {atomic, conflict} -> {error, ?ERR_CONFLICT};
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:remove_privacy_list(LUser, LServer, Name) of
+ {atomic, conflict} ->
+ Txt = <<"Cannot remove default list">>,
+ {error, ?ERRT_CONFLICT(Lang, Txt)};
{atomic, ok} ->
ejabberd_sm:route(jid:make(LUser, LServer,
<<"">>),
@@ -589,12 +311,11 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
list = []},
Name}}),
{result, []};
- _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
+ _ -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
end;
List ->
- case set_privacy_list(LUser, LServer, Name, List,
- gen_mod:db_type(LServer, ?MODULE))
- of
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:set_privacy_list(LUser, LServer, Name, List) of
{atomic, ok} ->
NeedDb = is_list_needdb(List),
ejabberd_sm:route(jid:make(LUser, LServer,
@@ -606,10 +327,10 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
needdb = NeedDb},
Name}}),
{result, []};
- _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
+ _ -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
end
end;
-process_list_set(_LUser, _LServer, false, _Els) ->
+process_list_set(_LUser, _LServer, false, _Els, _Lang) ->
{error, ?ERR_BAD_REQUEST}.
parse_items([]) -> remove;
@@ -621,10 +342,10 @@ parse_items([#xmlel{name = <<"item">>, attrs = Attrs,
children = SubEls}
| Els],
Res) ->
- Type = xml:get_attr(<<"type">>, Attrs),
- Value = xml:get_attr(<<"value">>, Attrs),
- SAction = xml:get_attr(<<"action">>, Attrs),
- SOrder = xml:get_attr(<<"order">>, Attrs),
+ Type = fxml:get_attr(<<"type">>, Attrs),
+ Value = fxml:get_attr(<<"value">>, Attrs),
+ SAction = fxml:get_attr(<<"action">>, Attrs),
+ SOrder = fxml:get_attr(<<"order">>, Attrs),
Action = case catch list_to_action(element(2, SAction))
of
{'EXIT', _} -> false;
@@ -674,7 +395,7 @@ parse_items([#xmlel{name = <<"item">>, attrs = Attrs,
case I2 of
false -> false;
_ ->
- case parse_matches(I2, xml:remove_cdata(SubEls)) of
+ case parse_matches(I2, fxml:remove_cdata(SubEls)) of
false -> false;
I3 -> parse_items(Els, [I3 | Res])
end
@@ -714,115 +435,20 @@ is_list_needdb(Items) ->
end,
Items).
-get_user_list(Acc, User, Server) ->
+get_user_list(_Acc, User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- {Default, Items} = get_user_list(Acc, LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ {Default, Items} = Mod:get_user_list(LUser, LServer),
NeedDb = is_list_needdb(Items),
#userlist{name = Default, list = Items,
needdb = NeedDb}.
-get_user_list(_, LUser, LServer, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer})
- of
- [] -> {none, []};
- [#privacy{default = Default, lists = Lists}] ->
- case Default of
- none -> {none, []};
- _ ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} -> {Default, List};
- _ -> {none, []}
- end
- end;
- _ -> {none, []}
- end;
-get_user_list(_, LUser, LServer, riak) ->
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, #privacy{default = Default, lists = Lists}} ->
- case Default of
- none -> {none, []};
- _ ->
- case lists:keysearch(Default, 1, Lists) of
- {value, {_, List}} -> {Default, List};
- _ -> {none, []}
- end
- end;
- {error, _} ->
- {none, []}
- end;
-get_user_list(_, LUser, LServer, odbc) ->
- case catch sql_get_default_privacy_list(LUser, LServer)
- of
- {selected, [<<"name">>], []} -> {none, []};
- {selected, [<<"name">>], [[Default]]} ->
- case catch sql_get_privacy_list_data(LUser, LServer,
- Default)
- of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>, <<"ord">>,
- <<"match_all">>, <<"match_iq">>, <<"match_message">>,
- <<"match_presence_in">>, <<"match_presence_out">>],
- RItems} ->
- {Default, lists:flatmap(fun raw_to_item/1, RItems)};
- _ -> {none, []}
- end;
- _ -> {none, []}
- end.
-
get_user_lists(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- get_user_lists(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
-
-get_user_lists(LUser, LServer, mnesia) ->
- case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
- [#privacy{} = P] ->
- {ok, P};
- _ ->
- error
- end;
-get_user_lists(LUser, LServer, riak) ->
- case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
- {ok, #privacy{} = P} ->
- {ok, P};
- {error, _} ->
- error
- end;
-get_user_lists(LUser, LServer, odbc) ->
- Default = case catch sql_get_default_privacy_list(LUser, LServer) of
- {selected, [<<"name">>], []} ->
- none;
- {selected, [<<"name">>], [[DefName]]} ->
- DefName;
- _ ->
- none
- end,
- case catch sql_get_privacy_list_names(LUser, LServer) of
- {selected, [<<"name">>], Names} ->
- Lists =
- lists:flatmap(
- fun([Name]) ->
- case catch sql_get_privacy_list_data(
- LUser, LServer, Name) of
- {selected,
- [<<"t">>, <<"value">>, <<"action">>,
- <<"ord">>, <<"match_all">>, <<"match_iq">>,
- <<"match_message">>, <<"match_presence_in">>,
- <<"match_presence_out">>],
- RItems} ->
- [{Name, lists:flatmap(fun raw_to_item/1, RItems)}];
- _ ->
- []
- end
- end, Names),
- {ok, #privacy{default = Default,
- us = {LUser, LServer},
- lists = Lists}};
- _ ->
- error
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_user_lists(LUser, LServer).
%% From is the sender, To is the destination.
%% If Dir = out, User@Server is the sender account (From).
@@ -852,7 +478,7 @@ check_packet(_, User, Server,
<<"message">> -> message;
<<"iq">> -> iq;
<<"presence">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
%% notification
<<"">> -> presence;
<<"unavailable">> -> presence;
@@ -946,17 +572,8 @@ is_type_match(Type, Value, JID, Subscription, Groups) ->
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- remove_user(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-remove_user(LUser, LServer, mnesia) ->
- F = fun () -> mnesia:delete({privacy, {LUser, LServer}})
- end,
- mnesia:transaction(F);
-remove_user(LUser, LServer, riak) ->
- {atomic, ejabberd_riak:delete(privacy, {LUser, LServer})};
-remove_user(LUser, LServer, odbc) ->
- sql_del_privacy_lists(LUser, LServer).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_user(LUser, LServer).
updated_list(_, #userlist{name = OldName} = Old,
#userlist{name = NewName} = New) ->
@@ -964,296 +581,17 @@ updated_list(_, #userlist{name = OldName} = Old,
true -> Old
end.
-raw_to_item([SType, SValue, SAction, SOrder, SMatchAll,
- SMatchIQ, SMatchMessage, SMatchPresenceIn,
- SMatchPresenceOut] = Row) ->
- try
- {Type, Value} = case SType of
- <<"n">> -> {none, none};
- <<"j">> ->
- case jid:from_string(SValue) of
- #jid{} = JID ->
- {jid, jid:tolower(JID)}
- end;
- <<"g">> -> {group, SValue};
- <<"s">> ->
- case SValue of
- <<"none">> -> {subscription, none};
- <<"both">> -> {subscription, both};
- <<"from">> -> {subscription, from};
- <<"to">> -> {subscription, to}
- end
- end,
- Action = case SAction of
- <<"a">> -> allow;
- <<"d">> -> deny
- end,
- Order = jlib:binary_to_integer(SOrder),
- MatchAll = ejabberd_odbc:to_bool(SMatchAll),
- MatchIQ = ejabberd_odbc:to_bool(SMatchIQ),
- MatchMessage = ejabberd_odbc:to_bool(SMatchMessage),
- MatchPresenceIn = ejabberd_odbc:to_bool(SMatchPresenceIn),
- MatchPresenceOut = ejabberd_odbc:to_bool(SMatchPresenceOut),
- [#listitem{type = Type, value = Value, action = Action,
- order = Order, match_all = MatchAll, match_iq = MatchIQ,
- match_message = MatchMessage,
- match_presence_in = MatchPresenceIn,
- match_presence_out = MatchPresenceOut}]
- catch _:_ ->
- ?WARNING_MSG("failed to parse row: ~p", [Row]),
- []
- end.
-
-item_to_raw(#listitem{type = Type, value = Value,
- action = Action, order = Order, match_all = MatchAll,
- match_iq = MatchIQ, match_message = MatchMessage,
- match_presence_in = MatchPresenceIn,
- match_presence_out = MatchPresenceOut}) ->
- {SType, SValue} = case Type of
- none -> {<<"n">>, <<"">>};
- jid ->
- {<<"j">>,
- ejabberd_odbc:escape(jid:to_string(Value))};
- group -> {<<"g">>, ejabberd_odbc:escape(Value)};
- subscription ->
- case Value of
- none -> {<<"s">>, <<"none">>};
- both -> {<<"s">>, <<"both">>};
- from -> {<<"s">>, <<"from">>};
- to -> {<<"s">>, <<"to">>}
- end
- end,
- SAction = case Action of
- allow -> <<"a">>;
- deny -> <<"d">>
- end,
- SOrder = iolist_to_binary(integer_to_list(Order)),
- SMatchAll = if MatchAll -> <<"1">>;
- true -> <<"0">>
- end,
- SMatchIQ = if MatchIQ -> <<"1">>;
- true -> <<"0">>
- end,
- SMatchMessage = if MatchMessage -> <<"1">>;
- true -> <<"0">>
- end,
- SMatchPresenceIn = if MatchPresenceIn -> <<"1">>;
- true -> <<"0">>
- end,
- SMatchPresenceOut = if MatchPresenceOut -> <<"1">>;
- true -> <<"0">>
- end,
- [SType, SValue, SAction, SOrder, SMatchAll, SMatchIQ,
- SMatchMessage, SMatchPresenceIn, SMatchPresenceOut].
-
-sql_get_default_privacy_list(LUser, LServer) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:get_default_privacy_list(LServer,
- Username).
-
-sql_get_default_privacy_list_t(LUser) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:get_default_privacy_list_t(Username).
-
-sql_get_privacy_list_names(LUser, LServer) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:get_privacy_list_names(LServer, Username).
-
-sql_get_privacy_list_names_t(LUser) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:get_privacy_list_names_t(Username).
-
-sql_get_privacy_list_id(LUser, LServer, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:get_privacy_list_id(LServer, Username,
- SName).
-
-sql_get_privacy_list_id_t(LUser, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:get_privacy_list_id_t(Username, SName).
-
-sql_get_privacy_list_data(LUser, LServer, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:get_privacy_list_data(LServer, Username,
- SName).
-
-sql_get_privacy_list_data_t(LUser, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:get_privacy_list_data_t(Username, SName).
-
-sql_get_privacy_list_data_by_id(ID, LServer) ->
- odbc_queries:get_privacy_list_data_by_id(LServer, ID).
-
-sql_get_privacy_list_data_by_id_t(ID) ->
- odbc_queries:get_privacy_list_data_by_id_t(ID).
-
-sql_set_default_privacy_list(LUser, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:set_default_privacy_list(Username, SName).
-
-sql_unset_default_privacy_list(LUser, LServer) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:unset_default_privacy_list(LServer,
- Username).
-
-sql_remove_privacy_list(LUser, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:remove_privacy_list(Username, SName).
-
-sql_add_privacy_list(LUser, Name) ->
- Username = ejabberd_odbc:escape(LUser),
- SName = ejabberd_odbc:escape(Name),
- odbc_queries:add_privacy_list(Username, SName).
-
-sql_set_privacy_list(ID, RItems) ->
- odbc_queries:set_privacy_list(ID, RItems).
-
-sql_del_privacy_lists(LUser, LServer) ->
- Username = ejabberd_odbc:escape(LUser),
- Server = ejabberd_odbc:escape(LServer),
- odbc_queries:del_privacy_lists(LServer, Server,
- Username).
-
-update_table() ->
- Fields = record_info(fields, privacy),
- case mnesia:table_info(privacy, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- privacy, Fields, set,
- fun(#privacy{us = {U, _}}) -> U end,
- fun(#privacy{us = {U, S}, default = Def, lists = Lists} = R) ->
- NewLists =
- lists:map(
- fun({Name, Ls}) ->
- NewLs =
- lists:map(
- fun(#listitem{value = Val} = L) ->
- NewVal =
- case Val of
- {LU, LS, LR} ->
- {iolist_to_binary(LU),
- iolist_to_binary(LS),
- iolist_to_binary(LR)};
- none -> none;
- both -> both;
- from -> from;
- to -> to;
- _ -> iolist_to_binary(Val)
- end,
- L#listitem{value = NewVal}
- end, Ls),
- {iolist_to_binary(Name), NewLs}
- end, Lists),
- NewDef = case Def of
- none -> none;
- _ -> iolist_to_binary(Def)
- end,
- NewUS = {iolist_to_binary(U), iolist_to_binary(S)},
- R#privacy{us = NewUS, default = NewDef,
- lists = NewLists}
- end);
- _ ->
- ?INFO_MSG("Recreating privacy table", []),
- mnesia:transform_table(privacy, ignore, Fields)
- end.
-
-export(Server) ->
- case catch ejabberd_odbc:sql_query(jid:nameprep(Server),
- [<<"select id from privacy_list order by "
- "id desc limit 1;">>]) of
- {selected, [<<"id">>], [[I]]} ->
- put(id, jlib:binary_to_integer(I));
- _ ->
- put(id, 0)
- end,
- [{privacy,
- fun(Host, #privacy{us = {LUser, LServer}, lists = Lists,
- default = Default})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- if Default /= none ->
- SDefault = ejabberd_odbc:escape(Default),
- [[<<"delete from privacy_default_list where ">>,
- <<"username='">>, Username, <<"';">>],
- [<<"insert into privacy_default_list(username, "
- "name) ">>,
- <<"values ('">>, Username, <<"', '">>,
- SDefault, <<"');">>]];
- true ->
- []
- end ++
- lists:flatmap(
- fun({Name, List}) ->
- SName = ejabberd_odbc:escape(Name),
- RItems = lists:map(fun item_to_raw/1, List),
- ID = jlib:integer_to_binary(get_id()),
- [[<<"delete from privacy_list where username='">>,
- Username, <<"' and name='">>,
- SName, <<"';">>],
- [<<"insert into privacy_list(username, "
- "name, id) values ('">>,
- Username, <<"', '">>, SName,
- <<"', '">>, ID, <<"');">>],
- [<<"delete from privacy_list_data where "
- "id='">>, ID, <<"';">>]] ++
- [[<<"insert into privacy_list_data(id, t, "
- "value, action, ord, match_all, match_iq, "
- "match_message, match_presence_in, "
- "match_presence_out) values ('">>,
- ID, <<"', '">>, str:join(Items, <<"', '">>),
- <<"');">>] || Items <- RItems]
- end,
- Lists);
- (_Host, _R) ->
- []
- end}].
-
-get_id() ->
- ID = get(id),
- put(id, ID + 1),
- ID + 1.
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import(LServer) ->
- [{<<"select username from privacy_list;">>,
- fun([LUser]) ->
- Default = case sql_get_default_privacy_list_t(LUser) of
- {selected, [<<"name">>], []} ->
- none;
- {selected, [<<"name">>], [[DefName]]} ->
- DefName;
- _ ->
- none
- end,
- {selected, [<<"name">>], Names} =
- sql_get_privacy_list_names_t(LUser),
- Lists = lists:flatmap(
- fun([Name]) ->
- case sql_get_privacy_list_data_t(LUser, Name) of
- {selected, _, RItems} ->
- [{Name,
- lists:map(fun raw_to_item/1,
- RItems)}];
- _ ->
- []
- end
- end, Names),
- #privacy{default = Default,
- us = {LUser, LServer},
- lists = Lists}
- end}].
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
-import(_LServer, mnesia, #privacy{} = P) ->
- mnesia:dirty_write(P);
-import(_LServer, riak, #privacy{} = P) ->
- ejabberd_riak:put(P, privacy_schema());
-import(_, _, _) ->
- pass.
+import(LServer, DBType, Data) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, Data).
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
diff --git a/src/mod_privacy_mnesia.erl b/src/mod_privacy_mnesia.erl
new file mode 100644
index 00000000..4026b7f6
--- /dev/null
+++ b/src/mod_privacy_mnesia.erl
@@ -0,0 +1,198 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_privacy_mnesia).
+
+-behaviour(mod_privacy).
+
+%% API
+-export([init/2, process_lists_get/2, process_list_get/3,
+ process_default_set/3, process_active_set/3,
+ remove_privacy_list/3, set_privacy_list/1,
+ set_privacy_list/4, get_user_list/2, get_user_lists/2,
+ remove_user/2, import/2]).
+
+-include("jlib.hrl").
+-include("mod_privacy.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(privacy,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, privacy)}]),
+ update_table().
+
+process_lists_get(LUser, LServer) ->
+ case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
+ {'EXIT', _Reason} -> error;
+ [] -> {none, []};
+ [#privacy{default = Default, lists = Lists}] ->
+ LItems = lists:map(fun ({N, _}) ->
+ #xmlel{name = <<"list">>,
+ attrs = [{<<"name">>, N}],
+ children = []}
+ end, Lists),
+ {Default, LItems}
+ end.
+
+process_list_get(LUser, LServer, Name) ->
+ case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
+ {'EXIT', _Reason} -> error;
+ [] -> not_found;
+ [#privacy{lists = Lists}] ->
+ case lists:keysearch(Name, 1, Lists) of
+ {value, {_, List}} -> List;
+ _ -> not_found
+ end
+ end.
+
+process_default_set(LUser, LServer, {value, Name}) ->
+ F = fun () ->
+ case mnesia:read({privacy, {LUser, LServer}}) of
+ [] -> not_found;
+ [#privacy{lists = Lists} = P] ->
+ case lists:keymember(Name, 1, Lists) of
+ true ->
+ mnesia:write(P#privacy{default = Name,
+ lists = Lists}),
+ ok;
+ false -> not_found
+ end
+ end
+ end,
+ mnesia:transaction(F);
+process_default_set(LUser, LServer, false) ->
+ F = fun () ->
+ case mnesia:read({privacy, {LUser, LServer}}) of
+ [] -> ok;
+ [R] -> mnesia:write(R#privacy{default = none})
+ end
+ end,
+ mnesia:transaction(F).
+
+process_active_set(LUser, LServer, Name) ->
+ case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
+ [] -> error;
+ [#privacy{lists = Lists}] ->
+ case lists:keysearch(Name, 1, Lists) of
+ {value, {_, List}} -> List;
+ false -> error
+ end
+ end.
+
+remove_privacy_list(LUser, LServer, Name) ->
+ F = fun () ->
+ case mnesia:read({privacy, {LUser, LServer}}) of
+ [] -> ok;
+ [#privacy{default = Default, lists = Lists} = P] ->
+ if Name == Default -> conflict;
+ true ->
+ NewLists = lists:keydelete(Name, 1, Lists),
+ mnesia:write(P#privacy{lists = NewLists})
+ end
+ end
+ end,
+ mnesia:transaction(F).
+
+set_privacy_list(Privacy) ->
+ mnesia:dirty_write(Privacy).
+
+set_privacy_list(LUser, LServer, Name, List) ->
+ F = fun () ->
+ case mnesia:wread({privacy, {LUser, LServer}}) of
+ [] ->
+ NewLists = [{Name, List}],
+ mnesia:write(#privacy{us = {LUser, LServer},
+ lists = NewLists});
+ [#privacy{lists = Lists} = P] ->
+ NewLists1 = lists:keydelete(Name, 1, Lists),
+ NewLists = [{Name, List} | NewLists1],
+ mnesia:write(P#privacy{lists = NewLists})
+ end
+ end,
+ mnesia:transaction(F).
+
+get_user_list(LUser, LServer) ->
+ case catch mnesia:dirty_read(privacy, {LUser, LServer})
+ of
+ [] -> {none, []};
+ [#privacy{default = Default, lists = Lists}] ->
+ case Default of
+ none -> {none, []};
+ _ ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} -> {Default, List};
+ _ -> {none, []}
+ end
+ end;
+ _ -> {none, []}
+ end.
+
+get_user_lists(LUser, LServer) ->
+ case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
+ [#privacy{} = P] ->
+ {ok, P};
+ _ ->
+ error
+ end.
+
+remove_user(LUser, LServer) ->
+ F = fun () -> mnesia:delete({privacy, {LUser, LServer}}) end,
+ mnesia:transaction(F).
+
+import(_LServer, #privacy{} = P) ->
+ mnesia:dirty_write(P).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_table() ->
+ Fields = record_info(fields, privacy),
+ case mnesia:table_info(privacy, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ privacy, Fields, set,
+ fun(#privacy{us = {U, _}}) -> U end,
+ fun(#privacy{us = {U, S}, default = Def, lists = Lists} = R) ->
+ NewLists =
+ lists:map(
+ fun({Name, Ls}) ->
+ NewLs =
+ lists:map(
+ fun(#listitem{value = Val} = L) ->
+ NewVal =
+ case Val of
+ {LU, LS, LR} ->
+ {iolist_to_binary(LU),
+ iolist_to_binary(LS),
+ iolist_to_binary(LR)};
+ none -> none;
+ both -> both;
+ from -> from;
+ to -> to;
+ _ -> iolist_to_binary(Val)
+ end,
+ L#listitem{value = NewVal}
+ end, Ls),
+ {iolist_to_binary(Name), NewLs}
+ end, Lists),
+ NewDef = case Def of
+ none -> none;
+ _ -> iolist_to_binary(Def)
+ end,
+ NewUS = {iolist_to_binary(U), iolist_to_binary(S)},
+ R#privacy{us = NewUS, default = NewDef,
+ lists = NewLists}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating privacy table", []),
+ mnesia:transform_table(privacy, ignore, Fields)
+ end.
diff --git a/src/mod_privacy_riak.erl b/src/mod_privacy_riak.erl
new file mode 100644
index 00000000..0c43e74f
--- /dev/null
+++ b/src/mod_privacy_riak.erl
@@ -0,0 +1,160 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_privacy_riak).
+
+-behaviour(mod_privacy).
+
+%% API
+-export([init/2, process_lists_get/2, process_list_get/3,
+ process_default_set/3, process_active_set/3,
+ remove_privacy_list/3, set_privacy_list/1,
+ set_privacy_list/4, get_user_list/2, get_user_lists/2,
+ remove_user/2, import/2]).
+
+-export([privacy_schema/0]).
+
+-include("jlib.hrl").
+-include("mod_privacy.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+process_lists_get(LUser, LServer) ->
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, #privacy{default = Default, lists = Lists}} ->
+ LItems = lists:map(fun ({N, _}) ->
+ #xmlel{name = <<"list">>,
+ attrs = [{<<"name">>, N}],
+ children = []}
+ end,
+ Lists),
+ {Default, LItems};
+ {error, notfound} ->
+ {none, []};
+ {error, _} ->
+ error
+ end.
+
+process_list_get(LUser, LServer, Name) ->
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, #privacy{lists = Lists}} ->
+ case lists:keysearch(Name, 1, Lists) of
+ {value, {_, List}} -> List;
+ _ -> not_found
+ end;
+ {error, notfound} ->
+ not_found;
+ {error, _} ->
+ error
+ end.
+
+process_default_set(LUser, LServer, {value, Name}) ->
+ {atomic,
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, #privacy{lists = Lists} = P} ->
+ case lists:keymember(Name, 1, Lists) of
+ true ->
+ ejabberd_riak:put(P#privacy{default = Name,
+ lists = Lists},
+ privacy_schema());
+ false ->
+ not_found
+ end;
+ {error, _} ->
+ not_found
+ end};
+process_default_set(LUser, LServer, false) ->
+ {atomic,
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, R} ->
+ ejabberd_riak:put(R#privacy{default = none}, privacy_schema());
+ {error, _} ->
+ ok
+ end}.
+
+process_active_set(LUser, LServer, Name) ->
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, #privacy{lists = Lists}} ->
+ case lists:keysearch(Name, 1, Lists) of
+ {value, {_, List}} -> List;
+ false -> error
+ end;
+ {error, _} ->
+ error
+ end.
+
+remove_privacy_list(LUser, LServer, Name) ->
+ {atomic,
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, #privacy{default = Default, lists = Lists} = P} ->
+ if Name == Default ->
+ conflict;
+ true ->
+ NewLists = lists:keydelete(Name, 1, Lists),
+ ejabberd_riak:put(P#privacy{lists = NewLists},
+ privacy_schema())
+ end;
+ {error, _} ->
+ ok
+ end}.
+
+set_privacy_list(Privacy) ->
+ ejabberd_riak:put(Privacy, privacy_schema()).
+
+set_privacy_list(LUser, LServer, Name, List) ->
+ {atomic,
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, #privacy{lists = Lists} = P} ->
+ NewLists1 = lists:keydelete(Name, 1, Lists),
+ NewLists = [{Name, List} | NewLists1],
+ ejabberd_riak:put(P#privacy{lists = NewLists}, privacy_schema());
+ {error, _} ->
+ NewLists = [{Name, List}],
+ ejabberd_riak:put(#privacy{us = {LUser, LServer},
+ lists = NewLists},
+ privacy_schema())
+ end}.
+
+get_user_list(LUser, LServer) ->
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, #privacy{default = Default, lists = Lists}} ->
+ case Default of
+ none -> {none, []};
+ _ ->
+ case lists:keysearch(Default, 1, Lists) of
+ {value, {_, List}} -> {Default, List};
+ _ -> {none, []}
+ end
+ end;
+ {error, _} ->
+ {none, []}
+ end.
+
+get_user_lists(LUser, LServer) ->
+ case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
+ {ok, #privacy{} = P} ->
+ {ok, P};
+ {error, _} ->
+ error
+ end.
+
+remove_user(LUser, LServer) ->
+ {atomic, ejabberd_riak:delete(privacy, {LUser, LServer})}.
+
+import(_LServer, #privacy{} = P) ->
+ ejabberd_riak:put(P, privacy_schema()).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+privacy_schema() ->
+ {record_info(fields, privacy), #privacy{}}.
diff --git a/src/mod_privacy_sql.erl b/src/mod_privacy_sql.erl
new file mode 100644
index 00000000..6b996fa8
--- /dev/null
+++ b/src/mod_privacy_sql.erl
@@ -0,0 +1,397 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_privacy_sql).
+
+-behaviour(mod_privacy).
+
+%% API
+-export([init/2, process_lists_get/2, process_list_get/3,
+ process_default_set/3, process_active_set/3,
+ remove_privacy_list/3, set_privacy_list/1,
+ set_privacy_list/4, get_user_list/2, get_user_lists/2,
+ remove_user/2, import/1, import/2, export/1]).
+
+-export([item_to_raw/1, raw_to_item/1,
+ sql_add_privacy_list/2,
+ sql_get_default_privacy_list/2,
+ sql_get_default_privacy_list_t/1,
+ sql_get_privacy_list_data/3,
+ sql_get_privacy_list_data_by_id_t/1,
+ sql_get_privacy_list_id_t/2,
+ sql_set_default_privacy_list/2, sql_set_privacy_list/2]).
+
+-include("jlib.hrl").
+-include("mod_privacy.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+process_lists_get(LUser, LServer) ->
+ Default = case catch sql_get_default_privacy_list(LUser, LServer) of
+ {selected, []} -> none;
+ {selected, [{DefName}]} -> DefName;
+ _ -> none
+ end,
+ case catch sql_get_privacy_list_names(LUser, LServer) of
+ {selected, Names} ->
+ LItems = lists:map(fun ({N}) ->
+ #xmlel{name = <<"list">>,
+ attrs = [{<<"name">>, N}],
+ children = []}
+ end,
+ Names),
+ {Default, LItems};
+ _ -> error
+ end.
+
+process_list_get(LUser, LServer, Name) ->
+ case catch sql_get_privacy_list_id(LUser, LServer, Name) of
+ {selected, []} -> not_found;
+ {selected, [{ID}]} ->
+ case catch sql_get_privacy_list_data_by_id(ID, LServer) of
+ {selected, RItems} ->
+ lists:flatmap(fun raw_to_item/1, RItems);
+ _ -> error
+ end;
+ _ -> error
+ end.
+
+process_default_set(LUser, LServer, {value, Name}) ->
+ F = fun () ->
+ case sql_get_privacy_list_names_t(LUser) of
+ {selected, []} -> not_found;
+ {selected, Names} ->
+ case lists:member({Name}, Names) of
+ true -> sql_set_default_privacy_list(LUser, Name), ok;
+ false -> not_found
+ end
+ end
+ end,
+ sql_queries:sql_transaction(LServer, F);
+process_default_set(LUser, LServer, false) ->
+ case catch sql_unset_default_privacy_list(LUser,
+ LServer)
+ of
+ {'EXIT', _Reason} -> {atomic, error};
+ {error, _Reason} -> {atomic, error};
+ _ -> {atomic, ok}
+ end.
+
+process_active_set(LUser, LServer, Name) ->
+ case catch sql_get_privacy_list_id(LUser, LServer, Name) of
+ {selected, []} -> error;
+ {selected, [{ID}]} ->
+ case catch sql_get_privacy_list_data_by_id(ID, LServer) of
+ {selected, RItems} ->
+ lists:flatmap(fun raw_to_item/1, RItems);
+ _ -> error
+ end;
+ _ -> error
+ end.
+
+remove_privacy_list(LUser, LServer, Name) ->
+ F = fun () ->
+ case sql_get_default_privacy_list_t(LUser) of
+ {selected, []} ->
+ sql_remove_privacy_list(LUser, Name), ok;
+ {selected, [{Default}]} ->
+ if Name == Default -> conflict;
+ true -> sql_remove_privacy_list(LUser, Name), ok
+ end
+ end
+ end,
+ sql_queries:sql_transaction(LServer, F).
+
+set_privacy_list(#privacy{us = {LUser, LServer},
+ default = Default,
+ lists = Lists}) ->
+ F = fun() ->
+ lists:foreach(
+ fun({Name, List}) ->
+ sql_add_privacy_list(LUser, Name),
+ {selected, [<<"id">>], [[I]]} =
+ sql_get_privacy_list_id_t(LUser, Name),
+ RItems = lists:map(fun item_to_raw/1, List),
+ sql_set_privacy_list(I, RItems),
+ if is_binary(Default) ->
+ sql_set_default_privacy_list(LUser, Default),
+ ok;
+ true ->
+ ok
+ end
+ end, Lists)
+ end,
+ sql_queries:sql_transaction(LServer, F).
+
+set_privacy_list(LUser, LServer, Name, List) ->
+ RItems = lists:map(fun item_to_raw/1, List),
+ F = fun () ->
+ ID = case sql_get_privacy_list_id_t(LUser, Name) of
+ {selected, []} ->
+ sql_add_privacy_list(LUser, Name),
+ {selected, [{I}]} =
+ sql_get_privacy_list_id_t(LUser, Name),
+ I;
+ {selected, [{I}]} -> I
+ end,
+ sql_set_privacy_list(ID, RItems),
+ ok
+ end,
+ sql_queries:sql_transaction(LServer, F).
+
+get_user_list(LUser, LServer) ->
+ case catch sql_get_default_privacy_list(LUser, LServer)
+ of
+ {selected, []} -> {none, []};
+ {selected, [{Default}]} ->
+ case catch sql_get_privacy_list_data(LUser, LServer,
+ Default) of
+ {selected, RItems} ->
+ {Default, lists:flatmap(fun raw_to_item/1, RItems)};
+ _ -> {none, []}
+ end;
+ _ -> {none, []}
+ end.
+
+get_user_lists(LUser, LServer) ->
+ Default = case catch sql_get_default_privacy_list(LUser, LServer) of
+ {selected, []} ->
+ none;
+ {selected, [{DefName}]} ->
+ DefName;
+ _ ->
+ none
+ end,
+ case catch sql_get_privacy_list_names(LUser, LServer) of
+ {selected, Names} ->
+ Lists =
+ lists:flatmap(
+ fun({Name}) ->
+ case catch sql_get_privacy_list_data(
+ LUser, LServer, Name) of
+ {selected, RItems} ->
+ [{Name, lists:flatmap(fun raw_to_item/1, RItems)}];
+ _ ->
+ []
+ end
+ end, Names),
+ {ok, #privacy{default = Default,
+ us = {LUser, LServer},
+ lists = Lists}};
+ _ ->
+ error
+ end.
+
+remove_user(LUser, LServer) ->
+ sql_del_privacy_lists(LUser, LServer).
+
+export(Server) ->
+ case catch ejabberd_sql:sql_query(jid:nameprep(Server),
+ [<<"select id from privacy_list order by "
+ "id desc limit 1;">>]) of
+ {selected, [<<"id">>], [[I]]} ->
+ put(id, jlib:binary_to_integer(I));
+ _ ->
+ put(id, 0)
+ end,
+ [{privacy,
+ fun(Host, #privacy{us = {LUser, LServer}, lists = Lists,
+ default = Default})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ if Default /= none ->
+ SDefault = ejabberd_sql:escape(Default),
+ [[<<"delete from privacy_default_list where ">>,
+ <<"username='">>, Username, <<"';">>],
+ [<<"insert into privacy_default_list(username, "
+ "name) ">>,
+ <<"values ('">>, Username, <<"', '">>,
+ SDefault, <<"');">>]];
+ true ->
+ []
+ end ++
+ lists:flatmap(
+ fun({Name, List}) ->
+ SName = ejabberd_sql:escape(Name),
+ RItems = lists:map(fun item_to_raw/1, List),
+ ID = jlib:integer_to_binary(get_id()),
+ [[<<"delete from privacy_list where username='">>,
+ Username, <<"' and name='">>,
+ SName, <<"';">>],
+ [<<"insert into privacy_list(username, "
+ "name, id) values ('">>,
+ Username, <<"', '">>, SName,
+ <<"', '">>, ID, <<"');">>],
+ [<<"delete from privacy_list_data where "
+ "id='">>, ID, <<"';">>]] ++
+ [[<<"insert into privacy_list_data(id, t, "
+ "value, action, ord, match_all, match_iq, "
+ "match_message, match_presence_in, "
+ "match_presence_out) values ('">>,
+ ID, <<"', '">>, str:join(Items, <<"', '">>),
+ <<"');">>] || Items <- RItems]
+ end,
+ Lists);
+ (_Host, _R) ->
+ []
+ end}].
+
+get_id() ->
+ ID = get(id),
+ put(id, ID + 1),
+ ID + 1.
+
+import(LServer) ->
+ [{<<"select username from privacy_list;">>,
+ fun([LUser]) ->
+ Default = case sql_get_default_privacy_list_t(LUser) of
+ {selected, [<<"name">>], []} ->
+ none;
+ {selected, [<<"name">>], [[DefName]]} ->
+ DefName;
+ _ ->
+ none
+ end,
+ {selected, [<<"name">>], Names} =
+ sql_get_privacy_list_names_t(LUser),
+ Lists = lists:flatmap(
+ fun([Name]) ->
+ case sql_get_privacy_list_data_t(LUser, Name) of
+ {selected, _, RItems} ->
+ [{Name,
+ lists:map(fun raw_to_item/1,
+ RItems)}];
+ _ ->
+ []
+ end
+ end, Names),
+ #privacy{default = Default,
+ us = {LUser, LServer},
+ lists = Lists}
+ end}].
+
+import(_, _) ->
+ pass.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+raw_to_item({SType, SValue, SAction, Order, MatchAll,
+ MatchIQ, MatchMessage, MatchPresenceIn,
+ MatchPresenceOut} = Row) ->
+ try
+ {Type, Value} = case SType of
+ <<"n">> -> {none, none};
+ <<"j">> ->
+ case jid:from_string(SValue) of
+ #jid{} = JID ->
+ {jid, jid:tolower(JID)}
+ end;
+ <<"g">> -> {group, SValue};
+ <<"s">> ->
+ case SValue of
+ <<"none">> -> {subscription, none};
+ <<"both">> -> {subscription, both};
+ <<"from">> -> {subscription, from};
+ <<"to">> -> {subscription, to}
+ end
+ end,
+ Action = case SAction of
+ <<"a">> -> allow;
+ <<"d">> -> deny
+ end,
+ [#listitem{type = Type, value = Value, action = Action,
+ order = Order, match_all = MatchAll, match_iq = MatchIQ,
+ match_message = MatchMessage,
+ match_presence_in = MatchPresenceIn,
+ match_presence_out = MatchPresenceOut}]
+ catch _:_ ->
+ ?WARNING_MSG("failed to parse row: ~p", [Row]),
+ []
+ end.
+
+item_to_raw(#listitem{type = Type, value = Value,
+ action = Action, order = Order, match_all = MatchAll,
+ match_iq = MatchIQ, match_message = MatchMessage,
+ match_presence_in = MatchPresenceIn,
+ match_presence_out = MatchPresenceOut}) ->
+ {SType, SValue} = case Type of
+ none -> {<<"n">>, <<"">>};
+ jid ->
+ {<<"j">>,
+ ejabberd_sql:escape(jid:to_string(Value))};
+ group -> {<<"g">>, ejabberd_sql:escape(Value)};
+ subscription ->
+ case Value of
+ none -> {<<"s">>, <<"none">>};
+ both -> {<<"s">>, <<"both">>};
+ from -> {<<"s">>, <<"from">>};
+ to -> {<<"s">>, <<"to">>}
+ end
+ end,
+ SAction = case Action of
+ allow -> <<"a">>;
+ deny -> <<"d">>
+ end,
+ {SType, SValue, SAction, Order, MatchAll, MatchIQ,
+ MatchMessage, MatchPresenceIn, MatchPresenceOut}.
+
+sql_get_default_privacy_list(LUser, LServer) ->
+ sql_queries:get_default_privacy_list(LServer, LUser).
+
+sql_get_default_privacy_list_t(LUser) ->
+ sql_queries:get_default_privacy_list_t(LUser).
+
+sql_get_privacy_list_names(LUser, LServer) ->
+ sql_queries:get_privacy_list_names(LServer, LUser).
+
+sql_get_privacy_list_names_t(LUser) ->
+ sql_queries:get_privacy_list_names_t(LUser).
+
+sql_get_privacy_list_id(LUser, LServer, Name) ->
+ sql_queries:get_privacy_list_id(LServer, LUser, Name).
+
+sql_get_privacy_list_id_t(LUser, Name) ->
+ sql_queries:get_privacy_list_id_t(LUser, Name).
+
+sql_get_privacy_list_data(LUser, LServer, Name) ->
+ sql_queries:get_privacy_list_data(LServer, LUser, Name).
+
+sql_get_privacy_list_data_t(LUser, Name) ->
+ Username = ejabberd_sql:escape(LUser),
+ SName = ejabberd_sql:escape(Name),
+ sql_queries:get_privacy_list_data_t(Username, SName).
+
+sql_get_privacy_list_data_by_id(ID, LServer) ->
+ sql_queries:get_privacy_list_data_by_id(LServer, ID).
+
+sql_get_privacy_list_data_by_id_t(ID) ->
+ sql_queries:get_privacy_list_data_by_id_t(ID).
+
+sql_set_default_privacy_list(LUser, Name) ->
+ sql_queries:set_default_privacy_list(LUser, Name).
+
+sql_unset_default_privacy_list(LUser, LServer) ->
+ sql_queries:unset_default_privacy_list(LServer, LUser).
+
+sql_remove_privacy_list(LUser, Name) ->
+ sql_queries:remove_privacy_list(LUser, Name).
+
+sql_add_privacy_list(LUser, Name) ->
+ sql_queries:add_privacy_list(LUser, Name).
+
+sql_set_privacy_list(ID, RItems) ->
+ sql_queries:set_privacy_list(ID, RItems).
+
+sql_del_privacy_lists(LUser, LServer) ->
+ sql_queries:del_privacy_lists(LServer, LUser).
diff --git a/src/mod_private.erl b/src/mod_private.erl
index a03d83e5..029789e6 100644
--- a/src/mod_private.erl
+++ b/src/mod_private.erl
@@ -33,18 +33,20 @@
-export([start/2, stop/1, process_sm_iq/3, import/3,
remove_user/2, get_data/2, export/1, import/1,
- mod_opt_type/1]).
+ mod_opt_type/1, set_data/3]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-
--record(private_storage,
- {usns = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() |
- '$1' | '_'},
- xml = #xmlel{} :: xmlel() | '_' | '$1'}).
-
+-include("mod_private.hrl").
+
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #private_storage{}) -> ok | pass.
+-callback set_data(binary(), binary(), [{binary(), xmlel()}]) -> {atomic, any()}.
+-callback get_data(binary(), binary(), binary()) -> {ok, xmlel()} | error.
+-callback get_all_data(binary(), binary()) -> [xmlel()].
+
-define(Xmlel_Query(Attrs, Children),
#xmlel{name = <<"query">>, attrs = Attrs,
children = Children}).
@@ -52,15 +54,8 @@
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(private_storage,
- [{disc_only_copies, [node()]},
- {attributes,
- record_info(fields, private_storage)}]),
- update_table();
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
@@ -73,255 +68,110 @@ stop(Host) ->
?NS_PRIVATE).
process_sm_iq(#jid{luser = LUser, lserver = LServer},
- #jid{luser = LUser, lserver = LServer}, IQ)
+ #jid{luser = LUser, lserver = LServer}, #iq{lang = Lang} = IQ)
when IQ#iq.type == set ->
case IQ#iq.sub_el of
#xmlel{name = <<"query">>, children = Xmlels} ->
case filter_xmlels(Xmlels) of
[] ->
+ Txt = <<"No private data found in this query">>,
IQ#iq{type = error,
- sub_el = [IQ#iq.sub_el, ?ERR_NOT_ACCEPTABLE]};
+ sub_el = [IQ#iq.sub_el, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)]};
Data ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- F = fun () ->
- lists:foreach(fun (Datum) ->
- set_data(LUser, LServer,
- Datum, DBType)
- end,
- Data)
- end,
- case DBType of
- odbc -> ejabberd_odbc:sql_transaction(LServer, F);
- mnesia -> mnesia:transaction(F);
- riak -> F()
- end,
+ set_data(LUser, LServer, Data),
IQ#iq{type = result, sub_el = []}
end;
_ ->
+ Txt = <<"No query found">>,
IQ#iq{type = error,
- sub_el = [IQ#iq.sub_el, ?ERR_NOT_ACCEPTABLE]}
+ sub_el = [IQ#iq.sub_el, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)]}
end;
%%
process_sm_iq(#jid{luser = LUser, lserver = LServer},
- #jid{luser = LUser, lserver = LServer}, IQ)
+ #jid{luser = LUser, lserver = LServer}, #iq{lang = Lang} = IQ)
when IQ#iq.type == get ->
case IQ#iq.sub_el of
#xmlel{name = <<"query">>, attrs = Attrs,
children = Xmlels} ->
case filter_xmlels(Xmlels) of
[] ->
+ Txt = <<"No private data found in this query">>,
IQ#iq{type = error,
- sub_el = [IQ#iq.sub_el, ?ERR_BAD_FORMAT]};
+ sub_el = [IQ#iq.sub_el, ?ERRT_BAD_FORMAT(Lang, Txt)]};
Data ->
case catch get_data(LUser, LServer, Data) of
{'EXIT', _Reason} ->
+ Txt = <<"Database failure">>,
IQ#iq{type = error,
sub_el =
- [IQ#iq.sub_el, ?ERR_INTERNAL_SERVER_ERROR]};
+ [IQ#iq.sub_el, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]};
Storage_Xmlels ->
IQ#iq{type = result,
sub_el = [?Xmlel_Query(Attrs, Storage_Xmlels)]}
end
end;
_ ->
+ Txt = <<"No query found">>,
IQ#iq{type = error,
- sub_el = [IQ#iq.sub_el, ?ERR_BAD_FORMAT]}
+ sub_el = [IQ#iq.sub_el, ?ERRT_BAD_FORMAT(Lang, Txt)]}
end;
%%
-process_sm_iq(_From, _To, IQ) ->
+process_sm_iq(_From, _To, #iq{lang = Lang} = IQ) ->
+ Txt = <<"Query to another users is forbidden">>,
IQ#iq{type = error,
- sub_el = [IQ#iq.sub_el, ?ERR_FORBIDDEN]}.
+ sub_el = [IQ#iq.sub_el, ?ERRT_FORBIDDEN(Lang, Txt)]}.
filter_xmlels(Xmlels) -> filter_xmlels(Xmlels, []).
filter_xmlels([], Data) -> lists:reverse(Data);
filter_xmlels([#xmlel{attrs = Attrs} = Xmlel | Xmlels],
Data) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
<<"">> -> [];
XmlNS -> filter_xmlels(Xmlels, [{XmlNS, Xmlel} | Data])
end;
filter_xmlels([_ | Xmlels], Data) ->
filter_xmlels(Xmlels, Data).
-set_data(LUser, LServer, {XmlNS, Xmlel}, mnesia) ->
- mnesia:write(#private_storage{usns =
- {LUser, LServer, XmlNS},
- xml = Xmlel});
-set_data(LUser, LServer, {XMLNS, El}, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- LXMLNS = ejabberd_odbc:escape(XMLNS),
- SData = ejabberd_odbc:escape(xml:element_to_binary(El)),
- odbc_queries:set_private_data(LServer, Username, LXMLNS,
- SData);
-set_data(LUser, LServer, {XMLNS, El}, riak) ->
- ejabberd_riak:put(#private_storage{usns = {LUser, LServer, XMLNS},
- xml = El},
- private_storage_schema(),
- [{'2i', [{<<"us">>, {LUser, LServer}}]}]).
+set_data(LUser, LServer, Data) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:set_data(LUser, LServer, Data).
get_data(LUser, LServer, Data) ->
- get_data(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE), Data, []).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ get_data(LUser, LServer, Data, Mod, []).
-get_data(_LUser, _LServer, _DBType, [],
- Storage_Xmlels) ->
+get_data(_LUser, _LServer, [], _Mod, Storage_Xmlels) ->
lists:reverse(Storage_Xmlels);
-get_data(LUser, LServer, mnesia,
- [{XmlNS, Xmlel} | Data], Storage_Xmlels) ->
- case mnesia:dirty_read(private_storage,
- {LUser, LServer, XmlNS})
- of
- [#private_storage{xml = Storage_Xmlel}] ->
- get_data(LUser, LServer, mnesia, Data,
- [Storage_Xmlel | Storage_Xmlels]);
- _ ->
- get_data(LUser, LServer, mnesia, Data,
- [Xmlel | Storage_Xmlels])
- end;
-get_data(LUser, LServer, odbc, [{XMLNS, El} | Els],
- Res) ->
- Username = ejabberd_odbc:escape(LUser),
- LXMLNS = ejabberd_odbc:escape(XMLNS),
- case catch odbc_queries:get_private_data(LServer,
- Username, LXMLNS)
- of
- {selected, [<<"data">>], [[SData]]} ->
- case xml_stream:parse_element(SData) of
- Data when is_record(Data, xmlel) ->
- get_data(LUser, LServer, odbc, Els, [Data | Res])
- end;
- _ -> get_data(LUser, LServer, odbc, Els, [El | Res])
- end;
-get_data(LUser, LServer, riak, [{XMLNS, El} | Els],
- Res) ->
- case ejabberd_riak:get(private_storage, private_storage_schema(),
- {LUser, LServer, XMLNS}) of
- {ok, #private_storage{xml = NewEl}} ->
- get_data(LUser, LServer, riak, Els, [NewEl|Res]);
- _ ->
- get_data(LUser, LServer, riak, Els, [El|Res])
+get_data(LUser, LServer, [{XmlNS, Xmlel} | Data], Mod, Storage_Xmlels) ->
+ case Mod:get_data(LUser, LServer, XmlNS) of
+ {ok, Storage_Xmlel} ->
+ get_data(LUser, LServer, Data, Mod, [Storage_Xmlel | Storage_Xmlels]);
+ error ->
+ get_data(LUser, LServer, Data, Mod, [Xmlel | Storage_Xmlels])
end.
get_data(LUser, LServer) ->
- get_all_data(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-get_all_data(LUser, LServer, mnesia) ->
- lists:flatten(
- mnesia:dirty_select(private_storage,
- [{#private_storage{usns = {LUser, LServer, '_'},
- xml = '$1'},
- [], ['$1']}]));
-get_all_data(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_private_data(LServer, Username) of
- {selected, [<<"namespace">>, <<"data">>], Res} ->
- lists:flatmap(
- fun([_, SData]) ->
- case xml_stream:parse_element(SData) of
- #xmlel{} = El ->
- [El];
- _ ->
- []
- end
- end, Res);
- _ ->
- []
- end;
-get_all_data(LUser, LServer, riak) ->
- case ejabberd_riak:get_by_index(
- private_storage, private_storage_schema(),
- <<"us">>, {LUser, LServer}) of
- {ok, Res} ->
- [El || #private_storage{xml = El} <- Res];
- _ ->
- []
- end.
-
-private_storage_schema() ->
- {record_info(fields, private_storage), #private_storage{}}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_all_data(LUser, LServer).
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- remove_user(LUser, LServer,
- gen_mod:db_type(Server, ?MODULE)).
+ Mod = gen_mod:db_mod(Server, ?MODULE),
+ Mod:remove_user(LUser, LServer).
-remove_user(LUser, LServer, mnesia) ->
- F = fun () ->
- Namespaces = mnesia:select(private_storage,
- [{#private_storage{usns =
- {LUser,
- LServer,
- '$1'},
- _ = '_'},
- [], ['$$']}]),
- lists:foreach(fun ([Namespace]) ->
- mnesia:delete({private_storage,
- {LUser, LServer,
- Namespace}})
- end,
- Namespaces)
- end,
- mnesia:transaction(F);
-remove_user(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_user_private_storage(LServer,
- Username);
-remove_user(LUser, LServer, riak) ->
- {atomic, ejabberd_riak:delete_by_index(private_storage,
- <<"us">>, {LUser, LServer})}.
-
-update_table() ->
- Fields = record_info(fields, private_storage),
- case mnesia:table_info(private_storage, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- private_storage, Fields, set,
- fun(#private_storage{usns = {U, _, _}}) -> U end,
- fun(#private_storage{usns = {U, S, NS}, xml = El} = R) ->
- R#private_storage{usns = {iolist_to_binary(U),
- iolist_to_binary(S),
- iolist_to_binary(NS)},
- xml = xml:to_xmlel(El)}
- end);
- _ ->
- ?INFO_MSG("Recreating private_storage table", []),
- mnesia:transform_table(private_storage, ignore, Fields)
- end.
-
-export(_Server) ->
- [{private_storage,
- fun(Host, #private_storage{usns = {LUser, LServer, XMLNS},
- xml = Data})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- LXMLNS = ejabberd_odbc:escape(XMLNS),
- SData =
- ejabberd_odbc:escape(xml:element_to_binary(Data)),
- odbc_queries:set_private_data_sql(Username, LXMLNS,
- SData);
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import(LServer) ->
- [{<<"select username, namespace, data from private_storage;">>,
- fun([LUser, XMLNS, XML]) ->
- El = #xmlel{} = xml_stream:parse_element(XML),
- #private_storage{usns = {LUser, LServer, XMLNS},
- xml = El}
- end}].
-
-import(_LServer, mnesia, #private_storage{} = PS) ->
- mnesia:dirty_write(PS);
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
-import(_LServer, riak, #private_storage{usns = {LUser, LServer, _}} = PS) ->
- ejabberd_riak:put(PS, private_storage_schema(),
- [{'2i', [{<<"us">>, {LUser, LServer}}]}]);
-import(_, _, _) ->
- pass.
+import(LServer, DBType, PD) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, PD).
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
diff --git a/src/mod_private_mnesia.erl b/src/mod_private_mnesia.erl
new file mode 100644
index 00000000..7a852c4f
--- /dev/null
+++ b/src/mod_private_mnesia.erl
@@ -0,0 +1,97 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_private_mnesia).
+-behaviour(mod_private).
+
+%% API
+-export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2,
+ import/2]).
+
+-include("jlib.hrl").
+-include("mod_private.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(private_storage,
+ [{disc_only_copies, [node()]},
+ {attributes,
+ record_info(fields, private_storage)}]),
+ update_table().
+
+set_data(LUser, LServer, Data) ->
+ F = fun () ->
+ lists:foreach(
+ fun({XmlNS, Xmlel}) ->
+ mnesia:write(
+ #private_storage{
+ usns = {LUser, LServer, XmlNS},
+ xml = Xmlel})
+ end, Data)
+ end,
+ mnesia:transaction(F).
+
+get_data(LUser, LServer, XmlNS) ->
+ case mnesia:dirty_read(private_storage, {LUser, LServer, XmlNS}) of
+ [#private_storage{xml = Storage_Xmlel}] ->
+ {ok, Storage_Xmlel};
+ _ ->
+ error
+ end.
+
+get_all_data(LUser, LServer) ->
+ lists:flatten(
+ mnesia:dirty_select(private_storage,
+ [{#private_storage{usns = {LUser, LServer, '_'},
+ xml = '$1'},
+ [], ['$1']}])).
+
+remove_user(LUser, LServer) ->
+ F = fun () ->
+ Namespaces = mnesia:select(private_storage,
+ [{#private_storage{usns =
+ {LUser,
+ LServer,
+ '$1'},
+ _ = '_'},
+ [], ['$$']}]),
+ lists:foreach(fun ([Namespace]) ->
+ mnesia:delete({private_storage,
+ {LUser, LServer,
+ Namespace}})
+ end,
+ Namespaces)
+ end,
+ mnesia:transaction(F).
+
+import(_LServer, #private_storage{} = PS) ->
+ mnesia:dirty_write(PS).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_table() ->
+ Fields = record_info(fields, private_storage),
+ case mnesia:table_info(private_storage, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ private_storage, Fields, set,
+ fun(#private_storage{usns = {U, _, _}}) -> U end,
+ fun(#private_storage{usns = {U, S, NS}, xml = El} = R) ->
+ R#private_storage{usns = {iolist_to_binary(U),
+ iolist_to_binary(S),
+ iolist_to_binary(NS)},
+ xml = fxml:to_xmlel(El)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating private_storage table", []),
+ mnesia:transform_table(private_storage, ignore, Fields)
+ end.
diff --git a/src/mod_private_riak.erl b/src/mod_private_riak.erl
new file mode 100644
index 00000000..11cfa477
--- /dev/null
+++ b/src/mod_private_riak.erl
@@ -0,0 +1,67 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_private_riak).
+
+-behaviour(mod_private).
+
+%% API
+-export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2,
+ import/2]).
+
+-include("jlib.hrl").
+-include("mod_private.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+set_data(LUser, LServer, Data) ->
+ lists:foreach(
+ fun({XMLNS, El}) ->
+ ejabberd_riak:put(#private_storage{usns = {LUser, LServer, XMLNS},
+ xml = El},
+ private_storage_schema(),
+ [{'2i', [{<<"us">>, {LUser, LServer}}]}])
+ end, Data),
+ {atomic, ok}.
+
+get_data(LUser, LServer, XMLNS) ->
+ case ejabberd_riak:get(private_storage, private_storage_schema(),
+ {LUser, LServer, XMLNS}) of
+ {ok, #private_storage{xml = El}} ->
+ {ok, El};
+ _ ->
+ error
+ end.
+
+get_all_data(LUser, LServer) ->
+ case ejabberd_riak:get_by_index(
+ private_storage, private_storage_schema(),
+ <<"us">>, {LUser, LServer}) of
+ {ok, Res} ->
+ [El || #private_storage{xml = El} <- Res];
+ _ ->
+ []
+ end.
+
+remove_user(LUser, LServer) ->
+ {atomic, ejabberd_riak:delete_by_index(private_storage,
+ <<"us">>, {LUser, LServer})}.
+
+import(_LServer, #private_storage{usns = {LUser, LServer, _}} = PS) ->
+ ejabberd_riak:put(PS, private_storage_schema(),
+ [{'2i', [{<<"us">>, {LUser, LServer}}]}]).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+private_storage_schema() ->
+ {record_info(fields, private_storage), #private_storage{}}.
diff --git a/src/mod_private_sql.erl b/src/mod_private_sql.erl
new file mode 100644
index 00000000..6ec6c9df
--- /dev/null
+++ b/src/mod_private_sql.erl
@@ -0,0 +1,97 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_private_sql).
+
+-behaviour(mod_private).
+
+%% API
+-export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2,
+ import/1, import/2, export/1]).
+
+-include("jlib.hrl").
+-include("mod_private.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+set_data(LUser, LServer, Data) ->
+ F = fun() ->
+ lists:foreach(
+ fun({XMLNS, El}) ->
+ SData = fxml:element_to_binary(El),
+ sql_queries:set_private_data(
+ LServer, LUser, XMLNS, SData)
+ end, Data)
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+get_data(LUser, LServer, XMLNS) ->
+ case catch sql_queries:get_private_data(LServer, LUser, XMLNS) of
+ {selected, [{SData}]} ->
+ case fxml_stream:parse_element(SData) of
+ Data when is_record(Data, xmlel) ->
+ {ok, Data};
+ _ ->
+ error
+ end;
+ _ ->
+ error
+ end.
+
+get_all_data(LUser, LServer) ->
+ case catch sql_queries:get_private_data(LServer, LUser) of
+ {selected, Res} ->
+ lists:flatmap(
+ fun({_, SData}) ->
+ case fxml_stream:parse_element(SData) of
+ #xmlel{} = El ->
+ [El];
+ _ ->
+ []
+ end
+ end, Res);
+ _ ->
+ []
+ end.
+
+remove_user(LUser, LServer) ->
+ sql_queries:del_user_private_storage(LServer, LUser).
+
+export(_Server) ->
+ [{private_storage,
+ fun(Host, #private_storage{usns = {LUser, LServer, XMLNS},
+ xml = Data})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ LXMLNS = ejabberd_sql:escape(XMLNS),
+ SData =
+ ejabberd_sql:escape(fxml:element_to_binary(Data)),
+ sql_queries:set_private_data_sql(Username, LXMLNS,
+ SData);
+ (_Host, _R) ->
+ []
+ end}].
+
+import(LServer) ->
+ [{<<"select username, namespace, data from private_storage;">>,
+ fun([LUser, XMLNS, XML]) ->
+ El = #xmlel{} = fxml_stream:parse_element(XML),
+ #private_storage{usns = {LUser, LServer, XMLNS},
+ xml = El}
+ end}].
+
+import(_, _) ->
+ pass.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_proxy65_service.erl b/src/mod_proxy65_service.erl
index 97d5b00a..d64afa04 100644
--- a/src/mod_proxy65_service.erl
+++ b/src/mod_proxy65_service.erl
@@ -63,7 +63,7 @@ start_link(Host, Opts) ->
init([Host, Opts]) ->
State = parse_options(Host, Opts),
- ejabberd_router:register_route(State#state.myhost),
+ ejabberd_router:register_route(State#state.myhost, Host),
{ok, State}.
terminate(_Reason, #state{myhost = MyHost}) ->
@@ -150,7 +150,7 @@ process_iq(_,
children = iq_vcard(Lang)}]};
%% bytestreams info request
process_iq(JID,
- #iq{type = get, sub_el = SubEl,
+ #iq{type = get, sub_el = SubEl, lang = Lang,
xmlns = ?NS_BYTESTREAMS} =
IQ,
#state{acl = ACL, stream_addr = StreamAddr,
@@ -165,21 +165,22 @@ process_iq(JID,
attrs = [{<<"xmlns">>, ?NS_BYTESTREAMS}],
children = StreamHostEl}]};
deny ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ Txt = <<"Denied by ACL">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
end;
%% bytestream activation request
process_iq(InitiatorJID,
- #iq{type = set, sub_el = SubEl,
+ #iq{type = set, sub_el = SubEl, lang = Lang,
xmlns = ?NS_BYTESTREAMS} =
IQ,
#state{acl = ACL, serverhost = ServerHost}) ->
case acl:match_rule(ServerHost, ACL, InitiatorJID) of
allow ->
- ActivateEl = xml:get_path_s(SubEl,
+ ActivateEl = fxml:get_path_s(SubEl,
[{elem, <<"activate">>}]),
- SID = xml:get_tag_attr_s(<<"sid">>, SubEl),
+ SID = fxml:get_tag_attr_s(<<"sid">>, SubEl),
case catch
- jid:from_string(xml:get_tag_cdata(ActivateEl))
+ jid:from_string(fxml:get_tag_cdata(ActivateEl))
of
TargetJID
when is_record(TargetJID, jid), SID /= <<"">>,
@@ -194,22 +195,27 @@ process_iq(InitiatorJID,
of
ok -> IQ#iq{type = result, sub_el = []};
false ->
+ Txt = <<"Failed to activate bytestream">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
+ sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]};
limit ->
+ Txt = <<"Too many active bytestreams">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_RESOURCE_CONSTRAINT]};
+ sub_el = [SubEl, ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt)]};
conflict ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_CONFLICT]};
+ Txt = <<"Bytestream already activated">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_CONFLICT(Lang, Txt)]};
_ ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
end;
_ ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ Txt = <<"Malformed JID">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
end;
deny ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ Txt = <<"Denied by ACL">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
end;
%% Unknown "set" or "get" request
process_iq(_, #iq{type = Type, sub_el = SubEl} = IQ, _)
diff --git a/src/mod_proxy65_stream.erl b/src/mod_proxy65_stream.erl
index 36dd7af1..84017329 100644
--- a/src/mod_proxy65_stream.erl
+++ b/src/mod_proxy65_stream.erl
@@ -153,7 +153,7 @@ wait_for_auth(Packet,
#state{socket = Socket, host = Host} = StateData) ->
case mod_proxy65_lib:unpack_auth_request(Packet) of
{User, Pass} ->
- Result = ejabberd_auth:check_password(User, Host, Pass),
+ Result = ejabberd_auth:check_password(User, <<"">>, Host, Pass),
gen_tcp:send(Socket,
mod_proxy65_lib:make_auth_reply(Result)),
case Result of
diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl
index 616c6492..e42d5c05 100644
--- a/src/mod_pubsub.erl
+++ b/src/mod_pubsub.erl
@@ -63,7 +63,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,
- delete_item/4, send_items/7, get_items/2, get_item/3,
+ 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]).
@@ -241,6 +241,7 @@ stop(Host) ->
init([ServerHost, Opts]) ->
?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]),
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>),
+ ejabberd_router:register_route(Host, ServerHost),
Access = gen_mod:get_opt(access_createnode, Opts,
fun(A) when is_atom(A) -> A end, all),
PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts,
@@ -256,22 +257,26 @@ init([ServerHost, Opts]) ->
DefaultNodeCfg = gen_mod:get_opt(default_node_config, Opts,
fun(A) when is_list(A) -> filter_node_options(A) end, []),
pubsub_index:init(Host, ServerHost, Opts),
- ets:new(gen_mod:get_module_proc(ServerHost, config), [set, named_table]),
{Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
mnesia:create_table(pubsub_last_item,
[{ram_copies, [node()]},
{attributes, record_info(fields, pubsub_last_item)}]),
mod_disco:register_feature(ServerHost, ?NS_PUBSUB),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {nodetree, NodeTree}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {plugins, Plugins}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {last_item_cache, LastItemCache}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {max_items_node, MaxItemsNode}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {max_subscriptions_node, MaxSubsNode}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {default_node_config, DefaultNodeCfg}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {pep_mapping, PepMapping}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {ignore_pep_from_offline, PepOffline}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {host, Host}),
- ets:insert(gen_mod:get_module_proc(ServerHost, config), {access, Access}),
+ lists:foreach(
+ fun(H) ->
+ T = gen_mod:get_module_proc(H, config),
+ ets:new(T, [set, named_table]),
+ ets:insert(T, {nodetree, NodeTree}),
+ ets:insert(T, {plugins, Plugins}),
+ ets:insert(T, {last_item_cache, LastItemCache}),
+ ets:insert(T, {max_items_node, MaxItemsNode}),
+ ets:insert(T, {max_subscriptions_node, MaxSubsNode}),
+ ets:insert(T, {default_node_config, DefaultNodeCfg}),
+ ets:insert(T, {pep_mapping, PepMapping}),
+ ets:insert(T, {ignore_pep_from_offline, PepOffline}),
+ ets:insert(T, {host, Host}),
+ ets:insert(T, {access, Access})
+ end, [Host, ServerHost]),
ejabberd_hooks:add(sm_remove_connection_hook, ServerHost,
?MODULE, on_user_offline, 75),
ejabberd_hooks:add(disco_local_identity, ServerHost,
@@ -309,7 +314,6 @@ init([ServerHost, Opts]) ->
false ->
ok
end,
- ejabberd_router:register_route(Host),
pubsub_migrate:update_node_database(Host, ServerHost),
pubsub_migrate:update_state_database(Host, ServerHost),
pubsub_migrate:update_lastitem_database(Host, ServerHost),
@@ -482,7 +486,7 @@ send_loop(State) ->
-> [xmlel()]
).
disco_local_identity(Acc, _From, To, <<>>, _Lang) ->
- case lists:member(?PEPNODE, plugins(To#jid.lserver)) of
+ case lists:member(?PEPNODE, plugins(host(To#jid.lserver))) of
true ->
[#xmlel{name = <<"identity">>,
attrs = [{<<"category">>, <<"pubsub">>},
@@ -504,7 +508,7 @@ disco_local_identity(Acc, _From, _To, _Node, _Lang) ->
-> [binary(),...]
).
disco_local_features(Acc, _From, To, <<>>, _Lang) ->
- Host = To#jid.lserver,
+ Host = host(To#jid.lserver),
Feats = case Acc of
{result, I} -> I;
_ -> []
@@ -873,7 +877,6 @@ handle_info(_Info, State) ->
%% @private
terminate(_Reason,
#state{host = Host, server_host = ServerHost, nodetree = TreePlugin, plugins = Plugins}) ->
- ejabberd_router:unregister_route(Host),
case lists:member(?PEPNODE, Plugins) of
true ->
ejabberd_hooks:delete(caps_add, ServerHost,
@@ -918,7 +921,8 @@ terminate(_Reason,
Pid ->
Pid ! stop
end,
- terminate_plugins(Host, ServerHost, Plugins, TreePlugin).
+ terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
+ ejabberd_router:unregister_route(Host).
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
@@ -951,7 +955,7 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
case jlib:iq_query_info(Packet) of
#iq{type = get, xmlns = ?NS_DISCO_INFO, sub_el = SubEl, lang = Lang} = IQ ->
#xmlel{attrs = QAttrs} = SubEl,
- Node = xml:get_attr_s(<<"node">>, QAttrs),
+ Node = fxml:get_attr_s(<<"node">>, QAttrs),
Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
[],
[ServerHost, ?MODULE, <<>>, <<>>]),
@@ -968,7 +972,7 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
ejabberd_router:route(To, From, Res);
#iq{type = get, xmlns = ?NS_DISCO_ITEMS, sub_el = SubEl} = IQ ->
#xmlel{attrs = QAttrs} = SubEl,
- Node = xml:get_attr_s(<<"node">>, QAttrs),
+ Node = fxml:get_attr_s(<<"node">>, QAttrs),
Res = case iq_disco_items(Host, Node, From, jlib:rsm_decode(IQ)) of
{result, IQRes} ->
jlib:iq_to_xml(IQ#iq{type = result,
@@ -1022,7 +1026,7 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
ok
end;
<<"message">> ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> ->
ok;
_ ->
@@ -1030,7 +1034,10 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
none ->
ok;
invalid ->
- Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
+ Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+ Txt = <<"Incorrect authorization response">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_router:route(To, From, Err);
XFields ->
handle_authorization_response(Host, From, To, Packet, XFields)
@@ -1040,7 +1047,7 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
ok
end;
_ ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> ->
ok;
<<"result">> ->
@@ -1236,7 +1243,7 @@ iq_get_vcard(Lang) ->
).
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) ->
- iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(ServerHost)).
+ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(Host)).
-spec(iq_pubsub/8 ::
(
@@ -1255,16 +1262,16 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) ->
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
#xmlel{children = SubEls} = SubEl,
- case xml:remove_cdata(SubEls) of
+ case fxml:remove_cdata(SubEls) of
[#xmlel{name = Name, attrs = Attrs, children = Els} | Rest] ->
- Node = xml:get_attr_s(<<"node">>, Attrs),
+ Node = fxml:get_attr_s(<<"node">>, Attrs),
case {IQType, Name} of
{set, <<"create">>} ->
Config = case Rest of
[#xmlel{name = <<"configure">>, children = C}] -> C;
_ -> []
end,
- Type = case xml:get_attr_s(<<"type">>, Attrs) of
+ Type = case fxml:get_attr_s(<<"type">>, Attrs) of
<<>> -> hd(Plugins);
T -> T
end,
@@ -1276,10 +1283,10 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
create_node(Host, ServerHost, Node, From, Type, Access, Config)
end;
{set, <<"publish">>} ->
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = <<"item">>, attrs = ItemAttrs,
children = Payload}] ->
- ItemId = xml:get_attr_s(<<"id">>, ItemAttrs),
+ ItemId = fxml:get_attr_s(<<"id">>, ItemAttrs),
publish_item(Host, ServerHost, Node, From, ItemId, Payload, Access);
[] ->
{error,
@@ -1289,14 +1296,14 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)}
end;
{set, <<"retract">>} ->
- ForceNotify = case xml:get_attr_s(<<"notify">>, Attrs) of
+ ForceNotify = case fxml:get_attr_s(<<"notify">>, Attrs) of
<<"1">> -> true;
<<"true">> -> true;
_ -> false
end,
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = <<"item">>, attrs = ItemAttrs}] ->
- ItemId = xml:get_attr_s(<<"id">>, ItemAttrs),
+ ItemId = fxml:get_attr_s(<<"id">>, ItemAttrs),
delete_item(Host, Node, From, ItemId, ForceNotify);
_ ->
{error,
@@ -1307,37 +1314,37 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
[#xmlel{name = <<"options">>, children = C}] -> C;
_ -> []
end,
- JID = xml:get_attr_s(<<"jid">>, Attrs),
+ JID = fxml:get_attr_s(<<"jid">>, Attrs),
subscribe_node(Host, Node, From, JID, Config);
{set, <<"unsubscribe">>} ->
- JID = xml:get_attr_s(<<"jid">>, Attrs),
- SubId = xml:get_attr_s(<<"subid">>, Attrs),
+ JID = fxml:get_attr_s(<<"jid">>, Attrs),
+ SubId = fxml:get_attr_s(<<"subid">>, Attrs),
unsubscribe_node(Host, Node, From, JID, SubId);
{get, <<"items">>} ->
- MaxItems = xml:get_attr_s(<<"max_items">>, Attrs),
- SubId = xml:get_attr_s(<<"subid">>, Attrs),
+ MaxItems = fxml:get_attr_s(<<"max_items">>, Attrs),
+ SubId = fxml:get_attr_s(<<"subid">>, Attrs),
ItemIds = lists:foldl(fun
(#xmlel{name = <<"item">>, attrs = ItemAttrs}, Acc) ->
- case xml:get_attr_s(<<"id">>, ItemAttrs) of
+ case fxml:get_attr_s(<<"id">>, ItemAttrs) of
<<>> -> Acc;
ItemId -> [ItemId | Acc]
end;
(_, Acc) ->
Acc
end,
- [], xml:remove_cdata(Els)),
+ [], fxml:remove_cdata(Els)),
get_items(Host, Node, From, SubId, MaxItems, ItemIds, jlib:rsm_decode(SubEl));
{get, <<"subscriptions">>} ->
get_subscriptions(Host, Node, From, Plugins);
{get, <<"affiliations">>} ->
get_affiliations(Host, Node, From, Plugins);
{get, <<"options">>} ->
- SubId = xml:get_attr_s(<<"subid">>, Attrs),
- JID = xml:get_attr_s(<<"jid">>, Attrs),
+ SubId = fxml:get_attr_s(<<"subid">>, Attrs),
+ JID = fxml:get_attr_s(<<"jid">>, Attrs),
get_options(Host, Node, JID, SubId, Lang);
{set, <<"options">>} ->
- SubId = xml:get_attr_s(<<"subid">>, Attrs),
- JID = xml:get_attr_s(<<"jid">>, Attrs),
+ SubId = fxml:get_attr_s(<<"subid">>, Attrs),
+ JID = fxml:get_attr_s(<<"jid">>, Attrs),
set_options(Host, Node, JID, SubId, Els);
_ ->
{error, ?ERR_FEATURE_NOT_IMPLEMENTED}
@@ -1362,10 +1369,10 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
).
iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
#xmlel{children = SubEls} = SubEl,
- Action = xml:remove_cdata(SubEls),
+ Action = fxml:remove_cdata(SubEls),
case Action of
[#xmlel{name = Name, attrs = Attrs, children = Els}] ->
- Node = xml:get_attr_s(<<"node">>, Attrs),
+ Node = fxml:get_attr_s(<<"node">>, Attrs),
case {IQType, Name} of
{get, <<"configure">>} ->
get_configure(Host, ServerHost, Node, From, Lang);
@@ -1380,11 +1387,11 @@ iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
{get, <<"subscriptions">>} ->
get_subscriptions(Host, Node, From);
{set, <<"subscriptions">>} ->
- set_subscriptions(Host, Node, From, xml:remove_cdata(Els));
+ set_subscriptions(Host, Node, From, fxml:remove_cdata(Els));
{get, <<"affiliations">>} ->
get_affiliations(Host, Node, From);
{set, <<"affiliations">>} ->
- set_affiliations(Host, Node, From, xml:remove_cdata(Els));
+ set_affiliations(Host, Node, From, fxml:remove_cdata(Els));
_ ->
{error, ?ERR_FEATURE_NOT_IMPLEMENTED}
end;
@@ -1414,13 +1421,14 @@ adhoc_request(Host, _ServerHost, Owner,
send_pending_node_form(Host, Owner, Lang, Plugins);
adhoc_request(Host, _ServerHost, Owner,
#adhoc_request{node = ?NS_PUBSUB_GET_PENDING,
- action = <<"execute">>, xdata = XData},
+ action = <<"execute">>, xdata = XData, lang = Lang},
_Access, _Plugins) ->
ParseOptions = case XData of
#xmlel{name = <<"x">>} = XEl ->
case jlib:parse_xdata_submit(XEl) of
invalid ->
- {error, ?ERR_BAD_REQUEST};
+ Txt = <<"Incorrect data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
XData2 ->
case set_xoption(Host, XData2, []) of
NewOpts when is_list(NewOpts) -> {result, NewOpts};
@@ -1428,8 +1436,8 @@ adhoc_request(Host, _ServerHost, Owner,
end
end;
_ ->
- ?INFO_MSG("Bad XForm: ~p", [XData]),
- {error, ?ERR_BAD_REQUEST}
+ Txt = <<"No data form found">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end,
case ParseOptions of
{result, XForm} ->
@@ -1459,7 +1467,9 @@ send_pending_node_form(Host, Owner, _Lang, Plugins) ->
end,
case lists:filter(Filter, Plugins) of
[] ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED};
+ Err = extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"get-pending">>),
+ {error, Err};
Ps ->
XOpts = [#xmlel{name = <<"option">>, attrs = [],
children = [#xmlel{name = <<"value">>,
@@ -1500,10 +1510,11 @@ send_pending_auth_events(Host, Node, Owner) ->
true ->
case node_call(Host, Type, get_affiliation, [Nidx, Owner]) of
{result, owner} -> node_call(Host, Type, get_node_subscriptions, [Nidx]);
- _ -> {error, ?ERR_FORBIDDEN}
+ _ -> {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)}
end;
false ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
+ {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED,
+ unsupported, <<"get-pending">>)}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
@@ -1597,9 +1608,9 @@ find_authorization_response(Packet) ->
#xmlel{children = Els} = Packet,
XData1 = lists:map(fun
(#xmlel{name = <<"x">>, attrs = XAttrs} = XEl) ->
- case xml:get_attr_s(<<"xmlns">>, XAttrs) of
+ case fxml:get_attr_s(<<"xmlns">>, XAttrs) of
?NS_XDATA ->
- case xml:get_attr_s(<<"type">>, XAttrs) of
+ case fxml:get_attr_s(<<"type">>, XAttrs) of
<<"cancel">> -> none;
_ -> jlib:parse_xdata_submit(XEl)
end;
@@ -1609,7 +1620,7 @@ find_authorization_response(Packet) ->
(_) ->
none
end,
- xml:remove_cdata(Els)),
+ fxml:remove_cdata(Els)),
XData = lists:filter(fun (E) -> E /= none end, XData1),
case XData of
[invalid] ->
@@ -1640,6 +1651,7 @@ send_authorization_approval(Host, JID, SNode, Subscription) ->
ejabberd_router:route(service_jid(Host), JID, Stanza).
handle_authorization_response(Host, From, To, Packet, XFields) ->
+ Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
case {lists:keysearch(<<"pubsub#node">>, 1, XFields),
lists:keysearch(<<"pubsub#subscriber_jid">>, 1, XFields),
lists:keysearch(<<"pubsub#allow">>, 1, XFields)}
@@ -1661,7 +1673,7 @@ handle_authorization_response(Host, From, To, Packet, XFields) ->
{result, Subs} = node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]),
update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs);
false ->
- {error, ?ERR_FORBIDDEN}
+ {error, ?ERRT_FORBIDDEN(Lang, <<"You're not an owner">>)}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
@@ -1676,7 +1688,8 @@ handle_authorization_response(Host, From, To, Packet, XFields) ->
ejabberd_router:route(To, From, Err)
end;
_ ->
- Err = jlib:make_error_reply(Packet, ?ERR_NOT_ACCEPTABLE),
+ Txt = <<"Incorrect data form">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end.
@@ -1687,7 +1700,7 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) ->
end,
Subs),
case Sub of
- [{pending, SubId}] ->
+ [{pending, SubId}|_] ->
NewSub = case Allow of
true -> subscribed;
false -> none
@@ -1696,7 +1709,8 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) ->
send_authorization_approval(Host, Subscriber, Node, NewSub),
{result, ok};
_ ->
- {error, ?ERR_UNEXPECTED_REQUEST}
+ Txt = <<"No pending subscriptions found">>,
+ {error, ?ERRT_UNEXPECTED_REQUEST(?MYLANG, Txt)}
end.
-define(XFIELD(Type, Label, Var, Val),
@@ -1775,6 +1789,20 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) ->
%%<li>nodetree create_node checks if nodeid already exists</li>
%%<li>node plugin create_node just sets default affiliation/subscription</li>
%%</ul>
+-spec(create_node/5 ::
+ (
+ Host :: mod_pubsub:host(),
+ ServerHost :: binary(),
+ Node :: <<>> | mod_pubsub:nodeId(),
+ Owner :: jid(),
+ Type :: binary())
+ -> {result, [xmlel(),...]}
+ %%%
+ | {error, xmlel()}
+ ).
+create_node(Host, ServerHost, Node, Owner, Type) ->
+ create_node(Host, ServerHost, Node, Owner, Type, all, []).
+
-spec(create_node/7 ::
(
Host :: mod_pubsub:host(),
@@ -1788,8 +1816,6 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) ->
%%%
| {error, xmlel()}
).
-create_node(Host, ServerHost, Node, Owner, Type) ->
- create_node(Host, ServerHost, Node, Owner, Type, all, []).
create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) ->
case lists:member(<<"instant-nodes">>, plugin_features(Host, Type)) of
true ->
@@ -1808,13 +1834,14 @@ create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) ->
end;
create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
Type = select_type(ServerHost, Host, Node, GivenType),
- ParseOptions = case xml:remove_cdata(Configuration) of
+ ParseOptions = case fxml:remove_cdata(Configuration) of
[] ->
{result, node_options(Host, Type)};
[#xmlel{name = <<"x">>} = XEl] ->
case jlib:parse_xdata_submit(XEl) of
invalid ->
- {error, ?ERR_BAD_REQUEST};
+ Txt = <<"Incorrect data form">>,
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)};
XData ->
case set_xoption(Host, XData, node_options(Host, Type)) of
NewOpts when is_list(NewOpts) -> {result, NewOpts};
@@ -1823,7 +1850,8 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
end;
_ ->
?INFO_MSG("Node ~p; bad configuration: ~p", [Node, Configuration]),
- {error, ?ERR_BAD_REQUEST}
+ Txt = <<"No data form found">>,
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)}
end,
case ParseOptions of
{result, NodeOptions} ->
@@ -1860,7 +1888,8 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
Error
end;
_ ->
- {error, ?ERR_FORBIDDEN}
+ Txt1 = <<"You're not allowed to create nodes">>,
+ {error, ?ERRT_FORBIDDEN(?MYLANG, Txt1)}
end
end,
Reply = [#xmlel{name = <<"pubsub">>,
@@ -1910,7 +1939,7 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
| {error, xmlel()}
).
delete_node(_Host, <<>>, _Owner) ->
- {error, ?ERR_NOT_ALLOWED};
+ {error, ?ERRT_NOT_ALLOWED(?MYLANG, <<"No node specified">>)};
delete_node(Host, Node, Owner) ->
Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
case node_call(Host, Type, get_affiliation, [Nidx, Owner]) of
@@ -1922,7 +1951,7 @@ delete_node(Host, Node, Owner) ->
Error -> Error
end;
_ ->
- {error, ?ERR_FORBIDDEN}
+ {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)}
end
end,
Reply = [],
@@ -2231,22 +2260,28 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access) ->
{result, Reply};
{result, {_, Result}} ->
{result, Result};
- {error, ?ERR_ITEM_NOT_FOUND} ->
- Type = select_type(ServerHost, Host, Node),
- case lists:member(<<"auto-create">>, plugin_features(Host, Type)) of
+ {error, _} = Error ->
+ case is_item_not_found(Error) of
true ->
- case create_node(Host, ServerHost, Node, Publisher, Type, Access, []) of
- {result,
- [#xmlel{name = <<"pubsub">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
- children = [#xmlel{name = <<"create">>,
- attrs = [{<<"node">>, NewNode}]}]}]} ->
- publish_item(Host, ServerHost, NewNode, Publisher, ItemId, Payload);
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ Type = select_type(ServerHost, Host, Node),
+ case lists:member(<<"auto-create">>, plugin_features(Host, Type)) of
+ true ->
+ case create_node(Host, ServerHost, Node, Publisher, Type, Access, []) of
+ {result,
+ [#xmlel{name = <<"pubsub">>,
+ attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
+ children = [#xmlel{name = <<"create">>,
+ attrs = [{<<"node">>, NewNode}]}]}]} ->
+ publish_item(Host, ServerHost, NewNode, Publisher, ItemId, Payload);
+ _ ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+ end;
+ false ->
+ Txt = <<"Automatic node creation is not enabled">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, Txt)}
end;
false ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ Error
end;
Error ->
Error
@@ -2400,7 +2435,9 @@ get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) ->
end;
true ->
case catch jlib:binary_to_integer(SMaxItems) of
- {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
+ {'EXIT', _} ->
+ Txt = <<"Value of 'max_items' should be integer">>,
+ {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)};
Val -> Val
end
end,
@@ -2490,7 +2527,7 @@ get_last_item(Host, Type, Nidx, LJID, mnesia) ->
{result, {[LastItem|_], _}} -> LastItem;
_ -> undefined
end;
-get_last_item(Host, Type, Nidx, LJID, odbc) ->
+get_last_item(Host, Type, Nidx, LJID, sql) ->
case node_action(Host, Type, get_last_items, [Nidx, LJID, 1]) of
{result, [LastItem]} -> LastItem;
_ -> undefined
@@ -2505,7 +2542,7 @@ get_last_items(Host, Type, Nidx, LJID, Number, mnesia) ->
{result, {Items, _}} -> lists:sublist(Items, Number);
_ -> []
end;
-get_last_items(Host, Type, Nidx, LJID, Number, odbc) ->
+get_last_items(Host, Type, Nidx, LJID, Number, sql) ->
case node_action(Host, Type, get_last_items, [Nidx, LJID, Number]) of
{result, Items} -> Items;
_ -> []
@@ -2623,7 +2660,7 @@ get_affiliations(Host, Node, JID) ->
{error,
extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"modify-affiliations">>)};
Affiliation /= owner ->
- {error, ?ERR_FORBIDDEN};
+ {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)};
true ->
node_call(Host, Type, get_node_affiliations, [Nidx])
end
@@ -2668,8 +2705,8 @@ set_affiliations(Host, Node, From, EntitiesEls) ->
(El, Acc) ->
case El of
#xmlel{name = <<"affiliation">>, attrs = Attrs} ->
- JID = jid:from_string(xml:get_attr_s(<<"jid">>, Attrs)),
- Affiliation = string_to_affiliation(xml:get_attr_s(<<"affiliation">>, Attrs)),
+ JID = jid:from_string(fxml:get_attr_s(<<"jid">>, Attrs)),
+ Affiliation = string_to_affiliation(fxml:get_attr_s(<<"affiliation">>, Attrs)),
if (JID == error) or (Affiliation == false) -> error;
true -> [{jid:tolower(JID), Affiliation} | Acc]
end
@@ -2716,7 +2753,7 @@ set_affiliations(Host, Node, From, EntitiesEls) ->
FilteredEntities),
{result, []};
_ ->
- {error, ?ERR_FORBIDDEN}
+ {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
@@ -2932,7 +2969,7 @@ get_subscriptions(Host, Node, JID) ->
{error,
extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"manage-subscriptions">>)};
Affiliation /= owner ->
- {error, ?ERR_FORBIDDEN};
+ {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)};
true ->
node_call(Host, Type, get_node_subscriptions, [Nidx])
end
@@ -2975,7 +3012,7 @@ get_subscriptions_for_send_last(Host, PType, mnesia, JID, LJID, BJID) ->
|| {Node, Sub, SubId, SubJID} <- Subs,
Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID),
match_option(Node, send_last_published_item, on_sub_and_presence)];
-get_subscriptions_for_send_last(Host, PType, odbc, JID, LJID, BJID) ->
+get_subscriptions_for_send_last(Host, PType, sql, JID, LJID, BJID) ->
case catch node_action(Host, PType,
get_entity_subscriptions_for_send_last,
[Host, JID])
@@ -2998,9 +3035,9 @@ set_subscriptions(Host, Node, From, EntitiesEls) ->
(El, Acc) ->
case El of
#xmlel{name = <<"subscription">>, attrs = Attrs} ->
- JID = jid:from_string(xml:get_attr_s(<<"jid">>, Attrs)),
- Sub = string_to_subscription(xml:get_attr_s(<<"subscription">>, Attrs)),
- SubId = xml:get_attr_s(<<"subid">>, Attrs),
+ JID = jid:from_string(fxml:get_attr_s(<<"jid">>, Attrs)),
+ Sub = string_to_subscription(fxml:get_attr_s(<<"subscription">>, Attrs)),
+ SubId = fxml:get_attr_s(<<"subid">>, Attrs),
if (JID == error) or (Sub == false) -> error;
true -> [{jid:tolower(JID), Sub, SubId} | Acc]
end
@@ -3043,10 +3080,10 @@ set_subscriptions(Host, Node, From, EntitiesEls) ->
[], Entities),
case Result of
[] -> {result, []};
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ [{error, E}|_] -> {error, E}
end;
_ ->
- {error, ?ERR_FORBIDDEN}
+ {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
@@ -3170,17 +3207,15 @@ sub_option_can_deliver(_, _, _) -> true.
presence_can_deliver(_, false) ->
true;
presence_can_deliver({User, Server, Resource}, true) ->
- case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of
+ case ejabberd_sm:get_user_present_resources(User, Server) of
[] ->
false;
Ss ->
lists:foldl(fun
(_, true) ->
true;
- ({session, _, _, _, undefined, _}, _Acc) ->
- false;
- ({session, {_, _, R}, _, _, _Priority, _}, _Acc) ->
- case Resource of
+ ({_, R}, _Acc) ->
+ case Resource of
<<>> -> true;
R -> true;
_ -> false
@@ -3294,9 +3329,14 @@ broadcast_publish_item(Host, Node, Nidx, Type, NodeOptions, ItemId, From, Payloa
true -> Payload;
false -> []
end,
+ Attrs = case get_option(NodeOptions, itemreply, none) of
+ owner -> itemAttr(ItemId); %% owner not supported
+ publisher -> itemAttr(ItemId, {<<"publisher">>, jid:to_string(From)});
+ none -> itemAttr(ItemId)
+ end,
Stanza = event_stanza(
[#xmlel{name = <<"items">>, attrs = nodeAttr(Node),
- children = [#xmlel{name = <<"item">>, attrs = itemAttr(ItemId),
+ children = [#xmlel{name = <<"item">>, attrs = Attrs,
children = Content}]}]),
broadcast_stanza(Host, From, Node, Nidx, Type,
NodeOptions, SubsByDepth, items, Stanza, true),
@@ -3587,7 +3627,7 @@ get_configure(Host, ServerHost, Node, From, Lang) ->
children =
get_configure_xfields(Type, Options, Lang, Groups)}]}]}]};
_ ->
- {error, ?ERR_FORBIDDEN}
+ {error, ?ERRT_FORBIDDEN(Lang, <<"You're not an owner">>)}
end
end,
case transaction(Host, Node, Action, sync_dirty) of
@@ -3626,7 +3666,7 @@ get_option(Options, Var, Def) ->
end.
node_options(Host, Type) ->
- case config(serverhost(Host), default_node_config) of
+ case config(Host, default_node_config) of
undefined -> node_plugin_options(Host, Type);
[] -> node_plugin_options(Host, Type);
Config -> Config
@@ -3648,7 +3688,7 @@ filter_node_options(Options) ->
node_owners_action(Host, Type, Nidx, []) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
- odbc ->
+ sql ->
case node_action(Host, Type, get_node_affiliations, [Nidx]) of
{result, Affs} -> [LJID || {LJID, Aff} <- Affs, Aff =:= owner];
_ -> []
@@ -3661,7 +3701,7 @@ node_owners_action(_Host, _Type, _Nidx, Owners) ->
node_owners_call(Host, Type, Nidx, []) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
- odbc ->
+ sql ->
case node_call(Host, Type, get_node_affiliations, [Nidx]) of
{result, Affs} -> [LJID || {LJID, Aff} <- Affs, Aff =:= owner];
_ -> []
@@ -3777,7 +3817,9 @@ get_configure_xfields(_Type, Options, Lang, Groups) ->
?BOOL_CONFIG_FIELD(<<"Only deliver notifications to available users">>,
presence_based_delivery),
?NLIST_CONFIG_FIELD(<<"The collections with which a node is affiliated">>,
- collection)].
+ collection),
+ ?ALIST_CONFIG_FIELD(<<"Whether owners or publisher should receive replies to items">>,
+ itemreply, [none, owner, publisher])].
%%<p>There are several reasons why the node configuration request might fail:</p>
%%<ul>
@@ -3788,9 +3830,9 @@ get_configure_xfields(_Type, Options, Lang, Groups) ->
%%<li>The specified node does not exist.</li>
%%</ul>
set_configure(Host, Node, From, Els, Lang) ->
- case xml:remove_cdata(Els) of
+ case fxml:remove_cdata(Els) of
[#xmlel{name = <<"x">>} = XEl] ->
- case {xml:get_tag_attr_s(<<"xmlns">>, XEl), xml:get_tag_attr_s(<<"type">>, XEl)} of
+ case {fxml:get_tag_attr_s(<<"xmlns">>, XEl), fxml:get_tag_attr_s(<<"type">>, XEl)} of
{?NS_XDATA, <<"cancel">>} ->
{result, []};
{?NS_XDATA, <<"submit">>} ->
@@ -3799,7 +3841,8 @@ set_configure(Host, Node, From, Els, Lang) ->
{result, owner} ->
case jlib:parse_xdata_submit(XEl) of
invalid ->
- {error, ?ERR_BAD_REQUEST};
+ Txt = <<"Incorrect data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
XData ->
OldOpts = case Options of
[] -> node_options(Host, Type);
@@ -3819,7 +3862,8 @@ set_configure(Host, Node, From, Els, Lang) ->
end
end;
_ ->
- {error, ?ERR_FORBIDDEN}
+ Txt = <<"You're not an owner">>,
+ {error, ?ERRT_FORBIDDEN(Lang, Txt)}
end
end,
case transaction(Host, Node, Action, transaction) of
@@ -3833,10 +3877,12 @@ set_configure(Host, Node, From, Els, Lang) ->
Other
end;
_ ->
- {error, ?ERR_BAD_REQUEST}
+ Txt = <<"Incorrect data form">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end;
_ ->
- {error, ?ERR_BAD_REQUEST}
+ Txt = <<"No data form found">>,
+ {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end.
add_opt(Key, Value, Opts) ->
@@ -3851,7 +3897,10 @@ add_opt(Key, Value, Opts) ->
_ -> error
end,
case BoolVal of
- error -> {error, ?ERR_NOT_ACCEPTABLE};
+ error ->
+ Txt = <<"Value of '~s' should be boolean">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)};
_ -> set_xoption(Host, Opts, add_opt(Opt, BoolVal, NewOpts))
end).
@@ -3864,10 +3913,14 @@ add_opt(Key, Value, Opts) ->
if (Max =:= undefined) orelse (IVal =< Max) ->
set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts));
true ->
- {error, ?ERR_NOT_ACCEPTABLE}
+ Txt = <<"Incorrect value of '~s'">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
end;
_ ->
- {error, ?ERR_NOT_ACCEPTABLE}
+ Txt = <<"Value of '~s' should be integer">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
end).
-define(SET_ALIST_XOPT(Opt, Val, Vals),
@@ -3875,7 +3928,9 @@ add_opt(Key, Value, Opts) ->
true ->
set_xoption(Host, Opts, add_opt(Opt, jlib:binary_to_atom(Val), NewOpts));
false ->
- {error, ?ERR_NOT_ACCEPTABLE}
+ Txt = <<"Incorrect value of '~s'">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
end).
-define(SET_LIST_XOPT(Opt, Val),
@@ -3931,25 +3986,21 @@ set_xoption(Host, [{<<"pubsub#collection">>, Value} | Opts], NewOpts) ->
set_xoption(Host, [{<<"pubsub#node">>, [Value]} | Opts], NewOpts) ->
% NewValue = string_to_node(Value),
?SET_LIST_XOPT(node, Value);
+set_xoption(Host, [{<<"pubsub#itemreply">>, [Val]} | Opts], NewOpts) ->
+ ?SET_ALIST_XOPT(itemreply, Val, [none, owner, publisher]);
set_xoption(Host, [_ | Opts], NewOpts) ->
set_xoption(Host, Opts, NewOpts).
-get_max_items_node({_, ServerHost, _}) ->
- get_max_items_node(ServerHost);
get_max_items_node(Host) ->
- config(serverhost(Host), max_items_node, undefined).
+ config(Host, max_items_node, undefined).
-get_max_subscriptions_node({_, ServerHost, _}) ->
- get_max_subscriptions_node(ServerHost);
get_max_subscriptions_node(Host) ->
- config(serverhost(Host), max_subscriptions_node, undefined).
+ config(Host, max_subscriptions_node, undefined).
%%%% last item cache handling
-is_last_item_cache_enabled({_, ServerHost, _}) ->
- is_last_item_cache_enabled(ServerHost);
is_last_item_cache_enabled(Host) ->
- config(serverhost(Host), last_item_cache, false).
+ config(Host, last_item_cache, false).
set_cached_item({_, ServerHost, _}, Nidx, ItemId, Publisher, Payload) ->
set_cached_item(ServerHost, Nidx, ItemId, Publisher, Payload);
@@ -3999,19 +4050,13 @@ get_cached_item(Host, Nidx) ->
host(ServerHost) ->
config(ServerHost, host, <<"pubsub.", ServerHost/binary>>).
-serverhost({_U, Server, _R})->
- Server;
+serverhost({_U, ServerHost, _R})->
+ serverhost(ServerHost);
serverhost(Host) ->
- case binary:match(Host, <<"pubsub.">>) of
- {0,7} ->
- [_,ServerHost] = binary:split(Host, <<".">>),
- ServerHost;
- _ ->
- Host
- end.
+ ejabberd_router:host_of_route(Host).
tree(Host) ->
- case config(serverhost(Host), nodetree) of
+ case config(Host, nodetree) of
undefined -> tree(Host, ?STDTREE);
Tree -> Tree
end.
@@ -4021,19 +4066,19 @@ tree(_Host, <<"virtual">>) ->
tree(Host, Name) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
mnesia -> jlib:binary_to_atom(<<"nodetree_", Name/binary>>);
- odbc -> jlib:binary_to_atom(<<"nodetree_", Name/binary, "_odbc">>);
+ sql -> jlib:binary_to_atom(<<"nodetree_", Name/binary, "_sql">>);
_ -> Name
end.
plugin(Host, Name) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
mnesia -> jlib:binary_to_atom(<<"node_", Name/binary>>);
- odbc -> jlib:binary_to_atom(<<"node_", Name/binary, "_odbc">>);
+ sql -> jlib:binary_to_atom(<<"node_", Name/binary, "_sql">>);
_ -> Name
end.
plugins(Host) ->
- case config(serverhost(Host), plugins) of
+ case config(Host, plugins) of
undefined -> [?STDNODE];
[] -> [?STDNODE];
Plugins -> Plugins
@@ -4042,12 +4087,15 @@ plugins(Host) ->
subscription_plugin(Host) ->
case gen_mod:db_type(serverhost(Host), ?MODULE) of
mnesia -> pubsub_subscription;
- odbc -> pubsub_subscription_odbc;
+ sql -> pubsub_subscription_sql;
_ -> none
end.
config(ServerHost, Key) ->
config(ServerHost, Key, undefined).
+
+config({_User, Host, _Resource}, Key, Default) ->
+ config(Host, Key, Default);
config(ServerHost, Key, Default) ->
case catch ets:lookup(gen_mod:get_module_proc(ServerHost, config), Key) of
[{Key, Value}] -> Value;
@@ -4064,14 +4112,14 @@ select_type(ServerHost, Host, Node, Type) ->
_ ->
Type
end,
- ConfiguredTypes = plugins(ServerHost),
+ ConfiguredTypes = plugins(Host),
case lists:member(SelectedType, ConfiguredTypes) of
true -> SelectedType;
false -> hd(ConfiguredTypes)
end.
select_type(ServerHost, Host, Node) ->
- select_type(ServerHost, Host, Node, hd(plugins(ServerHost))).
+ select_type(ServerHost, Host, Node, hd(plugins(Host))).
feature(<<"rsm">>) -> ?NS_RSM;
feature(Feature) -> <<(?NS_PUBSUB)/binary, "#", Feature/binary>>.
@@ -4125,8 +4173,9 @@ features(Host, Node) when is_binary(Node) ->
tree_call({_User, Server, _Resource}, Function, Args) ->
tree_call(Server, Function, Args);
tree_call(Host, Function, Args) ->
- ?DEBUG("tree_call ~p ~p ~p", [Host, Function, Args]),
- catch apply(tree(Host), Function, Args).
+ Tree = tree(Host),
+ ?DEBUG("tree_call apply(~s, ~s, ~p) @ ~s", [Tree, Function, Args, Host]),
+ catch apply(Tree, Function, Args).
tree_action(Host, Function, Args) ->
?DEBUG("tree_action ~p ~p ~p", [Host, Function, Args]),
@@ -4135,8 +4184,8 @@ tree_action(Host, Function, Args) ->
case gen_mod:db_type(ServerHost, ?MODULE) of
mnesia ->
catch mnesia:sync_dirty(Fun);
- odbc ->
- case catch ejabberd_odbc:sql_bloc(ServerHost, Fun) of
+ sql ->
+ case catch ejabberd_sql:sql_bloc(ServerHost, Fun) of
{atomic, Result} ->
Result;
{aborted, Reason} ->
@@ -4195,7 +4244,7 @@ transaction(Host, Fun, Trans) ->
ServerHost = serverhost(Host),
DBType = gen_mod:db_type(ServerHost, ?MODULE),
Retry = case DBType of
- odbc -> 2;
+ sql -> 2;
_ -> 1
end,
transaction_retry(Host, ServerHost, Fun, Trans, DBType, Retry).
@@ -4206,12 +4255,12 @@ transaction_retry(Host, ServerHost, Fun, Trans, DBType, Count) ->
Res = case DBType of
mnesia ->
catch mnesia:Trans(Fun);
- odbc ->
+ sql ->
SqlFun = case Trans of
transaction -> sql_transaction;
_ -> sql_bloc
end,
- catch ejabberd_odbc:SqlFun(ServerHost, Fun);
+ catch ejabberd_sql:SqlFun(ServerHost, Fun);
_ ->
{unsupported, DBType}
end,
@@ -4253,6 +4302,13 @@ extended_error(#xmlel{name = Error, attrs = Attrs, children = SubEls}, Ext, ExtA
#xmlel{name = Error, attrs = Attrs,
children = lists:reverse([#xmlel{name = Ext, attrs = ExtAttrs} | SubEls])}.
+is_item_not_found({error, ErrEl}) ->
+ case fxml:get_subtag_with_xmlns(
+ ErrEl, <<"item-not-found">>, ?NS_STANZAS) of
+ #xmlel{} -> true;
+ _ -> false
+ end.
+
string_to_ljid(JID) ->
case jid:from_string(JID) of
error ->
@@ -4273,6 +4329,7 @@ nodeAttr(Node) -> [{<<"node">>, Node}].
itemAttr([]) -> [];
itemAttr(ItemId) -> [{<<"id">>, ItemId}].
+itemAttr(ItemId, From) -> [{<<"id">>, ItemId}, From].
itemsEls(Items) ->
[#xmlel{name = <<"item">>, attrs = itemAttr(ItemId), children = Payload}
diff --git a/src/mod_register.erl b/src/mod_register.erl
index c7bfd963..c1a7cab8 100644
--- a/src/mod_register.erl
+++ b/src/mod_register.erl
@@ -121,9 +121,9 @@ process_iq(From, To,
end,
case Type of
set ->
- UTag = xml:get_subtag(SubEl, <<"username">>),
- PTag = xml:get_subtag(SubEl, <<"password">>),
- RTag = xml:get_subtag(SubEl, <<"remove">>),
+ UTag = fxml:get_subtag(SubEl, <<"username">>),
+ PTag = fxml:get_subtag(SubEl, <<"password">>),
+ RTag = fxml:get_subtag(SubEl, <<"remove">>),
Server = To#jid.lserver,
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
fun(A) when is_atom(A) -> A end,
@@ -132,14 +132,14 @@ process_iq(From, To,
acl:match_rule(Server, Access, From),
if (UTag /= false) and (RTag /= false) and
AllowRemove ->
- User = xml:get_tag_cdata(UTag),
+ User = fxml:get_tag_cdata(UTag),
case From of
#jid{user = User, lserver = Server} ->
ejabberd_auth:remove_user(User, Server),
IQ#iq{type = result, sub_el = []};
_ ->
if PTag /= false ->
- Password = xml:get_tag_cdata(PTag),
+ Password = fxml:get_tag_cdata(PTag),
case ejabberd_auth:remove_user(User, Server,
Password)
of
@@ -151,21 +151,28 @@ process_iq(From, To,
%% modules. lists:foreach can
%% only return ok:
not_allowed ->
+ Txt = <<"Removal is not allowed">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ sub_el = [SubEl,
+ ?ERRT_NOT_ALLOWED(Lang, Txt)]};
not_exists ->
+ Txt = <<"No such user">>,
IQ#iq{type = error,
sub_el =
- [SubEl, ?ERR_ITEM_NOT_FOUND]};
- _ ->
+ [SubEl,
+ ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]};
+ Err ->
+ ?ERROR_MSG("failed to remove user ~s@~s: ~p",
+ [User, Server, Err]),
IQ#iq{type = error,
sub_el =
[SubEl,
?ERR_INTERNAL_SERVER_ERROR]}
end;
true ->
+ Txt = <<"No password in this query">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
end
end;
(UTag == false) and (RTag /= false) and AllowRemove ->
@@ -182,11 +189,13 @@ process_iq(From, To,
ejabberd_auth:remove_user(User, Server),
ignore;
_ ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+ Txt = <<"The query is only allowed from local users">>,
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}
end;
(UTag /= false) and (PTag /= false) ->
- User = xml:get_tag_cdata(UTag),
- Password = xml:get_tag_cdata(PTag),
+ User = fxml:get_tag_cdata(UTag),
+ Password = fxml:get_tag_cdata(PTag),
try_register_or_set_password(User, Server, Password,
From, IQ, SubEl, Source, Lang,
not IsCaptchaEnabled);
@@ -200,11 +209,14 @@ process_iq(From, To,
SubEl, Source, Lang,
true);
_ ->
+ Txt = <<"Incorrect data form">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
end;
{error, malformed} ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
+ Txt = <<"Incorrect CAPTCHA submit">>,
+ IQ#iq{type = error,
+ sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]};
_ ->
ErrText = <<"The CAPTCHA verification has failed">>,
IQ#iq{type = error,
@@ -344,7 +356,8 @@ try_register_or_set_password(User, Server, Password,
IQ#iq{type = error, sub_el = [SubEl, Error]}
end;
deny ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
+ Txt = <<"Denied by ACL">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
end;
_ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
@@ -359,13 +372,17 @@ try_set_password(User, Server, Password, IQ, SubEl,
of
ok -> IQ#iq{type = result, sub_el = []};
{error, empty_password} ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
+ Txt = <<"Empty password">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]};
{error, not_allowed} ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Changing password is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
{error, invalid_jid} ->
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
- _ ->
+ sub_el = [SubEl, ?ERR_JID_MALFORMED]};
+ Err ->
+ ?ERROR_MSG("failed to register user ~s@~s: ~p",
+ [User, Server, Err]),
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
end;
@@ -377,7 +394,7 @@ try_set_password(User, Server, Password, IQ, SubEl,
try_register(User, Server, Password, SourceRaw, Lang) ->
case jid:is_nodename(User) of
- false -> {error, ?ERR_BAD_REQUEST};
+ false -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Malformed username">>)};
_ ->
JID = jid:make(User, Server, <<"">>),
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
@@ -387,8 +404,8 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
case {acl:match_rule(Server, Access, JID),
check_ip_access(SourceRaw, IPAccess)}
of
- {deny, _} -> {error, ?ERR_FORBIDDEN};
- {_, deny} -> {error, ?ERR_FORBIDDEN};
+ {deny, _} -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
+ {_, deny} -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
{allow, allow} ->
Source = may_remove_resource(SourceRaw),
case check_timeout(Source) of
@@ -406,14 +423,20 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
Error ->
remove_timeout(Source),
case Error of
- {atomic, exists} -> {error, ?ERR_CONFLICT};
+ {atomic, exists} ->
+ Txt = <<"User already exists">>,
+ {error, ?ERRT_CONFLICT(Lang, Txt)};
{error, invalid_jid} ->
{error, ?ERR_JID_MALFORMED};
{error, not_allowed} ->
{error, ?ERR_NOT_ALLOWED};
{error, too_many_users} ->
- {error, ?ERR_NOT_ALLOWED};
- {error, _Reason} ->
+ Txt = <<"Too many users registered">>,
+ {error, ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt)};
+ {error, _} ->
+ ?ERROR_MSG("failed to register user "
+ "~s@~s: ~p",
+ [User, Server, Error]),
{error, ?ERR_INTERNAL_SERVER_ERROR}
end
end;
@@ -599,7 +622,7 @@ write_time({{Y, Mo, D}, {H, Mi, S}}) ->
[Y, Mo, D, H, Mi, S]).
process_xdata_submit(El) ->
- case xml:get_subtag(El, <<"x">>) of
+ case fxml:get_subtag(El, <<"x">>) of
false -> error;
Xdata ->
Fields = jlib:parse_xdata_submit(Xdata),
diff --git a/src/mod_register_web.erl b/src/mod_register_web.erl
index 35f52f3b..d9314031 100644
--- a/src/mod_register_web.erl
+++ b/src/mod_register_web.erl
@@ -113,8 +113,8 @@ process([<<"new">>],
end;
process([<<"delete">>],
#request{method = 'POST', q = Q, lang = Lang,
- host = Host}) ->
- case form_del_post(Q, Host) of
+ host = _HTTPHost}) ->
+ case form_del_post(Q) of
{atomic, ok} ->
Text = (?T(<<"Your Jabber account was successfully "
"deleted.">>)),
@@ -129,8 +129,8 @@ process([<<"delete">>],
%% should include the host where the POST was sent.
process([<<"change_password">>],
#request{method = 'POST', q = Q, lang = Lang,
- host = Host}) ->
- case form_changepass_post(Q, Host) of
+ host = _HTTPHost}) ->
+ case form_changepass_post(Q) of
{atomic, ok} ->
Text = (?T(<<"The password of your Jabber account "
"was successfully changed.">>)),
@@ -282,9 +282,9 @@ form_new_post(Q) ->
case catch get_register_parameters(Q) of
[Username, Host, Password, Password, Id, Key] ->
form_new_post(Username, Host, Password, {Id, Key});
- [_Username, _Password, _Password2, false, false] ->
+ [_Username, _Host, _Password, _Password2, false, false] ->
{error, passwords_not_identical};
- [_Username, _Password, _Password2, Id, Key] ->
+ [_Username, _Host, _Password, _Password2, Id, Key] ->
ejabberd_captcha:check_captcha(Id, Key),
{error, passwords_not_identical};
_ -> {error, wrong_parameters}
@@ -361,7 +361,8 @@ form_changepass_get(Host, Lang) ->
?INPUTS(<<"text">>, <<"username">>, <<"">>,
<<"20">>)]),
?XE(<<"li">>,
- [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]),
+ [?CT(<<"Server:">>), ?C(<<" ">>),
+ ?INPUTS(<<"text">>, <<"host">>, Host, <<"20">>)]),
?XE(<<"li">>,
[?CT(<<"Old Password:">>), ?C(<<" ">>),
?INPUTS(<<"password">>, <<"passwordold">>, <<"">>,
@@ -386,12 +387,12 @@ form_changepass_get(Host, Lang) ->
%%% Formulary change password POST
%%%----------------------------------------------------------------------
-form_changepass_post(Q, Host) ->
+form_changepass_post(Q) ->
case catch get_changepass_parameters(Q) of
- [Username, PasswordOld, Password, Password] ->
+ [Username, Host, PasswordOld, Password, Password] ->
try_change_password(Username, Host, PasswordOld,
Password);
- [_Username, _PasswordOld, _Password, _Password2] ->
+ [_Username, _Host, _PasswordOld, _Password, _Password2] ->
{error, passwords_not_identical};
_ -> {error, wrong_parameters}
end.
@@ -405,7 +406,7 @@ get_changepass_parameters(Q) ->
{value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
Value
end,
- [<<"username">>, <<"passwordold">>, <<"password">>,
+ [<<"username">>, <<"host">>, <<"passwordold">>, <<"password">>,
<<"password2">>]).
try_change_password(Username, Host, PasswordOld,
@@ -437,7 +438,7 @@ check_account_exists(Username, Host) ->
end.
check_password(Username, Host, Password) ->
- case ejabberd_auth:check_password(Username, Host,
+ case ejabberd_auth:check_password(Username, <<"">>, Host,
Password)
of
true -> password_correct;
@@ -470,7 +471,8 @@ form_del_get(Host, Lang) ->
?INPUTS(<<"text">>, <<"username">>, <<"">>,
<<"20">>)]),
?XE(<<"li">>,
- [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]),
+ [?CT(<<"Server:">>), ?C(<<" ">>),
+ ?INPUTS(<<"text">>, <<"host">>, Host, <<"20">>)]),
?XE(<<"li">>,
[?CT(<<"Password:">>), ?C(<<" ">>),
?INPUTS(<<"password">>, <<"password">>, <<"">>,
@@ -513,9 +515,9 @@ register_account2(Username, Host, Password) ->
%%% Formulary delete POST
%%%----------------------------------------------------------------------
-form_del_post(Q, Host) ->
+form_del_post(Q) ->
case catch get_unregister_parameters(Q) of
- [Username, Password] ->
+ [Username, Host, Password] ->
try_unregister_account(Username, Host, Password);
_ -> {error, wrong_parameters}
end.
@@ -529,7 +531,7 @@ get_unregister_parameters(Q) ->
{value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
Value
end,
- [<<"username">>, <<"password">>]).
+ [<<"username">>, <<"host">>, <<"password">>]).
try_unregister_account(Username, Host, Password) ->
try unregister_account(Username, Host, Password) of
diff --git a/src/mod_roster.erl b/src/mod_roster.erl
index adc2210d..16354dd8 100644
--- a/src/mod_roster.erl
+++ b/src/mod_roster.erl
@@ -49,8 +49,7 @@
get_jid_info/4, item_to_xml/1, webadmin_page/3,
webadmin_user/4, get_versioning_feature/2,
roster_versioning_enabled/1, roster_version/2,
- record_to_string/1, groups_to_string/1,
- mod_opt_type/1]).
+ mod_opt_type/1, set_roster/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -65,23 +64,27 @@
-export_type([subscription/0]).
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #roster{} | #roster_version{}) -> ok | pass.
+-callback read_roster_version(binary(), binary()) -> binary() | error.
+-callback write_roster_version(binary(), binary(), boolean(), binary()) -> any().
+-callback get_roster(binary(), binary()) -> [#roster{}].
+-callback get_roster_by_jid(binary(), binary(), ljid()) -> #roster{}.
+-callback get_only_items(binary(), binary()) -> [#roster{}].
+-callback roster_subscribe(binary(), binary(), ljid(), #roster{}) -> any().
+-callback transaction(binary(), function()) -> {atomic, any()} | {aborted, any()}.
+-callback get_roster_by_jid_with_groups(binary(), binary(), ljid()) -> #roster{}.
+-callback remove_user(binary(), binary()) -> {atomic, any()}.
+-callback update_roster(binary(), binary(), ljid(), #roster{}) -> any().
+-callback del_roster(binary(), binary(), ljid()) -> any().
+-callback read_subscription_and_groups(binary(), binary(), ljid()) ->
+ {subscription(), [binary()]}.
+
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(roster,
- [{disc_copies, [node()]},
- {attributes, record_info(fields, roster)}]),
- mnesia:create_table(roster_version,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, roster_version)}]),
- update_tables(),
- mnesia:add_table_index(roster, us),
- mnesia:add_table_index(roster_version, us);
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
ejabberd_hooks:add(roster_get, Host, ?MODULE,
get_user_roster, 50),
ejabberd_hooks:add(roster_in_subscription, Host,
@@ -137,13 +140,14 @@ process_iq(From, To, IQ) when ((From#jid.luser == <<"">>) andalso (From#jid.reso
process_iq_manager(From, To, IQ);
process_iq(From, To, IQ) ->
- #iq{sub_el = SubEl} = IQ,
+ #iq{sub_el = SubEl, lang = Lang} = IQ,
#jid{lserver = LServer} = From,
case lists:member(LServer, ?MYHOSTS) of
true -> process_local_iq(From, To, IQ);
_ ->
+ Txt = <<"The query is only allowed from local users">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}
+ sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]}
end.
process_local_iq(From, To, #iq{type = Type} = IQ) ->
@@ -193,28 +197,8 @@ roster_version(LServer, LUser) ->
end.
read_roster_version(LUser, LServer) ->
- read_roster_version(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-read_roster_version(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- case mnesia:dirty_read(roster_version, US) of
- [#roster_version{version = V}] -> V;
- [] -> error
- end;
-read_roster_version(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case odbc_queries:get_roster_version(LServer, Username)
- of
- {selected, [<<"version">>], [[Version]]} -> Version;
- {selected, [<<"version">>], []} -> error
- end;
-read_roster_version(LServer, LUser, riak) ->
- case ejabberd_riak:get(roster_version, roster_version_schema(),
- {LUser, LServer}) of
- {ok, #roster_version{version = V}} -> V;
- _Err -> error
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:read_roster_version(LUser, LServer).
write_roster_version(LUser, LServer) ->
write_roster_version(LUser, LServer, false).
@@ -224,38 +208,10 @@ write_roster_version_t(LUser, LServer) ->
write_roster_version(LUser, LServer, InTransaction) ->
Ver = p1_sha:sha(term_to_binary(p1_time_compat:unique_integer())),
- write_roster_version(LUser, LServer, InTransaction, Ver,
- gen_mod:db_type(LServer, ?MODULE)),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:write_roster_version(LUser, LServer, InTransaction, Ver),
Ver.
-write_roster_version(LUser, LServer, InTransaction, Ver,
- mnesia) ->
- US = {LUser, LServer},
- if InTransaction ->
- mnesia:write(#roster_version{us = US, version = Ver});
- true ->
- mnesia:dirty_write(#roster_version{us = US,
- version = Ver})
- end;
-write_roster_version(LUser, LServer, InTransaction, Ver,
- odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- EVer = ejabberd_odbc:escape(Ver),
- if InTransaction ->
- odbc_queries:set_roster_version(Username, EVer);
- true ->
- odbc_queries:sql_transaction(LServer,
- fun () ->
- odbc_queries:set_roster_version(Username,
- EVer)
- end)
- end;
-write_roster_version(LUser, LServer, _InTransaction, Ver,
- riak) ->
- US = {LUser, LServer},
- ejabberd_riak:put(#roster_version{us = US, version = Ver},
- roster_version_schema()).
-
%% Load roster from DB only if neccesary.
%% It is neccesary if
%% - roster versioning is disabled in server OR
@@ -267,7 +223,7 @@ process_iq_get(From, To, #iq{sub_el = SubEl} = IQ) ->
LServer = From#jid.lserver,
US = {LUser, LServer},
try {ItemsToSend, VersionToSend} = case
- {xml:get_tag_attr(<<"ver">>, SubEl),
+ {fxml:get_tag_attr(<<"ver">>, SubEl),
roster_versioning_enabled(LServer),
roster_version_on_db(LServer)}
of
@@ -351,65 +307,15 @@ get_user_roster(Acc, {LUser, LServer}) ->
++ Acc.
get_roster(LUser, LServer) ->
- get_roster(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_roster(LUser, LServer).
-get_roster(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- case catch mnesia:dirty_index_read(roster, US,
- #roster.us)
- of
- Items when is_list(Items)-> Items;
- _ -> []
- end;
-get_roster(LUser, LServer, riak) ->
- case ejabberd_riak:get_by_index(roster, roster_schema(),
- <<"us">>, {LUser, LServer}) of
- {ok, Items} -> Items;
- _Err -> []
- end;
-get_roster(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_roster(LServer, Username) of
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- Items}
- when is_list(Items) ->
- JIDGroups = case catch
- odbc_queries:get_roster_jid_groups(LServer,
- Username)
- of
- {selected, [<<"jid">>, <<"grp">>], JGrps}
- when is_list(JGrps) ->
- JGrps;
- _ -> []
- end,
- GroupsDict = lists:foldl(fun ([J, G], Acc) ->
- dict:append(J, G, Acc)
- end,
- dict:new(), JIDGroups),
- RItems = lists:flatmap(fun (I) ->
- case raw_to_record(LServer, I) of
- %% Bad JID in database:
- error -> [];
- R ->
- SJID =
- jid:to_string(R#roster.jid),
- Groups = case dict:find(SJID,
- GroupsDict)
- of
- {ok, Gs} -> Gs;
- error -> []
- end,
- [R#roster{groups = Groups}]
- end
- end,
- Items),
- RItems;
- _ -> []
- end.
+set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) ->
+ transaction(
+ LServer,
+ fun() ->
+ roster_subscribe_t(LUser, LServer, LJID, Item)
+ end).
item_to_xml(Item) ->
Attrs1 = [{<<"jid">>,
@@ -440,61 +346,16 @@ item_to_xml(Item) ->
children = SubEls}.
get_roster_by_jid_t(LUser, LServer, LJID) ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- get_roster_by_jid_t(LUser, LServer, LJID, DBType).
-
-get_roster_by_jid_t(LUser, LServer, LJID, mnesia) ->
- case mnesia:read({roster, {LUser, LServer, LJID}}) of
- [] ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID};
- [I] ->
- I#roster{jid = LJID, name = <<"">>, groups = [],
- xs = []}
- end;
-get_roster_by_jid_t(LUser, LServer, LJID, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- Res} =
- odbc_queries:get_roster_by_jid(LServer, Username, SJID),
- case Res of
- [] ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID};
- [I] ->
- R = raw_to_record(LServer, I),
- case R of
- %% Bad JID in database:
- error ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID};
- _ ->
- R#roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID, name = <<"">>}
- end
- end;
-get_roster_by_jid_t(LUser, LServer, LJID, riak) ->
- case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
- {ok, I} ->
- I#roster{jid = LJID, name = <<"">>, groups = [],
- xs = []};
- {error, notfound} ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID};
- Err ->
- exit(Err)
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_roster_by_jid(LUser, LServer, LJID).
-try_process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) ->
+try_process_iq_set(From, To, #iq{sub_el = SubEl, lang = Lang} = IQ) ->
#jid{server = Server} = From,
Access = gen_mod:get_module_opt(Server, ?MODULE, access, fun(A) when is_atom(A) -> A end, all),
case acl:match_rule(Server, Access, From) of
deny ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Denied by ACL">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
allow ->
process_iq_set(From, To, IQ)
end.
@@ -509,7 +370,7 @@ process_iq_set(From, To, #iq{sub_el = SubEl, id = Id} = IQ) ->
process_item_set(From, To,
#xmlel{attrs = Attrs, children = Els}, Managed) ->
- JID1 = jid:from_string(xml:get_attr_s(<<"jid">>,
+ JID1 = jid:from_string(fxml:get_attr_s(<<"jid">>,
Attrs)),
#jid{user = User, luser = LUser, lserver = LServer} =
From,
@@ -578,10 +439,10 @@ process_item_els(Item,
| Els]) ->
case Name of
<<"group">> ->
- Groups = [xml:get_cdata(SEls) | Item#roster.groups],
+ Groups = [fxml:get_cdata(SEls) | Item#roster.groups],
process_item_els(Item#roster{groups = Groups}, Els);
_ ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
<<"">> -> process_item_els(Item, Els);
_ ->
XEls = [#xmlel{name = Name, attrs = Attrs,
@@ -640,86 +501,37 @@ push_item_version(Server, User, From, Item,
end,
ejabberd_sm:get_user_resources(User, Server)).
-get_subscription_lists(Acc, User, Server) ->
+get_subscription_lists(_Acc, User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- DBType = gen_mod:db_type(LServer, ?MODULE),
- Items = get_subscription_lists(Acc, LUser, LServer,
- DBType),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Items = Mod:get_only_items(LUser, LServer),
fill_subscription_lists(LServer, Items, [], []).
-get_subscription_lists(_, LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- case mnesia:dirty_index_read(roster, US, #roster.us) of
- Items when is_list(Items) -> Items;
- _ -> []
- end;
-get_subscription_lists(_, LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_roster(LServer, Username) of
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- Items}
- when is_list(Items) ->
- lists:map(fun(I) -> raw_to_record(LServer, I) end, Items);
- _ -> []
- end;
-get_subscription_lists(_, LUser, LServer, riak) ->
- case ejabberd_riak:get_by_index(roster, roster_schema(),
- <<"us">>, {LUser, LServer}) of
- {ok, Items} -> Items;
- _Err -> []
- end.
-
-fill_subscription_lists(LServer, [#roster{} = I | Is],
- F, T) ->
+fill_subscription_lists(LServer, [I | Is], F, T) ->
J = element(3, I#roster.usj),
case I#roster.subscription of
- both ->
- fill_subscription_lists(LServer, Is, [J | F], [J | T]);
- from ->
- fill_subscription_lists(LServer, Is, [J | F], T);
- to -> fill_subscription_lists(LServer, Is, F, [J | T]);
- _ -> fill_subscription_lists(LServer, Is, F, T)
- end;
-fill_subscription_lists(LServer, [RawI | Is], F, T) ->
- I = raw_to_record(LServer, RawI),
- case I of
- %% Bad JID in database:
- error -> fill_subscription_lists(LServer, Is, F, T);
- _ -> fill_subscription_lists(LServer, [I | Is], F, T)
+ both ->
+ fill_subscription_lists(LServer, Is, [J | F], [J | T]);
+ from ->
+ fill_subscription_lists(LServer, Is, [J | F], T);
+ to -> fill_subscription_lists(LServer, Is, F, [J | T]);
+ _ -> fill_subscription_lists(LServer, Is, F, T)
end;
-fill_subscription_lists(_LServer, [], F, T) -> {F, T}.
+fill_subscription_lists(_LServer, [], F, T) ->
+ {F, T}.
ask_to_pending(subscribe) -> out;
ask_to_pending(unsubscribe) -> none;
ask_to_pending(Ask) -> Ask.
roster_subscribe_t(LUser, LServer, LJID, Item) ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- roster_subscribe_t(LUser, LServer, LJID, Item, DBType).
-
-roster_subscribe_t(_LUser, _LServer, _LJID, Item,
- mnesia) ->
- mnesia:write(Item);
-roster_subscribe_t(LUser, LServer, LJID, Item, odbc) ->
- ItemVals = record_to_string(Item),
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
- odbc_queries:roster_subscribe(LServer, Username, SJID,
- ItemVals);
-roster_subscribe_t(LUser, LServer, _LJID, Item, riak) ->
- ejabberd_riak:put(Item, roster_schema(),
- [{'2i', [{<<"us">>, {LUser, LServer}}]}]).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:roster_subscribe(LUser, LServer, LJID, Item).
transaction(LServer, F) ->
- case gen_mod:db_type(LServer, ?MODULE) of
- mnesia -> mnesia:transaction(F);
- odbc -> ejabberd_odbc:sql_transaction(LServer, F);
- riak -> {atomic, F()}
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:transaction(LServer, F).
in_subscription(_, User, Server, JID, Type, Reason) ->
process_subscription(in, User, Server, JID, Type,
@@ -729,57 +541,8 @@ out_subscription(User, Server, JID, Type) ->
process_subscription(out, User, Server, JID, Type, <<"">>).
get_roster_by_jid_with_groups_t(LUser, LServer, LJID) ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
- DBType).
-
-get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
- mnesia) ->
- case mnesia:read({roster, {LUser, LServer, LJID}}) of
- [] ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID};
- [I] -> I
- end;
-get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
- odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
- case odbc_queries:get_roster_by_jid(LServer, Username,
- SJID)
- of
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- [I]} ->
- R = raw_to_record(LServer, I),
- Groups = case odbc_queries:get_roster_groups(LServer,
- Username, SJID)
- of
- {selected, [<<"grp">>], JGrps} when is_list(JGrps) ->
- [JGrp || [JGrp] <- JGrps];
- _ -> []
- end,
- R#roster{groups = Groups};
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- []} ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID}
- end;
-get_roster_by_jid_with_groups_t(LUser, LServer, LJID, riak) ->
- case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
- {ok, I} ->
- I;
- {error, notfound} ->
- #roster{usj = {LUser, LServer, LJID},
- us = {LUser, LServer}, jid = LJID};
- Err ->
- exit(Err)
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_roster_by_jid_with_groups(LUser, LServer, LJID).
process_subscription(Direction, User, Server, JID1,
Type, Reason) ->
@@ -977,22 +740,8 @@ remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
send_unsubscription_to_rosteritems(LUser, LServer),
- remove_user(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-remove_user(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () ->
- lists:foreach(fun (R) -> mnesia:delete_object(R) end,
- mnesia:index_read(roster, US, #roster.us))
- end,
- mnesia:transaction(F);
-remove_user(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- odbc_queries:del_user_roster_t(LServer, Username),
- ok;
-remove_user(LUser, LServer, riak) ->
- {atomic, ejabberd_riak:delete_by_index(roster, <<"us">>, {LUser, LServer})}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_user(LUser, LServer).
%% For each contact with Subscription:
%% Both or From, send a "unsubscribed" presence stanza;
@@ -1050,39 +799,16 @@ set_items(User, Server, SubEl) ->
transaction(LServer, F).
update_roster_t(LUser, LServer, LJID, Item) ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- update_roster_t(LUser, LServer, LJID, Item, DBType).
-
-update_roster_t(_LUser, _LServer, _LJID, Item,
- mnesia) ->
- mnesia:write(Item);
-update_roster_t(LUser, LServer, LJID, Item, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
- ItemVals = record_to_string(Item),
- ItemGroups = groups_to_string(Item),
- odbc_queries:update_roster(LServer, Username, SJID, ItemVals,
- ItemGroups);
-update_roster_t(LUser, LServer, _LJID, Item, riak) ->
- ejabberd_riak:put(Item, roster_schema(),
- [{'2i', [{<<"us">>, {LUser, LServer}}]}]).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:update_roster(LUser, LServer, LJID, Item).
del_roster_t(LUser, LServer, LJID) ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- del_roster_t(LUser, LServer, LJID, DBType).
-
-del_roster_t(LUser, LServer, LJID, mnesia) ->
- mnesia:delete({roster, {LUser, LServer, LJID}});
-del_roster_t(LUser, LServer, LJID, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
- odbc_queries:del_roster(LServer, Username, SJID);
-del_roster_t(LUser, LServer, LJID, riak) ->
- ejabberd_riak:delete(roster, {LUser, LServer, LJID}).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:del_roster(LUser, LServer, LJID).
process_item_set_t(LUser, LServer,
#xmlel{attrs = Attrs, children = Els}) ->
- JID1 = jid:from_string(xml:get_attr_s(<<"jid">>,
+ JID1 = jid:from_string(fxml:get_attr_s(<<"jid">>,
Attrs)),
case JID1 of
error -> ok;
@@ -1141,13 +867,12 @@ process_item_attrs_ws(Item, []) -> Item.
get_in_pending_subscriptions(Ls, User, Server) ->
LServer = jid:nameprep(Server),
- get_in_pending_subscriptions(Ls, User, Server,
- gen_mod:db_type(LServer, ?MODULE)).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ get_in_pending_subscriptions(Ls, User, Server, Mod).
-get_in_pending_subscriptions(Ls, User, Server, DBType)
- when DBType == mnesia; DBType == riak ->
+get_in_pending_subscriptions(Ls, User, Server, Mod) ->
JID = jid:make(User, Server, <<"">>),
- Result = get_roster(JID#jid.luser, JID#jid.lserver, DBType),
+ Result = Mod:get_only_items(JID#jid.luser, JID#jid.lserver),
Ls ++ lists:map(fun (R) ->
Message = R#roster.askmessage,
Status = if is_binary(Message) -> (Message);
@@ -1172,102 +897,15 @@ get_in_pending_subscriptions(Ls, User, Server, DBType)
_ -> false
end
end,
- Result));
-get_in_pending_subscriptions(Ls, User, Server, odbc) ->
- JID = jid:make(User, Server, <<"">>),
- LUser = JID#jid.luser,
- LServer = JID#jid.lserver,
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_roster(LServer, Username) of
- {selected,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- Items}
- when is_list(Items) ->
- Ls ++
- lists:map(fun (R) ->
- Message = R#roster.askmessage,
- #xmlel{name = <<"presence">>,
- attrs =
- [{<<"from">>,
- jid:to_string(R#roster.jid)},
- {<<"to">>, jid:to_string(JID)},
- {<<"type">>, <<"subscribe">>}],
- children =
- [#xmlel{name = <<"status">>,
- attrs = [],
- children =
- [{xmlcdata, Message}]}]}
- end,
- lists:flatmap(fun (I) ->
- case raw_to_record(LServer, I) of
- %% Bad JID in database:
- error -> [];
- R ->
- case R#roster.ask of
- in -> [R];
- both -> [R];
- _ -> []
- end
- end
- end,
- Items));
- _ -> Ls
- end.
+ Result)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
read_subscription_and_groups(User, Server, LJID) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- read_subscription_and_groups(LUser, LServer, LJID,
- gen_mod:db_type(LServer, ?MODULE)).
-
-read_subscription_and_groups(LUser, LServer, LJID,
- mnesia) ->
- case catch mnesia:dirty_read(roster,
- {LUser, LServer, LJID})
- of
- [#roster{subscription = Subscription,
- groups = Groups}] ->
- {Subscription, Groups};
- _ -> error
- end;
-read_subscription_and_groups(LUser, LServer, LJID,
- odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
- case catch odbc_queries:get_subscription(LServer,
- Username, SJID)
- of
- {selected, [<<"subscription">>], [[SSubscription]]} ->
- Subscription = case SSubscription of
- <<"B">> -> both;
- <<"T">> -> to;
- <<"F">> -> from;
- _ -> none
- end,
- Groups = case catch
- odbc_queries:get_rostergroup_by_jid(LServer, Username,
- SJID)
- of
- {selected, [<<"grp">>], JGrps} when is_list(JGrps) ->
- [JGrp || [JGrp] <- JGrps];
- _ -> []
- end,
- {Subscription, Groups};
- _ -> error
- end;
-read_subscription_and_groups(LUser, LServer, LJID,
- riak) ->
- case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
- {ok, #roster{subscription = Subscription,
- groups = Groups}} ->
- {Subscription, Groups};
- _ ->
- error
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:read_subscription_and_groups(LUser, LServer, LJID).
get_jid_info(_, User, Server, JID) ->
LJID = jid:tolower(JID),
@@ -1287,128 +925,6 @@ get_jid_info(_, User, Server, JID) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-raw_to_record(LServer,
- [User, SJID, Nick, SSubscription, SAsk, SAskMessage,
- _SServer, _SSubscribe, _SType]) ->
- case jid:from_string(SJID) of
- error -> error;
- JID ->
- LJID = jid:tolower(JID),
- Subscription = case SSubscription of
- <<"B">> -> both;
- <<"T">> -> to;
- <<"F">> -> from;
- _ -> none
- end,
- Ask = case SAsk of
- <<"S">> -> subscribe;
- <<"U">> -> unsubscribe;
- <<"B">> -> both;
- <<"O">> -> out;
- <<"I">> -> in;
- _ -> none
- end,
- #roster{usj = {User, LServer, LJID},
- us = {User, LServer}, jid = LJID, name = Nick,
- subscription = Subscription, ask = Ask,
- askmessage = SAskMessage}
- end.
-
-record_to_string(#roster{us = {User, _Server},
- jid = JID, name = Name, subscription = Subscription,
- ask = Ask, askmessage = AskMessage}) ->
- Username = ejabberd_odbc:escape(User),
- SJID =
- ejabberd_odbc:escape(jid:to_string(jid:tolower(JID))),
- Nick = ejabberd_odbc:escape(Name),
- SSubscription = case Subscription of
- both -> <<"B">>;
- to -> <<"T">>;
- from -> <<"F">>;
- none -> <<"N">>
- end,
- SAsk = case Ask of
- subscribe -> <<"S">>;
- unsubscribe -> <<"U">>;
- both -> <<"B">>;
- out -> <<"O">>;
- in -> <<"I">>;
- none -> <<"N">>
- end,
- SAskMessage = ejabberd_odbc:escape(AskMessage),
- [Username, SJID, Nick, SSubscription, SAsk, SAskMessage,
- <<"N">>, <<"">>, <<"item">>].
-
-groups_to_string(#roster{us = {User, _Server},
- jid = JID, groups = Groups}) ->
- Username = ejabberd_odbc:escape(User),
- SJID =
- ejabberd_odbc:escape(jid:to_string(jid:tolower(JID))),
- lists:foldl(fun (<<"">>, Acc) -> Acc;
- (Group, Acc) ->
- G = ejabberd_odbc:escape(Group),
- [[Username, SJID, G] | Acc]
- end,
- [], Groups).
-
-update_tables() ->
- update_roster_table(),
- update_roster_version_table().
-
-update_roster_table() ->
- Fields = record_info(fields, roster),
- case mnesia:table_info(roster, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- roster, Fields, set,
- fun(#roster{usj = {U, _, _}}) -> U end,
- fun(#roster{usj = {U, S, {LU, LS, LR}},
- us = {U1, S1},
- jid = {U2, S2, R2},
- name = Name,
- groups = Gs,
- askmessage = Ask,
- xs = Xs} = R) ->
- R#roster{usj = {iolist_to_binary(U),
- iolist_to_binary(S),
- {iolist_to_binary(LU),
- iolist_to_binary(LS),
- iolist_to_binary(LR)}},
- us = {iolist_to_binary(U1),
- iolist_to_binary(S1)},
- jid = {iolist_to_binary(U2),
- iolist_to_binary(S2),
- iolist_to_binary(R2)},
- name = iolist_to_binary(Name),
- groups = [iolist_to_binary(G) || G <- Gs],
- askmessage = try iolist_to_binary(Ask)
- catch _:_ -> <<"">> end,
- xs = [xml:to_xmlel(X) || X <- Xs]}
- end);
- _ ->
- ?INFO_MSG("Recreating roster table", []),
- mnesia:transform_table(roster, ignore, Fields)
- end.
-
-%% Convert roster table to support virtual host
-%% Convert roster table: xattrs fields become
-update_roster_version_table() ->
- Fields = record_info(fields, roster_version),
- case mnesia:table_info(roster_version, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- roster_version, Fields, set,
- fun(#roster_version{us = {U, _}}) -> U end,
- fun(#roster_version{us = {U, S}, version = Ver} = R) ->
- R#roster_version{us = {iolist_to_binary(U),
- iolist_to_binary(S)},
- version = iolist_to_binary(Ver)}
- end);
- _ ->
- ?INFO_MSG("Recreating roster_version table", []),
- mnesia:transform_table(roster_version, ignore, Fields)
- end.
-
webadmin_page(_, Host,
#request{us = _US, path = [<<"user">>, U, <<"roster">>],
q = Query, lang = Lang} =
@@ -1632,8 +1148,9 @@ process_iq_manager(From, To, IQ) ->
true ->
process_iq_manager2(MatchDomain, To, IQ);
false ->
- #iq{sub_el = SubEl} = IQ,
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+ #iq{sub_el = SubEl, lang = Lang} = IQ,
+ Txt = <<"Roster management is not allowed from this domain">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
end.
process_iq_manager2(MatchDomain, To, IQ) ->
@@ -1705,68 +1222,17 @@ is_managed_from_id(<<"roster-remotely-managed">>) ->
is_managed_from_id(_Id) ->
false.
-roster_schema() ->
- {record_info(fields, roster), #roster{}}.
-
-roster_version_schema() ->
- {record_info(fields, roster_version), #roster_version{}}.
-
-export(_Server) ->
- [{roster,
- fun(Host, #roster{usj = {LUser, LServer, LJID}} = R)
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
- ItemVals = record_to_string(R),
- ItemGroups = groups_to_string(R),
- odbc_queries:update_roster_sql(Username, SJID,
- ItemVals, ItemGroups);
- (_Host, _R) ->
- []
- end},
- {roster_version,
- fun(Host, #roster_version{us = {LUser, LServer}, version = Ver})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- SVer = ejabberd_odbc:escape(Ver),
- [[<<"delete from roster_version where username='">>,
- Username, <<"';">>],
- [<<"insert into roster_version(username, version) values('">>,
- Username, <<"', '">>, SVer, <<"');">>]];
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import(LServer) ->
- [{<<"select username, jid, nick, subscription, "
- "ask, askmessage, server, subscribe, type from rosterusers;">>,
- fun([LUser, JID|_] = Row) ->
- Item = raw_to_record(LServer, Row),
- Username = ejabberd_odbc:escape(LUser),
- SJID = ejabberd_odbc:escape(JID),
- {selected, _, Rows} =
- ejabberd_odbc:sql_query_t(
- [<<"select grp from rostergroups where username='">>,
- Username, <<"' and jid='">>, SJID, <<"'">>]),
- Groups = [Grp || [Grp] <- Rows],
- Item#roster{groups = Groups}
- end},
- {<<"select username, version from roster_version;">>,
- fun([LUser, Ver]) ->
- #roster_version{us = {LUser, LServer}, version = Ver}
- end}].
-
-import(_LServer, mnesia, #roster{} = R) ->
- mnesia:dirty_write(R);
-import(_LServer, mnesia, #roster_version{} = RV) ->
- mnesia:dirty_write(RV);
-import(_LServer, riak, #roster{us = {LUser, LServer}} = R) ->
- ejabberd_riak:put(R, roster_schema(),
- [{'2i', [{<<"us">>, {LUser, LServer}}]}]);
-import(_LServer, riak, #roster_version{} = RV) ->
- ejabberd_riak:put(RV, roster_version_schema());
-import(_, _, _) ->
- pass.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
+
+import(LServer, DBType, R) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, R).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
diff --git a/src/mod_roster_mnesia.erl b/src/mod_roster_mnesia.erl
new file mode 100644
index 00000000..ddfa34d6
--- /dev/null
+++ b/src/mod_roster_mnesia.erl
@@ -0,0 +1,171 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_roster_mnesia).
+
+-behaviour(mod_roster).
+
+%% API
+-export([init/2, read_roster_version/2, write_roster_version/4,
+ get_roster/2, get_roster_by_jid/3, get_only_items/2,
+ roster_subscribe/4, get_roster_by_jid_with_groups/3,
+ remove_user/2, update_roster/4, del_roster/3, transaction/2,
+ read_subscription_and_groups/3, import/2]).
+
+-include("jlib.hrl").
+-include("mod_roster.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(roster,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, roster)}]),
+ mnesia:create_table(roster_version,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, roster_version)}]),
+ update_tables(),
+ mnesia:add_table_index(roster, us),
+ mnesia:add_table_index(roster_version, us).
+
+read_roster_version(LUser, LServer) ->
+ US = {LUser, LServer},
+ case mnesia:dirty_read(roster_version, US) of
+ [#roster_version{version = V}] -> V;
+ [] -> error
+ end.
+
+write_roster_version(LUser, LServer, InTransaction, Ver) ->
+ US = {LUser, LServer},
+ if InTransaction ->
+ mnesia:write(#roster_version{us = US, version = Ver});
+ true ->
+ mnesia:dirty_write(#roster_version{us = US, version = Ver})
+ end.
+
+get_roster(LUser, LServer) ->
+ mnesia:dirty_index_read(roster, {LUser, LServer}, #roster.us).
+
+get_roster_by_jid(LUser, LServer, LJID) ->
+ case mnesia:read({roster, {LUser, LServer, LJID}}) of
+ [] ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ [I] ->
+ I#roster{jid = LJID, name = <<"">>, groups = [],
+ xs = []}
+ end.
+
+get_only_items(LUser, LServer) ->
+ get_roster(LUser, LServer).
+
+roster_subscribe(_LUser, _LServer, _LJID, Item) ->
+ mnesia:write(Item).
+
+get_roster_by_jid_with_groups(LUser, LServer, LJID) ->
+ case mnesia:read({roster, {LUser, LServer, LJID}}) of
+ [] ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ [I] -> I
+ end.
+
+remove_user(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ lists:foreach(
+ fun (R) -> mnesia:delete_object(R) end,
+ mnesia:index_read(roster, US, #roster.us))
+ end,
+ mnesia:transaction(F).
+
+update_roster(_LUser, _LServer, _LJID, Item) ->
+ mnesia:write(Item).
+
+del_roster(LUser, LServer, LJID) ->
+ mnesia:delete({roster, {LUser, LServer, LJID}}).
+
+read_subscription_and_groups(LUser, LServer, LJID) ->
+ case mnesia:dirty_read(roster, {LUser, LServer, LJID}) of
+ [#roster{subscription = Subscription, groups = Groups}] ->
+ {Subscription, Groups};
+ _ ->
+ error
+ end.
+
+transaction(_LServer, F) ->
+ mnesia:transaction(F).
+
+import(_LServer, #roster{} = R) ->
+ mnesia:dirty_write(R);
+import(_LServer, #roster_version{} = RV) ->
+ mnesia:dirty_write(RV).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_tables() ->
+ update_roster_table(),
+ update_roster_version_table().
+
+update_roster_table() ->
+ Fields = record_info(fields, roster),
+ case mnesia:table_info(roster, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ roster, Fields, set,
+ fun(#roster{usj = {U, _, _}}) -> U end,
+ fun(#roster{usj = {U, S, {LU, LS, LR}},
+ us = {U1, S1},
+ jid = {U2, S2, R2},
+ name = Name,
+ groups = Gs,
+ askmessage = Ask,
+ xs = Xs} = R) ->
+ R#roster{usj = {iolist_to_binary(U),
+ iolist_to_binary(S),
+ {iolist_to_binary(LU),
+ iolist_to_binary(LS),
+ iolist_to_binary(LR)}},
+ us = {iolist_to_binary(U1),
+ iolist_to_binary(S1)},
+ jid = {iolist_to_binary(U2),
+ iolist_to_binary(S2),
+ iolist_to_binary(R2)},
+ name = iolist_to_binary(Name),
+ groups = [iolist_to_binary(G) || G <- Gs],
+ askmessage = try iolist_to_binary(Ask)
+ catch _:_ -> <<"">> end,
+ xs = [fxml:to_xmlel(X) || X <- Xs]}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating roster table", []),
+ mnesia:transform_table(roster, ignore, Fields)
+ end.
+
+%% Convert roster table to support virtual host
+%% Convert roster table: xattrs fields become
+update_roster_version_table() ->
+ Fields = record_info(fields, roster_version),
+ case mnesia:table_info(roster_version, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ roster_version, Fields, set,
+ fun(#roster_version{us = {U, _}}) -> U end,
+ fun(#roster_version{us = {U, S}, version = Ver} = R) ->
+ R#roster_version{us = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ version = iolist_to_binary(Ver)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating roster_version table", []),
+ mnesia:transform_table(roster_version, ignore, Fields)
+ end.
diff --git a/src/mod_roster_riak.erl b/src/mod_roster_riak.erl
new file mode 100644
index 00000000..38e87382
--- /dev/null
+++ b/src/mod_roster_riak.erl
@@ -0,0 +1,113 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_roster_riak).
+
+
+-behaviour(mod_roster).
+
+%% API
+-export([init/2, read_roster_version/2, write_roster_version/4,
+ get_roster/2, get_roster_by_jid/3,
+ roster_subscribe/4, get_roster_by_jid_with_groups/3,
+ remove_user/2, update_roster/4, del_roster/3, transaction/2,
+ read_subscription_and_groups/3, get_only_items/2, import/2]).
+
+-include("jlib.hrl").
+-include("mod_roster.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+read_roster_version(LUser, LServer) ->
+ case ejabberd_riak:get(roster_version, roster_version_schema(),
+ {LUser, LServer}) of
+ {ok, #roster_version{version = V}} -> V;
+ _Err -> error
+ end.
+
+write_roster_version(LUser, LServer, _InTransaction, Ver) ->
+ US = {LUser, LServer},
+ ejabberd_riak:put(#roster_version{us = US, version = Ver},
+ roster_version_schema()).
+
+get_roster(LUser, LServer) ->
+ case ejabberd_riak:get_by_index(roster, roster_schema(),
+ <<"us">>, {LUser, LServer}) of
+ {ok, Items} -> Items;
+ _Err -> []
+ end.
+
+get_roster_by_jid(LUser, LServer, LJID) ->
+ case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
+ {ok, I} ->
+ I#roster{jid = LJID, name = <<"">>, groups = [], xs = []};
+ {error, notfound} ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ Err ->
+ exit(Err)
+ end.
+
+get_only_items(LUser, LServer) ->
+ get_roster(LUser, LServer).
+
+roster_subscribe(LUser, LServer, _LJID, Item) ->
+ ejabberd_riak:put(Item, roster_schema(),
+ [{'2i', [{<<"us">>, {LUser, LServer}}]}]).
+
+transaction(_LServer, F) ->
+ {atomic, F()}.
+
+get_roster_by_jid_with_groups(LUser, LServer, LJID) ->
+ case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
+ {ok, I} ->
+ I;
+ {error, notfound} ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ Err ->
+ exit(Err)
+ end.
+
+remove_user(LUser, LServer) ->
+ {atomic, ejabberd_riak:delete_by_index(roster, <<"us">>, {LUser, LServer})}.
+
+update_roster(LUser, LServer, _LJID, Item) ->
+ ejabberd_riak:put(Item, roster_schema(),
+ [{'2i', [{<<"us">>, {LUser, LServer}}]}]).
+
+del_roster(LUser, LServer, LJID) ->
+ ejabberd_riak:delete(roster, {LUser, LServer, LJID}).
+
+read_subscription_and_groups(LUser, LServer, LJID) ->
+ case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
+ {ok, #roster{subscription = Subscription,
+ groups = Groups}} ->
+ {Subscription, Groups};
+ _ ->
+ error
+ end.
+
+import(_LServer, #roster{us = {LUser, LServer}} = R) ->
+ ejabberd_riak:put(R, roster_schema(),
+ [{'2i', [{<<"us">>, {LUser, LServer}}]}]);
+import(_LServer, #roster_version{} = RV) ->
+ ejabberd_riak:put(RV, roster_version_schema()).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+roster_schema() ->
+ {record_info(fields, roster), #roster{}}.
+
+roster_version_schema() ->
+ {record_info(fields, roster_version), #roster_version{}}.
diff --git a/src/mod_roster_sql.erl b/src/mod_roster_sql.erl
new file mode 100644
index 00000000..616d0ec6
--- /dev/null
+++ b/src/mod_roster_sql.erl
@@ -0,0 +1,308 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_roster_sql).
+
+-behaviour(mod_roster).
+
+%% API
+-export([init/2, read_roster_version/2, write_roster_version/4,
+ get_roster/2, get_roster_by_jid/3,
+ roster_subscribe/4, get_roster_by_jid_with_groups/3,
+ remove_user/2, update_roster/4, del_roster/3, transaction/2,
+ read_subscription_and_groups/3, get_only_items/2,
+ import/1, import/2, export/1]).
+
+-include("jlib.hrl").
+-include("mod_roster.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+read_roster_version(LUser, LServer) ->
+ case sql_queries:get_roster_version(LServer, LUser) of
+ {selected, [{Version}]} -> Version;
+ {selected, []} -> error
+ end.
+
+write_roster_version(LUser, LServer, InTransaction, Ver) ->
+ Username = ejabberd_sql:escape(LUser),
+ EVer = ejabberd_sql:escape(Ver),
+ if InTransaction ->
+ sql_queries:set_roster_version(Username, EVer);
+ true ->
+ sql_queries:sql_transaction(
+ LServer,
+ fun () ->
+ sql_queries:set_roster_version(Username, EVer)
+ end)
+ end.
+
+get_roster(LUser, LServer) ->
+ case catch sql_queries:get_roster(LServer, LUser) of
+ {selected, Items} when is_list(Items) ->
+ JIDGroups = case catch sql_queries:get_roster_jid_groups(
+ LServer, LUser) of
+ {selected, JGrps} when is_list(JGrps) ->
+ JGrps;
+ _ ->
+ []
+ end,
+ GroupsDict = lists:foldl(fun({J, G}, Acc) ->
+ dict:append(J, G, Acc)
+ end,
+ dict:new(), JIDGroups),
+ lists:flatmap(
+ fun(I) ->
+ case raw_to_record(LServer, I) of
+ %% Bad JID in database:
+ error -> [];
+ R ->
+ SJID = jid:to_string(R#roster.jid),
+ Groups = case dict:find(SJID, GroupsDict) of
+ {ok, Gs} -> Gs;
+ error -> []
+ end,
+ [R#roster{groups = Groups}]
+ end
+ end, Items);
+ _ ->
+ []
+ end.
+
+get_roster_by_jid(LUser, LServer, LJID) ->
+ {selected, Res} =
+ sql_queries:get_roster_by_jid(LServer, LUser, jid:to_string(LJID)),
+ case Res of
+ [] ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ [I] ->
+ R = raw_to_record(LServer, I),
+ case R of
+ %% Bad JID in database:
+ error ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID};
+ _ ->
+ R#roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID, name = <<"">>}
+ end
+ end.
+
+get_only_items(LUser, LServer) ->
+ case catch sql_queries:get_roster(LServer, LUser) of
+ {selected, Is} when is_list(Is) ->
+ lists:map(fun(I) -> raw_to_record(LServer, I) end, Is);
+ _ -> []
+ end.
+
+roster_subscribe(_LUser, _LServer, _LJID, Item) ->
+ ItemVals = record_to_row(Item),
+ sql_queries:roster_subscribe(ItemVals).
+
+transaction(LServer, F) ->
+ ejabberd_sql:sql_transaction(LServer, F).
+
+get_roster_by_jid_with_groups(LUser, LServer, LJID) ->
+ SJID = jid:to_string(LJID),
+ case sql_queries:get_roster_by_jid(LServer, LUser, SJID) of
+ {selected, [I]} ->
+ R = raw_to_record(LServer, I),
+ Groups =
+ case sql_queries:get_roster_groups(LServer, LUser, SJID) of
+ {selected, JGrps} when is_list(JGrps) ->
+ [JGrp || {JGrp} <- JGrps];
+ _ -> []
+ end,
+ R#roster{groups = Groups};
+ {selected, []} ->
+ #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer}, jid = LJID}
+ end.
+
+remove_user(LUser, LServer) ->
+ sql_queries:del_user_roster_t(LServer, LUser),
+ {atomic, ok}.
+
+update_roster(LUser, LServer, LJID, Item) ->
+ SJID = jid:to_string(LJID),
+ ItemVals = record_to_row(Item),
+ ItemGroups = Item#roster.groups,
+ sql_queries:update_roster(LServer, LUser, SJID, ItemVals,
+ ItemGroups).
+
+del_roster(LUser, LServer, LJID) ->
+ SJID = jid:to_string(LJID),
+ sql_queries:del_roster(LServer, LUser, SJID).
+
+read_subscription_and_groups(LUser, LServer, LJID) ->
+ SJID = jid:to_string(LJID),
+ case catch sql_queries:get_subscription(LServer, LUser, SJID) of
+ {selected, [{SSubscription}]} ->
+ Subscription = case SSubscription of
+ <<"B">> -> both;
+ <<"T">> -> to;
+ <<"F">> -> from;
+ _ -> none
+ end,
+ Groups = case catch sql_queries:get_rostergroup_by_jid(
+ LServer, LUser, SJID) of
+ {selected, JGrps} when is_list(JGrps) ->
+ [JGrp || {JGrp} <- JGrps];
+ _ -> []
+ end,
+ {Subscription, Groups};
+ _ ->
+ error
+ end.
+
+export(_Server) ->
+ [{roster,
+ fun(Host, #roster{usj = {LUser, LServer, LJID}} = R)
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ SJID = ejabberd_sql:escape(jid:to_string(LJID)),
+ ItemVals = record_to_string(R),
+ ItemGroups = groups_to_string(R),
+ sql_queries:update_roster_sql(Username, SJID,
+ ItemVals, ItemGroups);
+ (_Host, _R) ->
+ []
+ end},
+ {roster_version,
+ fun(Host, #roster_version{us = {LUser, LServer}, version = Ver})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ SVer = ejabberd_sql:escape(Ver),
+ [[<<"delete from roster_version where username='">>,
+ Username, <<"';">>],
+ [<<"insert into roster_version(username, version) values('">>,
+ Username, <<"', '">>, SVer, <<"');">>]];
+ (_Host, _R) ->
+ []
+ end}].
+
+import(LServer) ->
+ [{<<"select username, jid, nick, subscription, "
+ "ask, askmessage, server, subscribe, type from rosterusers;">>,
+ fun([LUser, JID|_] = Row) ->
+ Item = raw_to_record(LServer, Row),
+ Username = ejabberd_sql:escape(LUser),
+ SJID = ejabberd_sql:escape(JID),
+ {selected, _, Rows} =
+ ejabberd_sql:sql_query_t(
+ [<<"select grp from rostergroups where username='">>,
+ Username, <<"' and jid='">>, SJID, <<"'">>]),
+ Groups = [Grp || [Grp] <- Rows],
+ Item#roster{groups = Groups}
+ end},
+ {<<"select username, version from roster_version;">>,
+ fun([LUser, Ver]) ->
+ #roster_version{us = {LUser, LServer}, version = Ver}
+ end}].
+
+import(_, _) ->
+ pass.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+raw_to_record(LServer,
+ [User, SJID, Nick, SSubscription, SAsk, SAskMessage,
+ _SServer, _SSubscribe, _SType]) ->
+ raw_to_record(LServer,
+ {User, SJID, Nick, SSubscription, SAsk, SAskMessage,
+ _SServer, _SSubscribe, _SType});
+raw_to_record(LServer,
+ {User, SJID, Nick, SSubscription, SAsk, SAskMessage,
+ _SServer, _SSubscribe, _SType}) ->
+ case jid:from_string(SJID) of
+ error -> error;
+ JID ->
+ LJID = jid:tolower(JID),
+ Subscription = case SSubscription of
+ <<"B">> -> both;
+ <<"T">> -> to;
+ <<"F">> -> from;
+ _ -> none
+ end,
+ Ask = case SAsk of
+ <<"S">> -> subscribe;
+ <<"U">> -> unsubscribe;
+ <<"B">> -> both;
+ <<"O">> -> out;
+ <<"I">> -> in;
+ _ -> none
+ end,
+ #roster{usj = {User, LServer, LJID},
+ us = {User, LServer}, jid = LJID, name = Nick,
+ subscription = Subscription, ask = Ask,
+ askmessage = SAskMessage}
+ end.
+
+record_to_string(#roster{us = {User, _Server},
+ jid = JID, name = Name, subscription = Subscription,
+ ask = Ask, askmessage = AskMessage}) ->
+ Username = ejabberd_sql:escape(User),
+ SJID =
+ ejabberd_sql:escape(jid:to_string(jid:tolower(JID))),
+ Nick = ejabberd_sql:escape(Name),
+ SSubscription = case Subscription of
+ both -> <<"B">>;
+ to -> <<"T">>;
+ from -> <<"F">>;
+ none -> <<"N">>
+ end,
+ SAsk = case Ask of
+ subscribe -> <<"S">>;
+ unsubscribe -> <<"U">>;
+ both -> <<"B">>;
+ out -> <<"O">>;
+ in -> <<"I">>;
+ none -> <<"N">>
+ end,
+ SAskMessage = ejabberd_sql:escape(AskMessage),
+ [Username, SJID, Nick, SSubscription, SAsk, SAskMessage,
+ <<"N">>, <<"">>, <<"item">>].
+
+record_to_row(
+ #roster{us = {LUser, _LServer},
+ jid = JID, name = Name, subscription = Subscription,
+ ask = Ask, askmessage = AskMessage}) ->
+ SJID = jid:to_string(jid:tolower(JID)),
+ SSubscription = case Subscription of
+ both -> <<"B">>;
+ to -> <<"T">>;
+ from -> <<"F">>;
+ none -> <<"N">>
+ end,
+ SAsk = case Ask of
+ subscribe -> <<"S">>;
+ unsubscribe -> <<"U">>;
+ both -> <<"B">>;
+ out -> <<"O">>;
+ in -> <<"I">>;
+ none -> <<"N">>
+ end,
+ {LUser, SJID, Name, SSubscription, SAsk, AskMessage}.
+
+groups_to_string(#roster{us = {User, _Server},
+ jid = JID, groups = Groups}) ->
+ Username = ejabberd_sql:escape(User),
+ SJID =
+ ejabberd_sql:escape(jid:to_string(jid:tolower(JID))),
+ lists:foldl(fun (<<"">>, Acc) -> Acc;
+ (Group, Acc) ->
+ G = ejabberd_sql:escape(Group),
+ [[Username, SJID, G] | Acc]
+ end,
+ [], Groups).
diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl
index cde0f66b..6670cf77 100644
--- a/src/mod_shared_roster.erl
+++ b/src/mod_shared_roster.erl
@@ -38,7 +38,7 @@
list_groups/1, create_group/2, create_group/3,
delete_group/2, get_group_opts/2, set_group_opts/3,
get_group_users/2, get_group_explicit_users/2,
- is_user_in_group/3, add_user_to_group/3,
+ is_user_in_group/3, add_user_to_group/3, opts_to_binary/1,
remove_user_from_group/3, mod_opt_type/1]).
-include("ejabberd.hrl").
@@ -52,25 +52,28 @@
-include("ejabberd_web_admin.hrl").
--record(sr_group, {group_host = {<<"">>, <<"">>} :: {'$1' | binary(), '$2' | binary()},
- opts = [] :: list() | '_' | '$2'}).
-
--record(sr_user, {us = {<<"">>, <<"">>} :: {binary(), binary()},
- group_host = {<<"">>, <<"">>} :: {binary(), binary()}}).
+-include("mod_shared_roster.hrl").
+
+-type group_options() :: [{atom(), any()}].
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #sr_user{} | #sr_group{}) -> ok | pass.
+-callback list_groups(binary()) -> [binary()].
+-callback groups_with_opts(binary()) -> [{binary(), group_options()}].
+-callback create_group(binary(), binary(), group_options()) -> {atomic, any()}.
+-callback delete_group(binary(), binary()) -> {atomic, any()}.
+-callback get_group_opts(binary(), binary()) -> group_options() | error.
+-callback set_group_opts(binary(), binary(), group_options()) -> {atomic, any()}.
+-callback get_user_groups({binary(), binary()}, binary()) -> [binary()].
+-callback get_group_explicit_users(binary(), binary()) -> [{binary(), binary()}].
+-callback get_user_displayed_groups(binary(), binary(), group_options()) ->
+ [{binary(), group_options()}].
+-callback is_user_in_group({binary(), binary()}, binary(), binary()) -> boolean().
+-callback add_user_to_group(binary(), {binary(), binary()}, binary()) -> {atomic, any()}.
+-callback remove_user_from_group(binary(), {binary(), binary()}, binary()) -> {atomic, any()}.
start(Host, Opts) ->
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(sr_group,
- [{disc_copies, [node()]},
- {attributes, record_info(fields, sr_group)}]),
- mnesia:create_table(sr_user,
- [{disc_copies, [node()]}, {type, bag},
- {attributes, record_info(fields, sr_user)}]),
- update_tables(),
- mnesia:add_table_index(sr_user, group_host);
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE,
webadmin_menu, 70),
ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE,
@@ -206,11 +209,11 @@ get_rosteritem_name([ModVcard], U, S) ->
get_rosteritem_name_vcard([]) -> <<"">>;
get_rosteritem_name_vcard([Vcard]) ->
- case xml:get_path_s(Vcard,
+ case fxml:get_path_s(Vcard,
[{elem, <<"NICKNAME">>}, cdata])
of
<<"">> ->
- xml:get_path_s(Vcard, [{elem, <<"FN">>}, cdata]);
+ fxml:get_path_s(Vcard, [{elem, <<"FN">>}, cdata]);
Nickname -> Nickname
end.
@@ -391,195 +394,36 @@ process_subscription(Direction, User, Server, JID,
end.
list_groups(Host) ->
- list_groups(Host, gen_mod:db_type(Host, ?MODULE)).
-
-list_groups(Host, mnesia) ->
- mnesia:dirty_select(sr_group,
- [{#sr_group{group_host = {'$1', '$2'}, _ = '_'},
- [{'==', '$2', Host}], ['$1']}]);
-list_groups(Host, riak) ->
- case ejabberd_riak:get_keys_by_index(sr_group, <<"host">>, Host) of
- {ok, Gs} ->
- [G || {G, _} <- Gs];
- _ ->
- []
- end;
-list_groups(Host, odbc) ->
- case ejabberd_odbc:sql_query(Host,
- [<<"select name from sr_group;">>])
- of
- {selected, [<<"name">>], Rs} -> [G || [G] <- Rs];
- _ -> []
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:list_groups(Host).
groups_with_opts(Host) ->
- groups_with_opts(Host, gen_mod:db_type(Host, ?MODULE)).
-
-groups_with_opts(Host, mnesia) ->
- Gs = mnesia:dirty_select(sr_group,
- [{#sr_group{group_host = {'$1', Host}, opts = '$2',
- _ = '_'},
- [], [['$1', '$2']]}]),
- lists:map(fun ([G, O]) -> {G, O} end, Gs);
-groups_with_opts(Host, riak) ->
- case ejabberd_riak:get_by_index(sr_group, sr_group_schema(),
- <<"host">>, Host) of
- {ok, Rs} ->
- [{G, O} || #sr_group{group_host = {G, _}, opts = O} <- Rs];
- _ ->
- []
- end;
-groups_with_opts(Host, odbc) ->
- case ejabberd_odbc:sql_query(Host,
- [<<"select name, opts from sr_group;">>])
- of
- {selected, [<<"name">>, <<"opts">>], Rs} ->
- [{G, opts_to_binary(ejabberd_odbc:decode_term(Opts))}
- || [G, Opts] <- Rs];
- _ -> []
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:groups_with_opts(Host).
create_group(Host, Group) ->
create_group(Host, Group, []).
create_group(Host, Group, Opts) ->
- create_group(Host, Group, Opts,
- gen_mod:db_type(Host, ?MODULE)).
-
-create_group(Host, Group, Opts, mnesia) ->
- R = #sr_group{group_host = {Group, Host}, opts = Opts},
- F = fun () -> mnesia:write(R) end,
- mnesia:transaction(F);
-create_group(Host, Group, Opts, riak) ->
- {atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host},
- opts = Opts},
- sr_group_schema(),
- [{'2i', [{<<"host">>, Host}]}])};
-create_group(Host, Group, Opts, odbc) ->
- SGroup = ejabberd_odbc:escape(Group),
- SOpts = ejabberd_odbc:encode_term(Opts),
- F = fun () ->
- odbc_queries:update_t(<<"sr_group">>,
- [<<"name">>, <<"opts">>], [SGroup, SOpts],
- [<<"name='">>, SGroup, <<"'">>])
- end,
- ejabberd_odbc:sql_transaction(Host, F).
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:create_group(Host, Group, Opts).
delete_group(Host, Group) ->
- delete_group(Host, Group,
- gen_mod:db_type(Host, ?MODULE)).
-
-delete_group(Host, Group, mnesia) ->
- GroupHost = {Group, Host},
- F = fun () ->
- mnesia:delete({sr_group, GroupHost}),
- Users = mnesia:index_read(sr_user, GroupHost,
- #sr_user.group_host),
- lists:foreach(fun (UserEntry) ->
- mnesia:delete_object(UserEntry)
- end,
- Users)
- end,
- mnesia:transaction(F);
-delete_group(Host, Group, riak) ->
- try
- ok = ejabberd_riak:delete(sr_group, {Group, Host}),
- ok = ejabberd_riak:delete_by_index(sr_user, <<"group_host">>,
- {Group, Host}),
- {atomic, ok}
- catch _:{badmatch, Err} ->
- {atomic, Err}
- end;
-delete_group(Host, Group, odbc) ->
- SGroup = ejabberd_odbc:escape(Group),
- F = fun () ->
- ejabberd_odbc:sql_query_t([<<"delete from sr_group where name='">>,
- SGroup, <<"';">>]),
- ejabberd_odbc:sql_query_t([<<"delete from sr_user where grp='">>,
- SGroup, <<"';">>])
- end,
- case ejabberd_odbc:sql_transaction(Host, F) of
- {atomic,{updated,_}} -> {atomic, ok};
- Res -> Res
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:delete_group(Host, Group).
get_group_opts(Host, Group) ->
- get_group_opts(Host, Group,
- gen_mod:db_type(Host, ?MODULE)).
-
-get_group_opts(Host, Group, mnesia) ->
- case catch mnesia:dirty_read(sr_group, {Group, Host}) of
- [#sr_group{opts = Opts}] -> Opts;
- _ -> error
- end;
-get_group_opts(Host, Group, riak) ->
- case ejabberd_riak:get(sr_group, sr_group_schema(), {Group, Host}) of
- {ok, #sr_group{opts = Opts}} -> Opts;
- _ -> error
- end;
-get_group_opts(Host, Group, odbc) ->
- SGroup = ejabberd_odbc:escape(Group),
- case catch ejabberd_odbc:sql_query(Host,
- [<<"select opts from sr_group where name='">>,
- SGroup, <<"';">>])
- of
- {selected, [<<"opts">>], [[SOpts]]} ->
- opts_to_binary(ejabberd_odbc:decode_term(SOpts));
- _ -> error
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:get_group_opts(Host, Group).
set_group_opts(Host, Group, Opts) ->
- set_group_opts(Host, Group, Opts,
- gen_mod:db_type(Host, ?MODULE)).
-
-set_group_opts(Host, Group, Opts, mnesia) ->
- R = #sr_group{group_host = {Group, Host}, opts = Opts},
- F = fun () -> mnesia:write(R) end,
- mnesia:transaction(F);
-set_group_opts(Host, Group, Opts, riak) ->
- {atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host},
- opts = Opts},
- sr_group_schema(),
- [{'2i', [{<<"host">>, Host}]}])};
-set_group_opts(Host, Group, Opts, odbc) ->
- SGroup = ejabberd_odbc:escape(Group),
- SOpts = ejabberd_odbc:encode_term(Opts),
- F = fun () ->
- odbc_queries:update_t(<<"sr_group">>,
- [<<"name">>, <<"opts">>], [SGroup, SOpts],
- [<<"name='">>, SGroup, <<"'">>])
- end,
- ejabberd_odbc:sql_transaction(Host, F).
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:set_group_opts(Host, Group, Opts).
get_user_groups(US) ->
Host = element(2, US),
- DBType = gen_mod:db_type(Host, ?MODULE),
- get_user_groups(US, Host, DBType) ++
- get_special_users_groups(Host).
-
-get_user_groups(US, Host, mnesia) ->
- case catch mnesia:dirty_read(sr_user, US) of
- Rs when is_list(Rs) ->
- [Group
- || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
- _ -> []
- end;
-get_user_groups(US, Host, riak) ->
- case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of
- {ok, Rs} ->
- [Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
- _ ->
- []
- end;
-get_user_groups(US, Host, odbc) ->
- SJID = make_jid_s(US),
- case catch ejabberd_odbc:sql_query(Host,
- [<<"select grp from sr_user where jid='">>,
- SJID, <<"';">>])
- of
- {selected, [<<"grp">>], Rs} -> [G || [G] <- Rs];
- _ -> []
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:get_user_groups(US, Host) ++ get_special_users_groups(Host).
is_group_enabled(Host1, Group1) ->
{Host, Group} = split_grouphost(Host1, Group1),
@@ -630,39 +474,8 @@ get_group_users(Host, Group, GroupOpts) ->
++ get_group_explicit_users(Host, Group).
get_group_explicit_users(Host, Group) ->
- get_group_explicit_users(Host, Group,
- gen_mod:db_type(Host, ?MODULE)).
-
-get_group_explicit_users(Host, Group, mnesia) ->
- Read = (catch mnesia:dirty_index_read(sr_user,
- {Group, Host}, #sr_user.group_host)),
- case Read of
- Rs when is_list(Rs) -> [R#sr_user.us || R <- Rs];
- _ -> []
- end;
-get_group_explicit_users(Host, Group, riak) ->
- case ejabberd_riak:get_by_index(sr_user, sr_user_schema(),
- <<"group_host">>, {Group, Host}) of
- {ok, Rs} ->
- [R#sr_user.us || R <- Rs];
- _ ->
- []
- end;
-get_group_explicit_users(Host, Group, odbc) ->
- SGroup = ejabberd_odbc:escape(Group),
- case catch ejabberd_odbc:sql_query(Host,
- [<<"select jid from sr_user where grp='">>,
- SGroup, <<"';">>])
- of
- {selected, [<<"jid">>], Rs} ->
- lists:map(fun ([JID]) ->
- {U, S, _} =
- jid:tolower(jid:from_string(JID)),
- {U, S}
- end,
- Rs);
- _ -> []
- end.
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:get_group_explicit_users(Host, Group).
get_group_name(Host1, Group1) ->
{Host, Group} = split_grouphost(Host1, Group1),
@@ -718,44 +531,10 @@ get_special_displayed_groups(GroupsOpts) ->
%% for the list of groups of that server that user is member
%% get the list of groups displayed
get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
- Groups = get_user_displayed_groups(LUser, LServer,
- GroupsOpts,
- gen_mod:db_type(LServer, ?MODULE)),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Groups = Mod:get_user_displayed_groups(LUser, LServer, GroupsOpts),
displayed_groups(GroupsOpts, Groups).
-get_user_displayed_groups(LUser, LServer, GroupsOpts,
- mnesia) ->
- case catch mnesia:dirty_read(sr_user, {LUser, LServer})
- of
- Rs when is_list(Rs) ->
- [{Group, proplists:get_value(Group, GroupsOpts, [])}
- || #sr_user{group_host = {Group, H}} <- Rs,
- H == LServer];
- _ -> []
- end;
-get_user_displayed_groups(LUser, LServer, GroupsOpts,
- riak) ->
- case ejabberd_riak:get_by_index(sr_user, sr_user_schema(),
- <<"us">>, {LUser, LServer}) of
- {ok, Rs} ->
- [{Group, proplists:get_value(Group, GroupsOpts, [])}
- || #sr_user{group_host = {Group, _}} <- Rs];
- _ ->
- []
- end;
-get_user_displayed_groups(LUser, LServer, GroupsOpts,
- odbc) ->
- SJID = make_jid_s(LUser, LServer),
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select grp from sr_user where jid='">>,
- SJID, <<"';">>])
- of
- {selected, [<<"grp">>], Rs} ->
- [{Group, proplists:get_value(Group, GroupsOpts, [])}
- || [Group] <- Rs];
- _ -> []
- end.
-
%% @doc Get the list of groups that are displayed to this user
get_user_displayed_groups(US) ->
Host = element(2, US),
@@ -779,42 +558,12 @@ get_user_displayed_groups(US) ->
is_group_enabled(Host, Group)].
is_user_in_group(US, Group, Host) ->
- is_user_in_group(US, Group, Host,
- gen_mod:db_type(Host, ?MODULE)).
-
-is_user_in_group(US, Group, Host, mnesia) ->
- case catch mnesia:dirty_match_object(#sr_user{us = US,
- group_host = {Group, Host}})
- of
- [] -> lists:member(US, get_group_users(Host, Group));
- _ -> true
- end;
-is_user_in_group(US, Group, Host, riak) ->
- case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of
- {ok, Rs} ->
- case lists:any(
- fun(#sr_user{group_host = {G, H}}) ->
- (Group == G) and (Host == H)
- end, Rs) of
- false ->
- lists:member(US, get_group_users(Host, Group));
- true ->
- true
- end;
- _Err ->
- false
- end;
-is_user_in_group(US, Group, Host, odbc) ->
- SJID = make_jid_s(US),
- SGroup = ejabberd_odbc:escape(Group),
- case catch ejabberd_odbc:sql_query(Host,
- [<<"select * from sr_user where jid='">>,
- SJID, <<"' and grp='">>, SGroup,
- <<"';">>])
- of
- {selected, _, []} ->
- lists:member(US, get_group_users(Host, Group));
- _ -> true
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ case Mod:is_user_in_group(US, Group, Host) of
+ false ->
+ lists:member(US, get_group_users(Host, Group));
+ true ->
+ true
end.
%% @spec (Host::string(), {User::string(), Server::string()}, Group::string()) -> {atomic, ok}
@@ -837,31 +586,10 @@ add_user_to_group(Host, US, Group) ->
push_displayed_to_user(LUser, LServer, Host, both, DisplayedGroups),
broadcast_user_to_displayed(LUser, LServer, Host, both, DisplayedToGroups),
broadcast_displayed_to_user(LUser, LServer, Host, both, DisplayedGroups),
- add_user_to_group(Host, US, Group, gen_mod:db_type(Host, ?MODULE))
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Mod:add_user_to_group(Host, US, Group)
end.
-add_user_to_group(Host, US, Group, mnesia) ->
- R = #sr_user{us = US, group_host = {Group, Host}},
- F = fun () -> mnesia:write(R) end,
- mnesia:transaction(F);
-add_user_to_group(Host, US, Group, riak) ->
- {atomic, ejabberd_riak:put(
- #sr_user{us = US, group_host = {Group, Host}},
- sr_user_schema(),
- [{i, {US, {Group, Host}}},
- {'2i', [{<<"us">>, US},
- {<<"group_host">>, {Group, Host}}]}])};
-add_user_to_group(Host, US, Group, odbc) ->
- SJID = make_jid_s(US),
- SGroup = ejabberd_odbc:escape(Group),
- F = fun () ->
- odbc_queries:update_t(<<"sr_user">>,
- [<<"jid">>, <<"grp">>], [SJID, SGroup],
- [<<"jid='">>, SJID, <<"' and grp='">>,
- SGroup, <<"'">>])
- end,
- ejabberd_odbc:sql_transaction(Host, F).
-
get_displayed_groups(Group, LServer) ->
GroupsOpts = groups_with_opts(LServer),
GroupOpts = proplists:get_value(Group, GroupsOpts, []),
@@ -894,8 +622,8 @@ remove_user_from_group(Host, US, Group) ->
end,
(?MODULE):set_group_opts(Host, Group, NewGroupOpts);
nomatch ->
- Result = remove_user_from_group(Host, US, Group,
- gen_mod:db_type(Host, ?MODULE)),
+ Mod = gen_mod:db_mod(Host, ?MODULE),
+ Result = Mod:remove_user_from_group(Host, US, Group),
DisplayedToGroups = displayed_to_groups(Group, Host),
DisplayedGroups = get_displayed_groups(Group, LServer),
push_user_to_displayed(LUser, LServer, Group, Host, remove, DisplayedToGroups),
@@ -903,23 +631,6 @@ remove_user_from_group(Host, US, Group) ->
Result
end.
-remove_user_from_group(Host, US, Group, mnesia) ->
- R = #sr_user{us = US, group_host = {Group, Host}},
- F = fun () -> mnesia:delete_object(R) end,
- mnesia:transaction(F);
-remove_user_from_group(Host, US, Group, riak) ->
- {atomic, ejabberd_riak:delete(sr_group, {US, {Group, Host}})};
-remove_user_from_group(Host, US, Group, odbc) ->
- SJID = make_jid_s(US),
- SGroup = ejabberd_odbc:escape(Group),
- F = fun () ->
- ejabberd_odbc:sql_query_t([<<"delete from sr_user where jid='">>,
- SJID, <<"' and grp='">>, SGroup,
- <<"';">>]),
- ok
- end,
- ejabberd_odbc:sql_transaction(Host, F).
-
push_members_to_user(LUser, LServer, Group, Host,
Subscription) ->
GroupsOpts = groups_with_opts(LServer),
@@ -1160,7 +871,7 @@ list_shared_roster_groups(Host, Query, Lang) ->
[?INPUTT(<<"submit">>, <<"addnew">>,
<<"Add New">>)])])]))])),
(?H1GL((?T(<<"Shared Roster Groups">>)),
- <<"modsharedroster">>, <<"mod_shared_roster">>))
+ <<"mod_shared_roster">>, <<"mod_shared_roster">>))
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -1254,7 +965,7 @@ shared_roster_group(Host, Group, Query, Lang) ->
<<"20">>,
list_to_binary(FDisplayedGroups))])])])])),
(?H1GL((?T(<<"Shared Roster Groups">>)),
- <<"modsharedroster">>, <<"mod_shared_roster">>))
+ <<"mod_shared_roster">>, <<"mod_shared_roster">>))
++
[?XC(<<"h2">>, <<(?T(<<"Group ">>))/binary, Group/binary>>)] ++
case Res of
@@ -1385,13 +1096,6 @@ displayed_groups_update(Members, DisplayedGroups, Subscription) ->
end
end, Members).
-make_jid_s(U, S) ->
- ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:make(U,
- S,
- <<"">>)))).
-
-make_jid_s({U, S}) -> make_jid_s(U, S).
-
opts_to_binary(Opts) ->
lists:map(
fun({name, Name}) ->
@@ -1404,105 +1108,17 @@ opts_to_binary(Opts) ->
Opt
end, Opts).
-sr_group_schema() ->
- {record_info(fields, sr_group), #sr_group{}}.
-
-sr_user_schema() ->
- {record_info(fields, sr_user), #sr_user{}}.
-
-update_tables() ->
- update_sr_group_table(),
- update_sr_user_table().
-
-update_sr_group_table() ->
- Fields = record_info(fields, sr_group),
- case mnesia:table_info(sr_group, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- sr_group, Fields, set,
- fun(#sr_group{group_host = {G, _}}) -> G end,
- fun(#sr_group{group_host = {G, H},
- opts = Opts} = R) ->
- R#sr_group{group_host = {iolist_to_binary(G),
- iolist_to_binary(H)},
- opts = opts_to_binary(Opts)}
- end);
- _ ->
- ?INFO_MSG("Recreating sr_group table", []),
- mnesia:transform_table(sr_group, ignore, Fields)
- end.
-
-update_sr_user_table() ->
- Fields = record_info(fields, sr_user),
- case mnesia:table_info(sr_user, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- sr_user, Fields, bag,
- fun(#sr_user{us = {U, _}}) -> U end,
- fun(#sr_user{us = {U, S}, group_host = {G, H}} = R) ->
- R#sr_user{us = {iolist_to_binary(U), iolist_to_binary(S)},
- group_host = {iolist_to_binary(G),
- iolist_to_binary(H)}}
- end);
- _ ->
- ?INFO_MSG("Recreating sr_user table", []),
- mnesia:transform_table(sr_user, ignore, Fields)
- end.
-
-export(_Server) ->
- [{sr_group,
- fun(Host, #sr_group{group_host = {Group, LServer}, opts = Opts})
- when LServer == Host ->
- SGroup = ejabberd_odbc:escape(Group),
- SOpts = ejabberd_odbc:encode_term(Opts),
- [[<<"delete from sr_group where name='">>, Group, <<"';">>],
- [<<"insert into sr_group(name, opts) values ('">>,
- SGroup, <<"', '">>, SOpts, <<"');">>]];
- (_Host, _R) ->
- []
- end},
- {sr_user,
- fun(Host, #sr_user{us = {U, S}, group_host = {Group, LServer}})
- when LServer == Host ->
- SGroup = ejabberd_odbc:escape(Group),
- SJID = ejabberd_odbc:escape(
- jid:to_string(
- jid:tolower(
- jid:make(U, S, <<"">>)))),
- [[<<"delete from sr_user where jid='">>, SJID,
- <<"'and grp='">>, Group, <<"';">>],
- [<<"insert into sr_user(jid, grp) values ('">>,
- SJID, <<"', '">>, SGroup, <<"');">>]];
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import(LServer) ->
- [{<<"select name, opts from sr_group;">>,
- fun([Group, SOpts]) ->
- #sr_group{group_host = {Group, LServer},
- opts = ejabberd_odbc:decode_term(SOpts)}
- end},
- {<<"select jid, grp from sr_user;">>,
- fun([SJID, Group]) ->
- #jid{luser = U, lserver = S} = jid:from_string(SJID),
- #sr_user{us = {U, S}, group_host = {Group, LServer}}
- end}].
-
-import(_LServer, mnesia, #sr_group{} = G) ->
- mnesia:dirty_write(G);
-
-import(_LServer, mnesia, #sr_user{} = U) ->
- mnesia:dirty_write(U);
-import(_LServer, riak, #sr_group{group_host = {_, Host}} = G) ->
- ejabberd_riak:put(G, sr_group_schema(), [{'2i', [{<<"host">>, Host}]}]);
-import(_LServer, riak, #sr_user{us = US, group_host = {Group, Host}} = User) ->
- ejabberd_riak:put(User, sr_user_schema(),
- [{i, {US, {Group, Host}}},
- {'2i', [{<<"us">>, US},
- {<<"group_host">>, {Group, Host}}]}]);
-import(_, _, _) ->
- pass.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
+
+import(LServer, DBType, Data) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, Data).
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
mod_opt_type(_) -> [db_type].
diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl
index a4ac65c1..34588b90 100644
--- a/src/mod_shared_roster_ldap.erl
+++ b/src/mod_shared_roster_ldap.erl
@@ -47,16 +47,13 @@
-include("logger.hrl").
-include("jlib.hrl").
-include("mod_roster.hrl").
-
-include("eldap.hrl").
-define(CACHE_SIZE, 1000).
-
--define(USER_CACHE_VALIDITY, 300).
-
+-define(USER_CACHE_VALIDITY, 300). %% in seconds
-define(GROUP_CACHE_VALIDITY, 300).
-
--define(LDAP_SEARCH_TIMEOUT, 5).
+-define(LDAP_SEARCH_TIMEOUT, 5). %% Timeout for LDAP search queries in seconds
+-define(INVALID_SETTING_MSG, "~s is not properly set! ~s will not function.").
-record(state,
{host = <<"">> :: binary(),
@@ -334,12 +331,11 @@ get_group_users(Host, Group) ->
{ok, State} = eldap_utils:get_state(Host, ?MODULE),
case cache_tab:dirty_lookup(shared_roster_ldap_group,
{Group, Host},
- fun () -> search_group_info(State, Group) end)
- of
- {ok, #group_info{members = Members}}
+ fun () -> search_group_info(State, Group) end) of
+ {ok, #group_info{members = Members}}
when Members /= undefined ->
- Members;
- _ -> []
+ Members;
+ _ -> []
end.
get_group_name(Host, Group) ->
@@ -381,79 +377,49 @@ search_group_info(State, Group) ->
true -> fun ejabberd_auth:is_user_exists/2;
_ -> fun (_U, _S) -> true end
end,
- Host = State#state.host,
case eldap_search(State,
[eldap_filter:do_sub(State#state.gfilter,
[{<<"%g">>, Group}])],
[State#state.group_attr, State#state.group_desc,
State#state.uid])
of
- [] -> error;
+ [] ->
+ error;
LDAPEntries ->
- {GroupDesc, MembersLists} = lists:foldl(fun
- (#eldap_entry{attributes =
- Attrs},
- {DescAcc, JIDsAcc}) ->
- case
- {eldap_utils:get_ldap_attr(State#state.group_attr,
- Attrs),
- eldap_utils:get_ldap_attr(State#state.group_desc,
- Attrs),
- lists:keysearch(State#state.uid,
- 1,
- Attrs)}
- of
- {ID, Desc,
- {value,
- {GroupMemberAttr,
- Members}}}
- when ID /= <<"">>,
- GroupMemberAttr
- ==
- State#state.uid ->
- JIDs =
- lists:foldl(fun
- ({ok,
- UID},
- L) ->
- PUID =
- jid:nodeprep(UID),
- case
- PUID
- of
- error ->
- L;
- _ ->
- case
- AuthChecker(PUID,
- Host)
- of
- true ->
- [{PUID,
- Host}
- | L];
- _ ->
- L
- end
- end;
- (_,
- L) ->
- L
- end,
- [],
- lists:map(Extractor,
- Members)),
- {Desc,
- [JIDs
- | JIDsAcc]};
- _ ->
- {DescAcc, JIDsAcc}
- end
- end,
- {Group, []}, LDAPEntries),
- {ok,
- #group_info{desc = GroupDesc,
- members = lists:usort(lists:flatten(MembersLists))}}
+ {GroupDesc, MembersLists} = lists:foldl(fun(Entry, Acc) ->
+ extract_members(State, Extractor, AuthChecker, Entry, Acc)
+ end,
+ {Group, []}, LDAPEntries),
+ {ok, #group_info{desc = GroupDesc, members = lists:usort(lists:flatten(MembersLists))}}
+ end.
+
+extract_members(State, Extractor, AuthChecker, #eldap_entry{attributes = Attrs}, {DescAcc, JIDsAcc}) ->
+ Host = State#state.host,
+ case {eldap_utils:get_ldap_attr(State#state.group_attr, Attrs),
+ eldap_utils:get_ldap_attr(State#state.group_desc, Attrs),
+ lists:keysearch(State#state.uid, 1, Attrs)} of
+ {ID, Desc, {value, {GroupMemberAttr, Members}}} when ID /= <<"">>,
+ GroupMemberAttr == State#state.uid ->
+ JIDs = lists:foldl(fun({ok, UID}, L) ->
+ PUID = jid:nodeprep(UID),
+ case PUID of
+ error ->
+ L;
+ _ ->
+ case AuthChecker(PUID, Host) of
+ true ->
+ [{PUID, Host} | L];
+ _ ->
+ L
+ end
+ end;
+ (_, L) -> L
+ end,
+ [],
+ lists:map(Extractor, Members)),
+ {Desc, [JIDs | JIDsAcc]};
+ _ ->
+ {DescAcc, JIDsAcc}
end.
search_user_name(State, User) ->
diff --git a/src/mod_shared_roster_mnesia.erl b/src/mod_shared_roster_mnesia.erl
new file mode 100644
index 00000000..ca2e55e2
--- /dev/null
+++ b/src/mod_shared_roster_mnesia.erl
@@ -0,0 +1,167 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_shared_roster_mnesia).
+
+-behaviour(mod_shared_roster).
+
+%% API
+-export([init/2, list_groups/1, groups_with_opts/1, create_group/3,
+ delete_group/2, get_group_opts/2, set_group_opts/3,
+ get_user_groups/2, get_group_explicit_users/2,
+ get_user_displayed_groups/3, is_user_in_group/3,
+ add_user_to_group/3, remove_user_from_group/3, import/2]).
+
+-include("jlib.hrl").
+-include("mod_roster.hrl").
+-include("mod_shared_roster.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(sr_group,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, sr_group)}]),
+ mnesia:create_table(sr_user,
+ [{disc_copies, [node()]}, {type, bag},
+ {attributes, record_info(fields, sr_user)}]),
+ update_tables(),
+ mnesia:add_table_index(sr_user, group_host).
+
+list_groups(Host) ->
+ mnesia:dirty_select(sr_group,
+ [{#sr_group{group_host = {'$1', '$2'}, _ = '_'},
+ [{'==', '$2', Host}], ['$1']}]).
+
+groups_with_opts(Host) ->
+ Gs = mnesia:dirty_select(sr_group,
+ [{#sr_group{group_host = {'$1', Host}, opts = '$2',
+ _ = '_'},
+ [], [['$1', '$2']]}]),
+ lists:map(fun ([G, O]) -> {G, O} end, Gs).
+
+create_group(Host, Group, Opts) ->
+ R = #sr_group{group_host = {Group, Host}, opts = Opts},
+ F = fun () -> mnesia:write(R) end,
+ mnesia:transaction(F).
+
+delete_group(Host, Group) ->
+ GroupHost = {Group, Host},
+ F = fun () ->
+ mnesia:delete({sr_group, GroupHost}),
+ Users = mnesia:index_read(sr_user, GroupHost,
+ #sr_user.group_host),
+ lists:foreach(fun (UserEntry) ->
+ mnesia:delete_object(UserEntry)
+ end,
+ Users)
+ end,
+ mnesia:transaction(F).
+
+get_group_opts(Host, Group) ->
+ case catch mnesia:dirty_read(sr_group, {Group, Host}) of
+ [#sr_group{opts = Opts}] -> Opts;
+ _ -> error
+ end.
+
+set_group_opts(Host, Group, Opts) ->
+ R = #sr_group{group_host = {Group, Host}, opts = Opts},
+ F = fun () -> mnesia:write(R) end,
+ mnesia:transaction(F).
+
+get_user_groups(US, Host) ->
+ case catch mnesia:dirty_read(sr_user, US) of
+ Rs when is_list(Rs) ->
+ [Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
+ _ ->
+ []
+ end.
+
+get_group_explicit_users(Host, Group) ->
+ Read = (catch mnesia:dirty_index_read(sr_user,
+ {Group, Host}, #sr_user.group_host)),
+ case Read of
+ Rs when is_list(Rs) -> [R#sr_user.us || R <- Rs];
+ _ -> []
+ end.
+
+get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
+ case catch mnesia:dirty_read(sr_user, {LUser, LServer}) of
+ Rs when is_list(Rs) ->
+ [{Group, proplists:get_value(Group, GroupsOpts, [])}
+ || #sr_user{group_host = {Group, H}} <- Rs,
+ H == LServer];
+ _ ->
+ []
+ end.
+
+is_user_in_group(US, Group, Host) ->
+ case mnesia:dirty_match_object(
+ #sr_user{us = US, group_host = {Group, Host}}) of
+ [] -> false;
+ _ -> true
+ end.
+
+add_user_to_group(Host, US, Group) ->
+ R = #sr_user{us = US, group_host = {Group, Host}},
+ F = fun () -> mnesia:write(R) end,
+ mnesia:transaction(F).
+
+remove_user_from_group(Host, US, Group) ->
+ R = #sr_user{us = US, group_host = {Group, Host}},
+ F = fun () -> mnesia:delete_object(R) end,
+ mnesia:transaction(F).
+
+import(_LServer, #sr_group{} = G) ->
+ mnesia:dirty_write(G);
+import(_LServer, #sr_user{} = U) ->
+ mnesia:dirty_write(U).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_tables() ->
+ update_sr_group_table(),
+ update_sr_user_table().
+
+update_sr_group_table() ->
+ Fields = record_info(fields, sr_group),
+ case mnesia:table_info(sr_group, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ sr_group, Fields, set,
+ fun(#sr_group{group_host = {G, _}}) -> G end,
+ fun(#sr_group{group_host = {G, H},
+ opts = Opts} = R) ->
+ R#sr_group{group_host = {iolist_to_binary(G),
+ iolist_to_binary(H)},
+ opts = mod_shared_roster:opts_to_binary(Opts)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating sr_group table", []),
+ mnesia:transform_table(sr_group, ignore, Fields)
+ end.
+
+update_sr_user_table() ->
+ Fields = record_info(fields, sr_user),
+ case mnesia:table_info(sr_user, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ sr_user, Fields, bag,
+ fun(#sr_user{us = {U, _}}) -> U end,
+ fun(#sr_user{us = {U, S}, group_host = {G, H}} = R) ->
+ R#sr_user{us = {iolist_to_binary(U), iolist_to_binary(S)},
+ group_host = {iolist_to_binary(G),
+ iolist_to_binary(H)}}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating sr_user table", []),
+ mnesia:transform_table(sr_user, ignore, Fields)
+ end.
diff --git a/src/mod_shared_roster_riak.erl b/src/mod_shared_roster_riak.erl
new file mode 100644
index 00000000..0df35e37
--- /dev/null
+++ b/src/mod_shared_roster_riak.erl
@@ -0,0 +1,139 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_shared_roster_riak).
+
+-behaviour(mod_shared_roster).
+
+%% API
+-export([init/2, list_groups/1, groups_with_opts/1, create_group/3,
+ delete_group/2, get_group_opts/2, set_group_opts/3,
+ get_user_groups/2, get_group_explicit_users/2,
+ get_user_displayed_groups/3, is_user_in_group/3,
+ add_user_to_group/3, remove_user_from_group/3, import/2]).
+
+-include("jlib.hrl").
+-include("mod_roster.hrl").
+-include("mod_shared_roster.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+list_groups(Host) ->
+ case ejabberd_riak:get_keys_by_index(sr_group, <<"host">>, Host) of
+ {ok, Gs} ->
+ [G || {G, _} <- Gs];
+ _ ->
+ []
+ end.
+
+groups_with_opts(Host) ->
+ case ejabberd_riak:get_by_index(sr_group, sr_group_schema(),
+ <<"host">>, Host) of
+ {ok, Rs} ->
+ [{G, O} || #sr_group{group_host = {G, _}, opts = O} <- Rs];
+ _ ->
+ []
+ end.
+
+create_group(Host, Group, Opts) ->
+ {atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host},
+ opts = Opts},
+ sr_group_schema(),
+ [{'2i', [{<<"host">>, Host}]}])}.
+
+delete_group(Host, Group) ->
+ try
+ ok = ejabberd_riak:delete(sr_group, {Group, Host}),
+ ok = ejabberd_riak:delete_by_index(sr_user, <<"group_host">>,
+ {Group, Host}),
+ {atomic, ok}
+ catch _:{badmatch, Err} ->
+ {atomic, Err}
+ end.
+
+get_group_opts(Host, Group) ->
+ case ejabberd_riak:get(sr_group, sr_group_schema(), {Group, Host}) of
+ {ok, #sr_group{opts = Opts}} -> Opts;
+ _ -> error
+ end.
+
+set_group_opts(Host, Group, Opts) ->
+ {atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host},
+ opts = Opts},
+ sr_group_schema(),
+ [{'2i', [{<<"host">>, Host}]}])}.
+
+get_user_groups(US, Host) ->
+ case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of
+ {ok, Rs} ->
+ [Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
+ _ ->
+ []
+ end.
+
+get_group_explicit_users(Host, Group) ->
+ case ejabberd_riak:get_by_index(sr_user, sr_user_schema(),
+ <<"group_host">>, {Group, Host}) of
+ {ok, Rs} ->
+ [R#sr_user.us || R <- Rs];
+ _ ->
+ []
+ end.
+
+get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
+ case ejabberd_riak:get_by_index(sr_user, sr_user_schema(),
+ <<"us">>, {LUser, LServer}) of
+ {ok, Rs} ->
+ [{Group, proplists:get_value(Group, GroupsOpts, [])}
+ || #sr_user{group_host = {Group, _}} <- Rs];
+ _ ->
+ []
+ end.
+
+is_user_in_group(US, Group, Host) ->
+ case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of
+ {ok, Rs} ->
+ lists:any(
+ fun(#sr_user{group_host = {G, H}}) ->
+ (Group == G) and (Host == H)
+ end, Rs);
+ _Err ->
+ false
+ end.
+
+add_user_to_group(Host, US, Group) ->
+ {atomic, ejabberd_riak:put(
+ #sr_user{us = US, group_host = {Group, Host}},
+ sr_user_schema(),
+ [{i, {US, {Group, Host}}},
+ {'2i', [{<<"us">>, US},
+ {<<"group_host">>, {Group, Host}}]}])}.
+
+remove_user_from_group(Host, US, Group) ->
+ {atomic, ejabberd_riak:delete(sr_group, {US, {Group, Host}})}.
+
+import(_LServer, #sr_group{group_host = {_, Host}} = G) ->
+ ejabberd_riak:put(G, sr_group_schema(), [{'2i', [{<<"host">>, Host}]}]);
+import(_LServer, #sr_user{us = US, group_host = {Group, Host}} = User) ->
+ ejabberd_riak:put(User, sr_user_schema(),
+ [{i, {US, {Group, Host}}},
+ {'2i', [{<<"us">>, US},
+ {<<"group_host">>, {Group, Host}}]}]).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+sr_group_schema() ->
+ {record_info(fields, sr_group), #sr_group{}}.
+
+sr_user_schema() ->
+ {record_info(fields, sr_user), #sr_user{}}.
diff --git a/src/mod_shared_roster_sql.erl b/src/mod_shared_roster_sql.erl
new file mode 100644
index 00000000..bd123cd4
--- /dev/null
+++ b/src/mod_shared_roster_sql.erl
@@ -0,0 +1,212 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_shared_roster_sql).
+
+-behaviour(mod_shared_roster).
+
+%% API
+-export([init/2, list_groups/1, groups_with_opts/1, create_group/3,
+ delete_group/2, get_group_opts/2, set_group_opts/3,
+ get_user_groups/2, get_group_explicit_users/2,
+ get_user_displayed_groups/3, is_user_in_group/3,
+ add_user_to_group/3, remove_user_from_group/3, import/1,
+ import/2, export/1]).
+
+-include("jlib.hrl").
+-include("mod_roster.hrl").
+-include("mod_shared_roster.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+list_groups(Host) ->
+ case ejabberd_sql:sql_query(
+ Host, [<<"select name from sr_group;">>]) of
+ {selected, [<<"name">>], Rs} -> [G || [G] <- Rs];
+ _ -> []
+ end.
+
+groups_with_opts(Host) ->
+ case ejabberd_sql:sql_query(Host,
+ [<<"select name, opts from sr_group;">>])
+ of
+ {selected, [<<"name">>, <<"opts">>], Rs} ->
+ [{G, mod_shared_roster:opts_to_binary(ejabberd_sql:decode_term(Opts))}
+ || [G, Opts] <- Rs];
+ _ -> []
+ end.
+
+create_group(Host, Group, Opts) ->
+ SGroup = ejabberd_sql:escape(Group),
+ SOpts = ejabberd_sql:encode_term(Opts),
+ F = fun () ->
+ sql_queries:update_t(<<"sr_group">>,
+ [<<"name">>, <<"opts">>], [SGroup, SOpts],
+ [<<"name='">>, SGroup, <<"'">>])
+ end,
+ ejabberd_sql:sql_transaction(Host, F).
+
+delete_group(Host, Group) ->
+ SGroup = ejabberd_sql:escape(Group),
+ F = fun () ->
+ ejabberd_sql:sql_query_t([<<"delete from sr_group where name='">>,
+ SGroup, <<"';">>]),
+ ejabberd_sql:sql_query_t([<<"delete from sr_user where grp='">>,
+ SGroup, <<"';">>])
+ end,
+ case ejabberd_sql:sql_transaction(Host, F) of
+ {atomic,{updated,_}} -> {atomic, ok};
+ Res -> Res
+ end.
+
+get_group_opts(Host, Group) ->
+ SGroup = ejabberd_sql:escape(Group),
+ case catch ejabberd_sql:sql_query(
+ Host,
+ [<<"select opts from sr_group where name='">>,
+ SGroup, <<"';">>]) of
+ {selected, [<<"opts">>], [[SOpts]]} ->
+ mod_shared_roster:opts_to_binary(ejabberd_sql:decode_term(SOpts));
+ _ -> error
+ end.
+
+set_group_opts(Host, Group, Opts) ->
+ SGroup = ejabberd_sql:escape(Group),
+ SOpts = ejabberd_sql:encode_term(Opts),
+ F = fun () ->
+ sql_queries:update_t(<<"sr_group">>,
+ [<<"name">>, <<"opts">>], [SGroup, SOpts],
+ [<<"name='">>, SGroup, <<"'">>])
+ end,
+ ejabberd_sql:sql_transaction(Host, F).
+
+get_user_groups(US, Host) ->
+ SJID = make_jid_s(US),
+ case catch ejabberd_sql:sql_query(
+ Host,
+ [<<"select grp from sr_user where jid='">>,
+ SJID, <<"';">>]) of
+ {selected, [<<"grp">>], Rs} -> [G || [G] <- Rs];
+ _ -> []
+ end.
+
+get_group_explicit_users(Host, Group) ->
+ SGroup = ejabberd_sql:escape(Group),
+ case catch ejabberd_sql:sql_query(
+ Host,
+ [<<"select jid from sr_user where grp='">>,
+ SGroup, <<"';">>]) of
+ {selected, [<<"jid">>], Rs} ->
+ lists:map(
+ fun([JID]) ->
+ {U, S, _} = jid:tolower(jid:from_string(JID)),
+ {U, S}
+ end, Rs);
+ _ ->
+ []
+ end.
+
+get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
+ SJID = make_jid_s(LUser, LServer),
+ case catch ejabberd_sql:sql_query(
+ LServer,
+ [<<"select grp from sr_user where jid='">>,
+ SJID, <<"';">>]) of
+ {selected, [<<"grp">>], Rs} ->
+ [{Group, proplists:get_value(Group, GroupsOpts, [])}
+ || [Group] <- Rs];
+ _ -> []
+ end.
+
+is_user_in_group(US, Group, Host) ->
+ SJID = make_jid_s(US),
+ SGroup = ejabberd_sql:escape(Group),
+ case catch ejabberd_sql:sql_query(Host,
+ [<<"select * from sr_user where jid='">>,
+ SJID, <<"' and grp='">>, SGroup,
+ <<"';">>]) of
+ {selected, _, []} -> false;
+ _ -> true
+ end.
+
+add_user_to_group(Host, US, Group) ->
+ SJID = make_jid_s(US),
+ SGroup = ejabberd_sql:escape(Group),
+ F = fun () ->
+ sql_queries:update_t(<<"sr_user">>,
+ [<<"jid">>, <<"grp">>], [SJID, SGroup],
+ [<<"jid='">>, SJID, <<"' and grp='">>,
+ SGroup, <<"'">>])
+ end,
+ ejabberd_sql:sql_transaction(Host, F).
+
+remove_user_from_group(Host, US, Group) ->
+ SJID = make_jid_s(US),
+ SGroup = ejabberd_sql:escape(Group),
+ F = fun () ->
+ ejabberd_sql:sql_query_t([<<"delete from sr_user where jid='">>,
+ SJID, <<"' and grp='">>, SGroup,
+ <<"';">>]),
+ ok
+ end,
+ ejabberd_sql:sql_transaction(Host, F).
+
+export(_Server) ->
+ [{sr_group,
+ fun(Host, #sr_group{group_host = {Group, LServer}, opts = Opts})
+ when LServer == Host ->
+ SGroup = ejabberd_sql:escape(Group),
+ SOpts = ejabberd_sql:encode_term(Opts),
+ [[<<"delete from sr_group where name='">>, Group, <<"';">>],
+ [<<"insert into sr_group(name, opts) values ('">>,
+ SGroup, <<"', '">>, SOpts, <<"');">>]];
+ (_Host, _R) ->
+ []
+ end},
+ {sr_user,
+ fun(Host, #sr_user{us = {U, S}, group_host = {Group, LServer}})
+ when LServer == Host ->
+ SGroup = ejabberd_sql:escape(Group),
+ SJID = ejabberd_sql:escape(
+ jid:to_string(
+ jid:tolower(
+ jid:make(U, S, <<"">>)))),
+ [[<<"delete from sr_user where jid='">>, SJID,
+ <<"'and grp='">>, Group, <<"';">>],
+ [<<"insert into sr_user(jid, grp) values ('">>,
+ SJID, <<"', '">>, SGroup, <<"');">>]];
+ (_Host, _R) ->
+ []
+ end}].
+
+import(LServer) ->
+ [{<<"select name, opts from sr_group;">>,
+ fun([Group, SOpts]) ->
+ #sr_group{group_host = {Group, LServer},
+ opts = ejabberd_sql:decode_term(SOpts)}
+ end},
+ {<<"select jid, grp from sr_user;">>,
+ fun([SJID, Group]) ->
+ #jid{luser = U, lserver = S} = jid:from_string(SJID),
+ #sr_user{us = {U, S}, group_host = {Group, LServer}}
+ end}].
+
+import(_, _) ->
+ pass.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+make_jid_s(U, S) ->
+ ejabberd_sql:escape(jid:to_string(jid:tolower(jid:make(U, S, <<"">>)))).
+
+make_jid_s({U, S}) -> make_jid_s(U, S).
diff --git a/src/mod_sic.erl b/src/mod_sic.erl
index 3ffd9c91..b4eae0da 100644
--- a/src/mod_sic.erl
+++ b/src/mod_sic.erl
@@ -60,8 +60,9 @@ process_local_iq(#jid{user = User, server = Server,
_To, #iq{type = get, sub_el = _SubEl} = IQ) ->
get_ip({User, Server, Resource}, IQ);
process_local_iq(_From, _To,
- #iq{type = set, sub_el = SubEl} = IQ) ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
+ #iq{type = set, sub_el = SubEl, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}.
process_sm_iq(#jid{user = User, server = Server,
resource = Resource},
@@ -69,14 +70,17 @@ process_sm_iq(#jid{user = User, server = Server,
#iq{type = get, sub_el = _SubEl} = IQ) ->
get_ip({User, Server, Resource}, IQ);
process_sm_iq(_From, _To,
- #iq{type = get, sub_el = SubEl} = IQ) ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]};
+ #iq{type = get, sub_el = SubEl, lang = Lang} = IQ) ->
+ Txt = <<"Query to another users is forbidden">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]};
process_sm_iq(_From, _To,
- #iq{type = set, sub_el = SubEl} = IQ) ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
+ #iq{type = set, sub_el = SubEl, lang = Lang} = IQ) ->
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}.
get_ip({User, Server, Resource},
- #iq{sub_el =
+ #iq{lang = Lang,
+ sub_el =
#xmlel{name = Name, attrs = Attrs} = SubEl} =
IQ) ->
case ejabberd_sm:get_user_ip(User, Server, Resource) of
@@ -88,8 +92,9 @@ get_ip({User, Server, Resource},
[{xmlcdata,
iolist_to_binary(jlib:ip_to_list(IP))}]}]};
_ ->
+ Txt = <<"User session not found">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+ sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
end.
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
diff --git a/src/mod_stats.erl b/src/mod_stats.erl
index c9f659d3..c14cf8d1 100644
--- a/src/mod_stats.erl
+++ b/src/mod_stats.erl
@@ -50,17 +50,18 @@ stop(Host) ->
process_local_iq(_From, To,
#iq{id = _ID, type = Type, xmlns = XMLNS,
- sub_el = SubEl} =
+ sub_el = SubEl, lang = Lang} =
IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
#xmlel{children = Els} = SubEl,
- Node = str:tokens(xml:get_tag_attr_s(<<"node">>, SubEl),
+ Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, SubEl),
<<"/">>),
Names = get_names(Els, []),
- case get_local_stats(To#jid.server, Node, Names) of
+ case get_local_stats(To#jid.server, Node, Names, Lang) of
{result, Res} ->
IQ#iq{type = result,
sub_el =
@@ -76,7 +77,7 @@ get_names([], Res) -> Res;
get_names([#xmlel{name = <<"stat">>, attrs = Attrs}
| Els],
Res) ->
- Name = xml:get_attr_s(<<"name">>, Attrs),
+ Name = fxml:get_attr_s(<<"name">>, Attrs),
case Name of
<<"">> -> get_names(Els, Res);
_ -> get_names(Els, [Name | Res])
@@ -87,18 +88,18 @@ get_names([_ | Els], Res) -> get_names(Els, Res).
#xmlel{name = <<"stat">>, attrs = [{<<"name">>, Name}],
children = []}).
-get_local_stats(_Server, [], []) ->
+get_local_stats(_Server, [], [], _Lang) ->
{result,
[?STAT(<<"users/online">>), ?STAT(<<"users/total">>),
?STAT(<<"users/all-hosts/online">>),
?STAT(<<"users/all-hosts/total">>)]};
-get_local_stats(Server, [], Names) ->
+get_local_stats(Server, [], Names, _Lang) ->
{result,
lists:map(fun (Name) -> get_local_stat(Server, [], Name)
end,
Names)};
get_local_stats(_Server, [<<"running nodes">>, _],
- []) ->
+ [], _Lang) ->
{result,
[?STAT(<<"time/uptime">>), ?STAT(<<"time/cputime">>),
?STAT(<<"users/online">>),
@@ -107,16 +108,19 @@ get_local_stats(_Server, [<<"running nodes">>, _],
?STAT(<<"transactions/restarted">>),
?STAT(<<"transactions/logged">>)]};
get_local_stats(_Server, [<<"running nodes">>, ENode],
- Names) ->
+ Names, Lang) ->
case search_running_node(ENode) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false ->
+ Txt = <<"No running node found">>,
+ {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
{result,
lists:map(fun (Name) -> get_node_stat(Node, Name) end,
Names)}
end;
-get_local_stats(_Server, _, _) ->
- {error, ?ERR_FEATURE_NOT_IMPLEMENTED}.
+get_local_stats(_Server, _, _, Lang) ->
+ Txt = <<"No statistics found for this item">>,
+ {error, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)}.
-define(STATVAL(Val, Unit),
#xmlel{name = <<"stat">>,
diff --git a/src/mod_time.erl b/src/mod_time.erl
index 8bfe9f9f..740b654a 100644
--- a/src/mod_time.erl
+++ b/src/mod_time.erl
@@ -51,10 +51,11 @@ stop(Host) ->
?NS_TIME).
process_local_iq(_From, _To,
- #iq{type = Type, sub_el = SubEl} = IQ) ->
+ #iq{type = Type, sub_el = SubEl, lang = Lang} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
Now_universal = calendar:universal_time(),
Now_local = calendar:universal_time_to_local_time(Now_universal),
diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl
index db19b557..e5f5d9e3 100644
--- a/src/mod_vcard.erl
+++ b/src/mod_vcard.erl
@@ -33,53 +33,31 @@
-behaviour(gen_mod).
-export([start/2, init/3, stop/1, get_sm_features/5,
- process_local_iq/3, process_sm_iq/3, reindex_vcards/0,
+ process_local_iq/3, process_sm_iq/3, string2lower/1,
remove_user/2, export/1, import/1, import/3,
- mod_opt_type/1]).
+ mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
-include("ejabberd.hrl").
-include("logger.hrl").
-
-include("jlib.hrl").
+-include("mod_vcard.hrl").
-define(JUD_MATCHES, 30).
--record(vcard_search,
- {us, user, luser, fn, lfn, family, lfamily, given,
- lgiven, middle, lmiddle, nickname, lnickname, bday,
- lbday, ctry, lctry, locality, llocality, email, lemail,
- orgname, lorgname, orgunit, lorgunit}).
-
--record(vcard, {us = {<<"">>, <<"">>} :: {binary(), binary()} | binary(),
- vcard = #xmlel{} :: xmlel()}).
-
-define(PROCNAME, ejabberd_mod_vcard).
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #vcard{} | #vcard_search{}) -> ok | pass.
+-callback get_vcard(binary(), binary()) -> [xmlel()] | error.
+-callback set_vcard(binary(), binary(),
+ xmlel(), #vcard_search{}) -> {atomic, any()}.
+-callback search(binary(), [{binary(), [binary()]}], boolean(),
+ infinity | pos_integer()) -> [binary()].
+-callback remove_user(binary(), binary()) -> {atomic, any()}.
+
start(Host, Opts) ->
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(vcard,
- [{disc_only_copies, [node()]},
- {attributes, record_info(fields, vcard)}]),
- mnesia:create_table(vcard_search,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, vcard_search)}]),
- update_tables(),
- mnesia:add_table_index(vcard_search, luser),
- mnesia:add_table_index(vcard_search, lfn),
- mnesia:add_table_index(vcard_search, lfamily),
- mnesia:add_table_index(vcard_search, lgiven),
- mnesia:add_table_index(vcard_search, lmiddle),
- mnesia:add_table_index(vcard_search, lnickname),
- mnesia:add_table_index(vcard_search, lbday),
- mnesia:add_table_index(vcard_search, lctry),
- mnesia:add_table_index(vcard_search, llocality),
- mnesia:add_table_index(vcard_search, lemail),
- mnesia:add_table_index(vcard_search, lorgname),
- mnesia:add_table_index(vcard_search, lorgunit);
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
@@ -102,7 +80,7 @@ init(Host, ServerHost, Search) ->
case Search of
false -> loop(Host, ServerHost);
_ ->
- ejabberd_router:register_route(Host),
+ ejabberd_router:register_route(Host, ServerHost),
loop(Host, ServerHost)
end.
@@ -149,7 +127,8 @@ process_local_iq(_From, _To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
IQ#iq{type = result,
sub_el =
@@ -173,7 +152,7 @@ process_local_iq(_From, _To,
end.
process_sm_iq(From, To,
- #iq{type = Type, sub_el = SubEl} = IQ) ->
+ #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
#jid{user = User, lserver = LServer} = From,
@@ -182,14 +161,16 @@ process_sm_iq(From, To,
set_vcard(User, LServer, SubEl),
IQ#iq{type = result, sub_el = []};
false ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+ Txt = <<"The query is only allowed from local users">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}
end;
get ->
#jid{luser = LUser, lserver = LServer} = To,
case get_vcard(LUser, LServer) of
error ->
+ Txt = <<"Database failure">>,
IQ#iq{type = error,
- sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
+ sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]};
[] ->
IQ#iq{type = result,
sub_el = [#xmlel{name = <<"vCard">>,
@@ -200,68 +181,38 @@ process_sm_iq(From, To,
end.
get_vcard(LUser, LServer) ->
- get_vcard(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-get_vcard(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () -> mnesia:read({vcard, US}) end,
- case mnesia:transaction(F) of
- {atomic, Rs} ->
- lists:map(fun (R) -> R#vcard.vcard end, Rs);
- {aborted, _Reason} -> error
- end;
-get_vcard(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case catch odbc_queries:get_vcard(LServer, Username) of
- {selected, [<<"vcard">>], [[SVCARD]]} ->
- case xml_stream:parse_element(SVCARD) of
- {error, _Reason} -> error;
- VCARD -> [VCARD]
- end;
- {selected, [<<"vcard">>], []} -> [];
- _ -> error
- end;
-get_vcard(LUser, LServer, riak) ->
- case ejabberd_riak:get(vcard, vcard_schema(), {LUser, LServer}) of
- {ok, R} ->
- [R#vcard.vcard];
- {error, notfound} ->
- [];
- _ ->
- error
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_vcard(LUser, LServer).
-set_vcard(User, LServer, VCARD) ->
- FN = xml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
- Family = xml:get_path_s(VCARD,
+make_vcard_search(User, LUser, LServer, VCARD) ->
+ FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
+ Family = fxml:get_path_s(VCARD,
[{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
- Given = xml:get_path_s(VCARD,
+ Given = fxml:get_path_s(VCARD,
[{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]),
- Middle = xml:get_path_s(VCARD,
+ Middle = fxml:get_path_s(VCARD,
[{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]),
- Nickname = xml:get_path_s(VCARD,
+ Nickname = fxml:get_path_s(VCARD,
[{elem, <<"NICKNAME">>}, cdata]),
- BDay = xml:get_path_s(VCARD,
+ BDay = fxml:get_path_s(VCARD,
[{elem, <<"BDAY">>}, cdata]),
- CTRY = xml:get_path_s(VCARD,
+ CTRY = fxml:get_path_s(VCARD,
[{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]),
- Locality = xml:get_path_s(VCARD,
+ Locality = fxml:get_path_s(VCARD,
[{elem, <<"ADR">>}, {elem, <<"LOCALITY">>},
cdata]),
- EMail1 = xml:get_path_s(VCARD,
+ EMail1 = fxml:get_path_s(VCARD,
[{elem, <<"EMAIL">>}, {elem, <<"USERID">>}, cdata]),
- EMail2 = xml:get_path_s(VCARD,
+ EMail2 = fxml:get_path_s(VCARD,
[{elem, <<"EMAIL">>}, cdata]),
- OrgName = xml:get_path_s(VCARD,
+ OrgName = fxml:get_path_s(VCARD,
[{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]),
- OrgUnit = xml:get_path_s(VCARD,
+ OrgUnit = fxml:get_path_s(VCARD,
[{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]),
EMail = case EMail1 of
<<"">> -> EMail2;
_ -> EMail1
end,
- LUser = jid:nodeprep(User),
LFN = string2lower(FN),
LFamily = string2lower(Family),
LGiven = string2lower(Given),
@@ -273,105 +224,42 @@ set_vcard(User, LServer, VCARD) ->
LEMail = string2lower(EMail),
LOrgName = string2lower(OrgName),
LOrgUnit = string2lower(OrgUnit),
- if (LUser == error) ->
- {error, badarg};
- true ->
- case gen_mod:db_type(LServer, ?MODULE) of
- mnesia ->
- US = {LUser, LServer},
- F = fun () ->
- mnesia:write(#vcard{us = US, vcard = VCARD}),
- mnesia:write(#vcard_search{us = US,
- user = {User, LServer},
- luser = LUser, fn = FN,
- lfn = LFN,
- family = Family,
- lfamily = LFamily,
- given = Given,
- lgiven = LGiven,
- middle = Middle,
- lmiddle = LMiddle,
- nickname = Nickname,
- lnickname = LNickname,
- bday = BDay,
- lbday = LBDay,
- ctry = CTRY,
- lctry = LCTRY,
- locality = Locality,
- llocality = LLocality,
- email = EMail,
- lemail = LEMail,
- orgname = OrgName,
- lorgname = LOrgName,
- orgunit = OrgUnit,
- lorgunit = LOrgUnit})
- end,
- mnesia:transaction(F);
- riak ->
- US = {LUser, LServer},
- ejabberd_riak:put(#vcard{us = US, vcard = VCARD},
- vcard_schema(),
- [{'2i', [{<<"user">>, User},
- {<<"luser">>, LUser},
- {<<"fn">>, FN},
- {<<"lfn">>, LFN},
- {<<"family">>, Family},
- {<<"lfamily">>, LFamily},
- {<<"given">>, Given},
- {<<"lgiven">>, LGiven},
- {<<"middle">>, Middle},
- {<<"lmiddle">>, LMiddle},
- {<<"nickname">>, Nickname},
- {<<"lnickname">>, LNickname},
- {<<"bday">>, BDay},
- {<<"lbday">>, LBDay},
- {<<"ctry">>, CTRY},
- {<<"lctry">>, LCTRY},
- {<<"locality">>, Locality},
- {<<"llocality">>, LLocality},
- {<<"email">>, EMail},
- {<<"lemail">>, LEMail},
- {<<"orgname">>, OrgName},
- {<<"lorgname">>, LOrgName},
- {<<"orgunit">>, OrgUnit},
- {<<"lorgunit">>, LOrgUnit}]}]);
- odbc ->
- Username = ejabberd_odbc:escape(User),
- LUsername = ejabberd_odbc:escape(LUser),
- SVCARD =
- ejabberd_odbc:escape(xml:element_to_binary(VCARD)),
- SFN = ejabberd_odbc:escape(FN),
- SLFN = ejabberd_odbc:escape(LFN),
- SFamily = ejabberd_odbc:escape(Family),
- SLFamily = ejabberd_odbc:escape(LFamily),
- SGiven = ejabberd_odbc:escape(Given),
- SLGiven = ejabberd_odbc:escape(LGiven),
- SMiddle = ejabberd_odbc:escape(Middle),
- SLMiddle = ejabberd_odbc:escape(LMiddle),
- SNickname = ejabberd_odbc:escape(Nickname),
- SLNickname = ejabberd_odbc:escape(LNickname),
- SBDay = ejabberd_odbc:escape(BDay),
- SLBDay = ejabberd_odbc:escape(LBDay),
- SCTRY = ejabberd_odbc:escape(CTRY),
- SLCTRY = ejabberd_odbc:escape(LCTRY),
- SLocality = ejabberd_odbc:escape(Locality),
- SLLocality = ejabberd_odbc:escape(LLocality),
- SEMail = ejabberd_odbc:escape(EMail),
- SLEMail = ejabberd_odbc:escape(LEMail),
- SOrgName = ejabberd_odbc:escape(OrgName),
- SLOrgName = ejabberd_odbc:escape(LOrgName),
- SOrgUnit = ejabberd_odbc:escape(OrgUnit),
- SLOrgUnit = ejabberd_odbc:escape(LOrgUnit),
- odbc_queries:set_vcard(LServer, LUsername, SBDay, SCTRY,
- SEMail, SFN, SFamily, SGiven, SLBDay,
- SLCTRY, SLEMail, SLFN, SLFamily,
- SLGiven, SLLocality, SLMiddle,
- SLNickname, SLOrgName, SLOrgUnit,
- SLocality, SMiddle, SNickname, SOrgName,
- SOrgUnit, SVCARD, Username)
- end,
- ejabberd_hooks:run(vcard_set, LServer,
- [LUser, LServer, VCARD])
+ US = {LUser, LServer},
+ #vcard_search{us = US,
+ user = {User, LServer},
+ luser = LUser, fn = FN,
+ lfn = LFN,
+ family = Family,
+ lfamily = LFamily,
+ given = Given,
+ lgiven = LGiven,
+ middle = Middle,
+ lmiddle = LMiddle,
+ nickname = Nickname,
+ lnickname = LNickname,
+ bday = BDay,
+ lbday = LBDay,
+ ctry = CTRY,
+ lctry = LCTRY,
+ locality = Locality,
+ llocality = LLocality,
+ email = EMail,
+ lemail = LEMail,
+ orgname = OrgName,
+ lorgname = LOrgName,
+ orgunit = OrgUnit,
+ lorgunit = LOrgUnit}.
+
+set_vcard(User, LServer, VCARD) ->
+ case jid:nodeprep(User) of
+ error ->
+ {error, badarg};
+ LUser ->
+ VCardSearch = make_vcard_search(User, LUser, LServer, VCARD),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:set_vcard(LUser, LServer, VCARD, VCardSearch),
+ ejabberd_hooks:run(vcard_set, LServer,
+ [LUser, LServer, VCARD])
end.
string2lower(String) ->
@@ -445,15 +333,17 @@ do_route(ServerHost, From, To, Packet) ->
XDataEl = find_xdata_el(SubEl),
case XDataEl of
false ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_BAD_REQUEST),
+ Txt = <<"Data form not found">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_router:route(To, From, Err);
_ ->
XData = jlib:parse_xdata_submit(XDataEl),
case XData of
invalid ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_BAD_REQUEST),
+ Txt = <<"Incorrect data form">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_router:route(To, From, Err);
_ ->
ResIQ = IQ#iq{type = result,
@@ -493,7 +383,8 @@ do_route(ServerHost, From, To, Packet) ->
#iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} ->
case Type of
set ->
- Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
ejabberd_router:route(To, From, Err);
get ->
Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
@@ -539,10 +430,11 @@ do_route(ServerHost, From, To, Packet) ->
++ Info}]},
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
end;
- #iq{type = Type, xmlns = ?NS_DISCO_ITEMS} ->
+ #iq{type = Type, lang = Lang, xmlns = ?NS_DISCO_ITEMS} ->
case Type of
set ->
- Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
ejabberd_router:route(To, From, Err);
get ->
ResIQ = IQ#iq{type = result,
@@ -587,7 +479,7 @@ find_xdata_el1([]) -> false;
find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
children = SubEls}
| Els]) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
#xmlel{name = Name, attrs = Attrs, children = SubEls};
_ -> find_xdata_el1(Els)
@@ -671,498 +563,36 @@ record_to_item(_LServer, #vcard_search{} = R) ->
?FIELD(<<"orgunit">>, (R#vcard_search.orgunit))]}.
search(LServer, Data) ->
- DBType = gen_mod:db_type(LServer, ?MODULE),
- MatchSpec = make_matchspec(LServer, Data, DBType),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE, allow_return_all,
fun(B) when is_boolean(B) -> B end,
false),
- search(LServer, MatchSpec, AllowReturnAll, DBType).
-
-search(LServer, MatchSpec, AllowReturnAll, mnesia) ->
- if (MatchSpec == #vcard_search{_ = '_'}) and
- not AllowReturnAll ->
- [];
- true ->
- case catch mnesia:dirty_select(vcard_search,
- [{MatchSpec, [], ['$_']}])
- of
- {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), [];
- Rs ->
- case gen_mod:get_module_opt(LServer, ?MODULE, matches,
- fun(infinity) -> infinity;
- (I) when is_integer(I),
- I>0 ->
- I
- end, ?JUD_MATCHES) of
- infinity ->
- Rs;
- Val ->
- lists:sublist(Rs, Val)
- end
- end
- end;
-search(LServer, MatchSpec, AllowReturnAll, odbc) ->
- if (MatchSpec == <<"">>) and not AllowReturnAll -> [];
- true ->
- Limit = case gen_mod:get_module_opt(LServer, ?MODULE, matches,
- fun(infinity) -> infinity;
- (I) when is_integer(I),
- I>0 ->
- I
- end, ?JUD_MATCHES) of
- infinity ->
- <<"">>;
- Val ->
- [<<" LIMIT ">>,
- jlib:integer_to_binary(Val)]
- end,
- case catch ejabberd_odbc:sql_query(LServer,
- [<<"select username, fn, family, given, "
- "middle, nickname, bday, ctry, "
- "locality, email, orgname, orgunit "
- "from vcard_search ">>,
- MatchSpec, Limit, <<";">>])
- of
- {selected,
- [<<"username">>, <<"fn">>, <<"family">>, <<"given">>,
- <<"middle">>, <<"nickname">>, <<"bday">>, <<"ctry">>,
- <<"locality">>, <<"email">>, <<"orgname">>,
- <<"orgunit">>],
- Rs}
- when is_list(Rs) ->
- Rs;
- Error -> ?ERROR_MSG("~p", [Error]), []
- end
- end;
-search(_LServer, _MatchSpec, _AllowReturnAll, riak) ->
- [].
-
-make_matchspec(LServer, Data, mnesia) ->
- GlobMatch = #vcard_search{_ = '_'},
- Match = filter_fields(Data, GlobMatch, LServer, mnesia),
- Match;
-make_matchspec(LServer, Data, odbc) ->
- filter_fields(Data, <<"">>, LServer, odbc);
-make_matchspec(_LServer, _Data, riak) ->
- [].
-
-filter_fields([], Match, _LServer, mnesia) -> Match;
-filter_fields([], Match, _LServer, odbc) ->
- case Match of
- <<"">> -> <<"">>;
- _ -> [<<" where ">>, Match]
- end;
-filter_fields([{SVar, [Val]} | Ds], Match, LServer,
- mnesia)
- when is_binary(Val) and (Val /= <<"">>) ->
- LVal = string2lower(Val),
- NewMatch = case SVar of
- <<"user">> ->
- case gen_mod:get_module_opt(LServer, ?MODULE,
- search_all_hosts,
- fun(B) when is_boolean(B) ->
- B
- end, true)
- of
- true -> Match#vcard_search{luser = make_val(LVal)};
- false ->
- Host = find_my_host(LServer),
- Match#vcard_search{us = {make_val(LVal), Host}}
- end;
- <<"fn">> -> Match#vcard_search{lfn = make_val(LVal)};
- <<"last">> ->
- Match#vcard_search{lfamily = make_val(LVal)};
- <<"first">> ->
- Match#vcard_search{lgiven = make_val(LVal)};
- <<"middle">> ->
- Match#vcard_search{lmiddle = make_val(LVal)};
- <<"nick">> ->
- Match#vcard_search{lnickname = make_val(LVal)};
- <<"bday">> ->
- Match#vcard_search{lbday = make_val(LVal)};
- <<"ctry">> ->
- Match#vcard_search{lctry = make_val(LVal)};
- <<"locality">> ->
- Match#vcard_search{llocality = make_val(LVal)};
- <<"email">> ->
- Match#vcard_search{lemail = make_val(LVal)};
- <<"orgname">> ->
- Match#vcard_search{lorgname = make_val(LVal)};
- <<"orgunit">> ->
- Match#vcard_search{lorgunit = make_val(LVal)};
- _ -> Match
- end,
- filter_fields(Ds, NewMatch, LServer, mnesia);
-filter_fields([{SVar, [Val]} | Ds], Match, LServer,
- odbc)
- when is_binary(Val) and (Val /= <<"">>) ->
- LVal = string2lower(Val),
- NewMatch = case SVar of
- <<"user">> -> make_val(Match, <<"lusername">>, LVal);
- <<"fn">> -> make_val(Match, <<"lfn">>, LVal);
- <<"last">> -> make_val(Match, <<"lfamily">>, LVal);
- <<"first">> -> make_val(Match, <<"lgiven">>, LVal);
- <<"middle">> -> make_val(Match, <<"lmiddle">>, LVal);
- <<"nick">> -> make_val(Match, <<"lnickname">>, LVal);
- <<"bday">> -> make_val(Match, <<"lbday">>, LVal);
- <<"ctry">> -> make_val(Match, <<"lctry">>, LVal);
- <<"locality">> ->
- make_val(Match, <<"llocality">>, LVal);
- <<"email">> -> make_val(Match, <<"lemail">>, LVal);
- <<"orgname">> -> make_val(Match, <<"lorgname">>, LVal);
- <<"orgunit">> -> make_val(Match, <<"lorgunit">>, LVal);
- _ -> Match
- end,
- filter_fields(Ds, NewMatch, LServer, odbc);
-filter_fields([_ | Ds], Match, LServer, DBType) ->
- filter_fields(Ds, Match, LServer, DBType).
-
-make_val(Match, Field, Val) ->
- Condition = case str:suffix(<<"*">>, Val) of
- true ->
- Val1 = str:substr(Val, 1, byte_size(Val) - 1),
- SVal = <<(ejabberd_odbc:escape_like(Val1))/binary,
- "%">>,
- [Field, <<" LIKE '">>, SVal, <<"'">>];
- _ ->
- SVal = ejabberd_odbc:escape(Val),
- [Field, <<" = '">>, SVal, <<"'">>]
- end,
- case Match of
- <<"">> -> Condition;
- _ -> [Match, <<" and ">>, Condition]
- end.
-
-make_val(Val) ->
- case str:suffix(<<"*">>, Val) of
- true -> [str:substr(Val, 1, byte_size(Val) - 1)] ++ '_';
- _ -> Val
- end.
-
-find_my_host(LServer) ->
- Parts = str:tokens(LServer, <<".">>),
- find_my_host(Parts, ?MYHOSTS).
-
-find_my_host([], _Hosts) -> ?MYNAME;
-find_my_host([_ | Tail] = Parts, Hosts) ->
- Domain = parts_to_string(Parts),
- case lists:member(Domain, Hosts) of
- true -> Domain;
- false -> find_my_host(Tail, Hosts)
- end.
-
-parts_to_string(Parts) ->
- str:strip(list_to_binary(
- lists:map(fun (S) -> <<S/binary, $.>> end, Parts)),
- right, $.).
+ MaxMatch = gen_mod:get_module_opt(LServer, ?MODULE, matches,
+ fun(infinity) -> infinity;
+ (I) when is_integer(I),
+ I>0 ->
+ I
+ end, ?JUD_MATCHES),
+ Mod:search(LServer, Data, AllowReturnAll, MaxMatch).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-set_vcard_t(R, _) ->
- US = R#vcard.us,
- User = US,
- VCARD = R#vcard.vcard,
- FN = xml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
- Family = xml:get_path_s(VCARD,
- [{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
- Given = xml:get_path_s(VCARD,
- [{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]),
- Middle = xml:get_path_s(VCARD,
- [{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]),
- Nickname = xml:get_path_s(VCARD,
- [{elem, <<"NICKNAME">>}, cdata]),
- BDay = xml:get_path_s(VCARD,
- [{elem, <<"BDAY">>}, cdata]),
- CTRY = xml:get_path_s(VCARD,
- [{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]),
- Locality = xml:get_path_s(VCARD,
- [{elem, <<"ADR">>}, {elem, <<"LOCALITY">>},
- cdata]),
- EMail = xml:get_path_s(VCARD,
- [{elem, <<"EMAIL">>}, cdata]),
- OrgName = xml:get_path_s(VCARD,
- [{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]),
- OrgUnit = xml:get_path_s(VCARD,
- [{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]),
- {LUser, _LServer} = US,
- LFN = string2lower(FN),
- LFamily = string2lower(Family),
- LGiven = string2lower(Given),
- LMiddle = string2lower(Middle),
- LNickname = string2lower(Nickname),
- LBDay = string2lower(BDay),
- LCTRY = string2lower(CTRY),
- LLocality = string2lower(Locality),
- LEMail = string2lower(EMail),
- LOrgName = string2lower(OrgName),
- LOrgUnit = string2lower(OrgUnit),
- mnesia:write(#vcard_search{us = US, user = User,
- luser = LUser, fn = FN, lfn = LFN,
- family = Family, lfamily = LFamily,
- given = Given, lgiven = LGiven,
- middle = Middle, lmiddle = LMiddle,
- nickname = Nickname,
- lnickname = LNickname, bday = BDay,
- lbday = LBDay, ctry = CTRY, lctry = LCTRY,
- locality = Locality,
- llocality = LLocality, email = EMail,
- lemail = LEMail, orgname = OrgName,
- lorgname = LOrgName, orgunit = OrgUnit,
- lorgunit = LOrgUnit}).
-
-reindex_vcards() ->
- F = fun () -> mnesia:foldl(fun set_vcard_t/2, [], vcard)
- end,
- mnesia:transaction(F).
-
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
- remove_user(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-remove_user(LUser, LServer, mnesia) ->
- US = {LUser, LServer},
- F = fun () ->
- mnesia:delete({vcard, US}),
- mnesia:delete({vcard_search, US})
- end,
- mnesia:transaction(F);
-remove_user(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- ejabberd_odbc:sql_transaction(LServer,
- [[<<"delete from vcard where username='">>,
- Username, <<"';">>],
- [<<"delete from vcard_search where lusername='">>,
- Username, <<"';">>]]);
-remove_user(LUser, LServer, riak) ->
- {atomic, ejabberd_riak:delete(vcard, {LUser, LServer})}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_user(LUser, LServer).
-update_tables() ->
- update_vcard_table(),
- update_vcard_search_table().
-
-update_vcard_table() ->
- Fields = record_info(fields, vcard),
- case mnesia:table_info(vcard, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- vcard, Fields, set,
- fun(#vcard{us = {U, _}}) -> U end,
- fun(#vcard{us = {U, S}, vcard = El} = R) ->
- R#vcard{us = {iolist_to_binary(U),
- iolist_to_binary(S)},
- vcard = xml:to_xmlel(El)}
- end);
- _ ->
- ?INFO_MSG("Recreating vcard table", []),
- mnesia:transform_table(vcard, ignore, Fields)
- end.
-
-update_vcard_search_table() ->
- Fields = record_info(fields, vcard_search),
- case mnesia:table_info(vcard_search, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- vcard_search, Fields, set,
- fun(#vcard_search{us = {U, _}}) -> U end,
- fun(#vcard_search{} = VS) ->
- [vcard_search | L] = tuple_to_list(VS),
- NewL = lists:map(
- fun({U, S}) ->
- {iolist_to_binary(U),
- iolist_to_binary(S)};
- (Str) ->
- iolist_to_binary(Str)
- end, L),
- list_to_tuple([vcard_search | NewL])
- end);
- _ ->
- ?INFO_MSG("Recreating vcard_search table", []),
- mnesia:transform_table(vcard_search, ignore, Fields)
- end.
-
-vcard_schema() ->
- {record_info(fields, vcard), #vcard{}}.
-
-export(_Server) ->
- [{vcard,
- fun(Host, #vcard{us = {LUser, LServer}, vcard = VCARD})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- SVCARD =
- ejabberd_odbc:escape(xml:element_to_binary(VCARD)),
- [[<<"delete from vcard where username='">>, Username, <<"';">>],
- [<<"insert into vcard(username, vcard) values ('">>,
- Username, <<"', '">>, SVCARD, <<"');">>]];
- (_Host, _R) ->
- []
- end},
- {vcard_search,
- fun(Host, #vcard_search{user = {User, LServer}, luser = LUser,
- fn = FN, lfn = LFN, family = Family,
- lfamily = LFamily, given = Given,
- lgiven = LGiven, middle = Middle,
- lmiddle = LMiddle, nickname = Nickname,
- lnickname = LNickname, bday = BDay,
- lbday = LBDay, ctry = CTRY, lctry = LCTRY,
- locality = Locality, llocality = LLocality,
- email = EMail, lemail = LEMail,
- orgname = OrgName, lorgname = LOrgName,
- orgunit = OrgUnit, lorgunit = LOrgUnit})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(User),
- LUsername = ejabberd_odbc:escape(LUser),
- SFN = ejabberd_odbc:escape(FN),
- SLFN = ejabberd_odbc:escape(LFN),
- SFamily = ejabberd_odbc:escape(Family),
- SLFamily = ejabberd_odbc:escape(LFamily),
- SGiven = ejabberd_odbc:escape(Given),
- SLGiven = ejabberd_odbc:escape(LGiven),
- SMiddle = ejabberd_odbc:escape(Middle),
- SLMiddle = ejabberd_odbc:escape(LMiddle),
- SNickname = ejabberd_odbc:escape(Nickname),
- SLNickname = ejabberd_odbc:escape(LNickname),
- SBDay = ejabberd_odbc:escape(BDay),
- SLBDay = ejabberd_odbc:escape(LBDay),
- SCTRY = ejabberd_odbc:escape(CTRY),
- SLCTRY = ejabberd_odbc:escape(LCTRY),
- SLocality = ejabberd_odbc:escape(Locality),
- SLLocality = ejabberd_odbc:escape(LLocality),
- SEMail = ejabberd_odbc:escape(EMail),
- SLEMail = ejabberd_odbc:escape(LEMail),
- SOrgName = ejabberd_odbc:escape(OrgName),
- SLOrgName = ejabberd_odbc:escape(LOrgName),
- SOrgUnit = ejabberd_odbc:escape(OrgUnit),
- SLOrgUnit = ejabberd_odbc:escape(LOrgUnit),
- [[<<"delete from vcard_search where lusername='">>,
- LUsername, <<"';">>],
- [<<"insert into vcard_search( username, "
- "lusername, fn, lfn, family, lfamily, "
- " given, lgiven, middle, lmiddle, "
- "nickname, lnickname, bday, lbday, "
- "ctry, lctry, locality, llocality, "
- " email, lemail, orgname, lorgname, "
- "orgunit, lorgunit)values (">>,
- <<" '">>, Username, <<"', '">>, LUsername,
- <<"', '">>, SFN, <<"', '">>, SLFN,
- <<"', '">>, SFamily, <<"', '">>, SLFamily,
- <<"', '">>, SGiven, <<"', '">>, SLGiven,
- <<"', '">>, SMiddle, <<"', '">>, SLMiddle,
- <<"', '">>, SNickname, <<"', '">>, SLNickname,
- <<"', '">>, SBDay, <<"', '">>, SLBDay,
- <<"', '">>, SCTRY, <<"', '">>, SLCTRY,
- <<"', '">>, SLocality, <<"', '">>, SLLocality,
- <<"', '">>, SEMail, <<"', '">>, SLEMail,
- <<"', '">>, SOrgName, <<"', '">>, SLOrgName,
- <<"', '">>, SOrgUnit, <<"', '">>, SLOrgUnit,
- <<"');">>]];
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import(LServer) ->
- [{<<"select username, vcard from vcard;">>,
- fun([LUser, SVCard]) ->
- #xmlel{} = VCARD = xml_stream:parse_element(SVCard),
- #vcard{us = {LUser, LServer}, vcard = VCARD}
- end},
- {<<"select username, lusername, fn, lfn, family, lfamily, "
- "given, lgiven, middle, lmiddle, nickname, lnickname, "
- "bday, lbday, ctry, lctry, locality, llocality, email, "
- "lemail, orgname, lorgname, orgunit, lorgunit from vcard_search;">>,
- fun([User, LUser, FN, LFN,
- Family, LFamily, Given, LGiven,
- Middle, LMiddle, Nickname, LNickname,
- BDay, LBDay, CTRY, LCTRY, Locality, LLocality,
- EMail, LEMail, OrgName, LOrgName, OrgUnit, LOrgUnit]) ->
- #vcard_search{us = {LUser, LServer},
- user = {User, LServer}, luser = LUser,
- fn = FN, lfn = LFN, family = Family,
- lfamily = LFamily, given = Given,
- lgiven = LGiven, middle = Middle,
- lmiddle = LMiddle, nickname = Nickname,
- lnickname = LNickname, bday = BDay,
- lbday = LBDay, ctry = CTRY, lctry = LCTRY,
- locality = Locality, llocality = LLocality,
- email = EMail, lemail = LEMail,
- orgname = OrgName, lorgname = LOrgName,
- orgunit = OrgUnit, lorgunit = LOrgUnit}
- end}].
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
-import(_LServer, mnesia, #vcard{} = VCard) ->
- mnesia:dirty_write(VCard);
-import(_LServer, mnesia, #vcard_search{} = S) ->
- mnesia:dirty_write(S);
-import(_LServer, riak, #vcard{us = {LUser, _}, vcard = El} = VCard) ->
- FN = xml:get_path_s(El, [{elem, <<"FN">>}, cdata]),
- Family = xml:get_path_s(El,
- [{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
- Given = xml:get_path_s(El,
- [{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]),
- Middle = xml:get_path_s(El,
- [{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]),
- Nickname = xml:get_path_s(El,
- [{elem, <<"NICKNAME">>}, cdata]),
- BDay = xml:get_path_s(El,
- [{elem, <<"BDAY">>}, cdata]),
- CTRY = xml:get_path_s(El,
- [{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]),
- Locality = xml:get_path_s(El,
- [{elem, <<"ADR">>}, {elem, <<"LOCALITY">>},
- cdata]),
- EMail1 = xml:get_path_s(El,
- [{elem, <<"EMAIL">>}, {elem, <<"USERID">>}, cdata]),
- EMail2 = xml:get_path_s(El,
- [{elem, <<"EMAIL">>}, cdata]),
- OrgName = xml:get_path_s(El,
- [{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]),
- OrgUnit = xml:get_path_s(El,
- [{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]),
- EMail = case EMail1 of
- <<"">> -> EMail2;
- _ -> EMail1
- end,
- LFN = string2lower(FN),
- LFamily = string2lower(Family),
- LGiven = string2lower(Given),
- LMiddle = string2lower(Middle),
- LNickname = string2lower(Nickname),
- LBDay = string2lower(BDay),
- LCTRY = string2lower(CTRY),
- LLocality = string2lower(Locality),
- LEMail = string2lower(EMail),
- LOrgName = string2lower(OrgName),
- LOrgUnit = string2lower(OrgUnit),
- ejabberd_riak:put(VCard, vcard_schema(),
- [{'2i', [{<<"user">>, LUser},
- {<<"luser">>, LUser},
- {<<"fn">>, FN},
- {<<"lfn">>, LFN},
- {<<"family">>, Family},
- {<<"lfamily">>, LFamily},
- {<<"given">>, Given},
- {<<"lgiven">>, LGiven},
- {<<"middle">>, Middle},
- {<<"lmiddle">>, LMiddle},
- {<<"nickname">>, Nickname},
- {<<"lnickname">>, LNickname},
- {<<"bday">>, BDay},
- {<<"lbday">>, LBDay},
- {<<"ctry">>, CTRY},
- {<<"lctry">>, LCTRY},
- {<<"locality">>, Locality},
- {<<"llocality">>, LLocality},
- {<<"email">>, EMail},
- {<<"lemail">>, LEMail},
- {<<"orgname">>, OrgName},
- {<<"lorgname">>, LOrgName},
- {<<"orgunit">>, OrgUnit},
- {<<"lorgunit">>, LOrgUnit}]}]);
-import(_LServer, riak, #vcard_search{}) ->
- ok;
-import(_, _, _) ->
- pass.
+import(LServer, DBType, VCard) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, VCard).
mod_opt_type(allow_return_all) ->
fun (B) when is_boolean(B) -> B end;
diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl
index b24356d8..46f81af0 100644
--- a/src/mod_vcard_ldap.erl
+++ b/src/mod_vcard_ldap.erl
@@ -21,7 +21,7 @@
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
-%%%----------------------------------------------------------------------
+%%%---------------------u-------------------------------------------------
-module(mod_vcard_ldap).
@@ -173,7 +173,7 @@ init([Host, Opts]) ->
State#state.password, State#state.tls_options),
case State#state.search of
true ->
- ejabberd_router:register_route(State#state.myhost);
+ ejabberd_router:register_route(State#state.myhost, Host);
_ -> ok
end,
{ok, State}.
@@ -206,7 +206,8 @@ process_local_iq(_From, _To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
IQ#iq{type = result,
sub_el =
@@ -240,10 +241,11 @@ process_sm_iq(_From, #jid{lserver = LServer} = To,
process_vcard_ldap(To, IQ, Server) ->
{ok, State} = eldap_utils:get_state(Server, ?PROCNAME),
- #iq{type = Type, sub_el = SubEl} = IQ,
+ #iq{type = Type, sub_el = SubEl, lang = Lang} = IQ,
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
#jid{luser = LUser} = To,
LServer = State#state.serverhost,
@@ -455,15 +457,17 @@ route(State, From, To, Packet) ->
XDataEl = find_xdata_el(SubEl),
case XDataEl of
false ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_BAD_REQUEST),
+ Txt = <<"Data form not found">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_router:route(To, From, Err);
_ ->
XData = jlib:parse_xdata_submit(XDataEl),
case XData of
invalid ->
- Err = jlib:make_error_reply(Packet,
- ?ERR_BAD_REQUEST),
+ Txt = <<"Incorrect data form">>,
+ Err = jlib:make_error_reply(
+ Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_router:route(To, From, Err);
_ ->
ResIQ = IQ#iq{type = result,
@@ -505,7 +509,8 @@ route(State, From, To, Packet) ->
#iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} ->
case Type of
set ->
- Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
ejabberd_router:route(To, From, Err);
get ->
Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
@@ -545,10 +550,11 @@ route(State, From, To, Packet) ->
++ Info}]},
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
end;
- #iq{type = Type, xmlns = ?NS_DISCO_ITEMS} ->
+ #iq{type = Type, lang = Lang, xmlns = ?NS_DISCO_ITEMS} ->
case Type of
set ->
- Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
ejabberd_router:route(To, From, Err);
get ->
ResIQ = IQ#iq{type = result,
@@ -723,7 +729,7 @@ find_xdata_el1([]) -> false;
find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
children = SubEls}
| Els]) ->
- case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
#xmlel{name = Name, attrs = Attrs, children = SubEls};
_ -> find_xdata_el1(Els)
diff --git a/src/mod_vcard_mnesia.erl b/src/mod_vcard_mnesia.erl
new file mode 100644
index 00000000..781a135c
--- /dev/null
+++ b/src/mod_vcard_mnesia.erl
@@ -0,0 +1,213 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_vcard_mnesia).
+
+-behaviour(mod_vcard).
+
+%% API
+-export([init/2, import/2, get_vcard/2, set_vcard/4, search/4, remove_user/2]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("mod_vcard.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(vcard,
+ [{disc_only_copies, [node()]},
+ {attributes, record_info(fields, vcard)}]),
+ mnesia:create_table(vcard_search,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, vcard_search)}]),
+ update_tables(),
+ mnesia:add_table_index(vcard_search, luser),
+ mnesia:add_table_index(vcard_search, lfn),
+ mnesia:add_table_index(vcard_search, lfamily),
+ mnesia:add_table_index(vcard_search, lgiven),
+ mnesia:add_table_index(vcard_search, lmiddle),
+ mnesia:add_table_index(vcard_search, lnickname),
+ mnesia:add_table_index(vcard_search, lbday),
+ mnesia:add_table_index(vcard_search, lctry),
+ mnesia:add_table_index(vcard_search, llocality),
+ mnesia:add_table_index(vcard_search, lemail),
+ mnesia:add_table_index(vcard_search, lorgname),
+ mnesia:add_table_index(vcard_search, lorgunit).
+
+get_vcard(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () -> mnesia:read({vcard, US}) end,
+ case mnesia:transaction(F) of
+ {atomic, Rs} ->
+ lists:map(fun (R) -> R#vcard.vcard end, Rs);
+ {aborted, _Reason} -> error
+ end.
+
+set_vcard(LUser, LServer, VCARD, VCardSearch) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ mnesia:write(#vcard{us = US, vcard = VCARD}),
+ mnesia:write(VCardSearch)
+ end,
+ mnesia:transaction(F).
+
+search(LServer, Data, AllowReturnAll, MaxMatch) ->
+ MatchSpec = make_matchspec(LServer, Data),
+ if (MatchSpec == #vcard_search{_ = '_'}) and
+ not AllowReturnAll ->
+ [];
+ true ->
+ case catch mnesia:dirty_select(vcard_search,
+ [{MatchSpec, [], ['$_']}]) of
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("~p", [Reason]), [];
+ Rs ->
+ case MaxMatch of
+ infinity ->
+ Rs;
+ Val ->
+ lists:sublist(Rs, Val)
+ end
+ end
+ end.
+
+remove_user(LUser, LServer) ->
+ US = {LUser, LServer},
+ F = fun () ->
+ mnesia:delete({vcard, US}),
+ mnesia:delete({vcard_search, US})
+ end,
+ mnesia:transaction(F).
+
+import(_LServer, #vcard{} = VCard) ->
+ mnesia:dirty_write(VCard);
+import(_LServer, #vcard_search{} = S) ->
+ mnesia:dirty_write(S).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_tables() ->
+ update_vcard_table(),
+ update_vcard_search_table().
+
+update_vcard_table() ->
+ Fields = record_info(fields, vcard),
+ case mnesia:table_info(vcard, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ vcard, Fields, set,
+ fun(#vcard{us = {U, _}}) -> U end,
+ fun(#vcard{us = {U, S}, vcard = El} = R) ->
+ R#vcard{us = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ vcard = fxml:to_xmlel(El)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating vcard table", []),
+ mnesia:transform_table(vcard, ignore, Fields)
+ end.
+
+update_vcard_search_table() ->
+ Fields = record_info(fields, vcard_search),
+ case mnesia:table_info(vcard_search, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ vcard_search, Fields, set,
+ fun(#vcard_search{us = {U, _}}) -> U end,
+ fun(#vcard_search{} = VS) ->
+ [vcard_search | L] = tuple_to_list(VS),
+ NewL = lists:map(
+ fun({U, S}) ->
+ {iolist_to_binary(U),
+ iolist_to_binary(S)};
+ (Str) ->
+ iolist_to_binary(Str)
+ end, L),
+ list_to_tuple([vcard_search | NewL])
+ end);
+ _ ->
+ ?INFO_MSG("Recreating vcard_search table", []),
+ mnesia:transform_table(vcard_search, ignore, Fields)
+ end.
+
+make_matchspec(LServer, Data) ->
+ GlobMatch = #vcard_search{_ = '_'},
+ Match = filter_fields(Data, GlobMatch, LServer),
+ Match.
+
+filter_fields([], Match, _LServer) ->
+ Match;
+filter_fields([{SVar, [Val]} | Ds], Match, LServer)
+ when is_binary(Val) and (Val /= <<"">>) ->
+ LVal = mod_vcard:string2lower(Val),
+ NewMatch = case SVar of
+ <<"user">> ->
+ case gen_mod:get_module_opt(LServer, ?MODULE,
+ search_all_hosts,
+ fun(B) when is_boolean(B) ->
+ B
+ end, true) of
+ true -> Match#vcard_search{luser = make_val(LVal)};
+ false ->
+ Host = find_my_host(LServer),
+ Match#vcard_search{us = {make_val(LVal), Host}}
+ end;
+ <<"fn">> -> Match#vcard_search{lfn = make_val(LVal)};
+ <<"last">> ->
+ Match#vcard_search{lfamily = make_val(LVal)};
+ <<"first">> ->
+ Match#vcard_search{lgiven = make_val(LVal)};
+ <<"middle">> ->
+ Match#vcard_search{lmiddle = make_val(LVal)};
+ <<"nick">> ->
+ Match#vcard_search{lnickname = make_val(LVal)};
+ <<"bday">> ->
+ Match#vcard_search{lbday = make_val(LVal)};
+ <<"ctry">> ->
+ Match#vcard_search{lctry = make_val(LVal)};
+ <<"locality">> ->
+ Match#vcard_search{llocality = make_val(LVal)};
+ <<"email">> ->
+ Match#vcard_search{lemail = make_val(LVal)};
+ <<"orgname">> ->
+ Match#vcard_search{lorgname = make_val(LVal)};
+ <<"orgunit">> ->
+ Match#vcard_search{lorgunit = make_val(LVal)};
+ _ -> Match
+ end,
+ filter_fields(Ds, NewMatch, LServer);
+filter_fields([_ | Ds], Match, LServer) ->
+ filter_fields(Ds, Match, LServer).
+
+make_val(Val) ->
+ case str:suffix(<<"*">>, Val) of
+ true -> [str:substr(Val, 1, byte_size(Val) - 1)] ++ '_';
+ _ -> Val
+ end.
+
+find_my_host(LServer) ->
+ Parts = str:tokens(LServer, <<".">>),
+ find_my_host(Parts, ?MYHOSTS).
+
+find_my_host([], _Hosts) -> ?MYNAME;
+find_my_host([_ | Tail] = Parts, Hosts) ->
+ Domain = parts_to_string(Parts),
+ case lists:member(Domain, Hosts) of
+ true -> Domain;
+ false -> find_my_host(Tail, Hosts)
+ end.
+
+parts_to_string(Parts) ->
+ str:strip(list_to_binary(
+ lists:map(fun (S) -> <<S/binary, $.>> end, Parts)),
+ right, $.).
diff --git a/src/mod_vcard_riak.erl b/src/mod_vcard_riak.erl
new file mode 100644
index 00000000..38634738
--- /dev/null
+++ b/src/mod_vcard_riak.erl
@@ -0,0 +1,151 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_vcard_riak).
+
+-behaviour(mod_vcard).
+
+%% API
+-export([init/2, get_vcard/2, set_vcard/4, search/4, remove_user/2,
+ import/2]).
+
+-include("jlib.hrl").
+-include("mod_vcard.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+get_vcard(LUser, LServer) ->
+ case ejabberd_riak:get(vcard, vcard_schema(), {LUser, LServer}) of
+ {ok, R} ->
+ [R#vcard.vcard];
+ {error, notfound} ->
+ [];
+ _ ->
+ error
+ end.
+
+set_vcard(LUser, LServer, VCARD,
+ #vcard_search{user = {User, _},
+ fn = FN,
+ lfn = LFN,
+ family = Family,
+ lfamily = LFamily,
+ given = Given,
+ lgiven = LGiven,
+ middle = Middle,
+ lmiddle = LMiddle,
+ nickname = Nickname,
+ lnickname = LNickname,
+ bday = BDay,
+ lbday = LBDay,
+ ctry = CTRY,
+ lctry = LCTRY,
+ locality = Locality,
+ llocality = LLocality,
+ email = EMail,
+ lemail = LEMail,
+ orgname = OrgName,
+ lorgname = LOrgName,
+ orgunit = OrgUnit,
+ lorgunit = LOrgUnit}) ->
+ US = {LUser, LServer},
+ {atomic,
+ ejabberd_riak:put(#vcard{us = US, vcard = VCARD},
+ vcard_schema(),
+ [{'2i', [{<<"user">>, User},
+ {<<"luser">>, LUser},
+ {<<"fn">>, FN},
+ {<<"lfn">>, LFN},
+ {<<"family">>, Family},
+ {<<"lfamily">>, LFamily},
+ {<<"given">>, Given},
+ {<<"lgiven">>, LGiven},
+ {<<"middle">>, Middle},
+ {<<"lmiddle">>, LMiddle},
+ {<<"nickname">>, Nickname},
+ {<<"lnickname">>, LNickname},
+ {<<"bday">>, BDay},
+ {<<"lbday">>, LBDay},
+ {<<"ctry">>, CTRY},
+ {<<"lctry">>, LCTRY},
+ {<<"locality">>, Locality},
+ {<<"llocality">>, LLocality},
+ {<<"email">>, EMail},
+ {<<"lemail">>, LEMail},
+ {<<"orgname">>, OrgName},
+ {<<"lorgname">>, LOrgName},
+ {<<"orgunit">>, OrgUnit},
+ {<<"lorgunit">>, LOrgUnit}]}])}.
+
+search(_LServer, _Data, _AllowReturnAll, _MaxMatch) ->
+ [].
+
+remove_user(LUser, LServer) ->
+ {atomic, ejabberd_riak:delete(vcard, {LUser, LServer})}.
+
+import(_LServer, #vcard{us = {LUser, LServer}, vcard = El} = VCard) ->
+ #vcard_search{fn = FN,
+ lfn = LFN,
+ family = Family,
+ lfamily = LFamily,
+ given = Given,
+ lgiven = LGiven,
+ middle = Middle,
+ lmiddle = LMiddle,
+ nickname = Nickname,
+ lnickname = LNickname,
+ bday = BDay,
+ lbday = LBDay,
+ ctry = CTRY,
+ lctry = LCTRY,
+ locality = Locality,
+ llocality = LLocality,
+ email = EMail,
+ lemail = LEMail,
+ orgname = OrgName,
+ lorgname = LOrgName,
+ orgunit = OrgUnit,
+ lorgunit = LOrgUnit} =
+ mod_vcard:make_vcard_search(LUser, LUser, LServer, El),
+ ejabberd_riak:put(VCard, vcard_schema(),
+ [{'2i', [{<<"user">>, LUser},
+ {<<"luser">>, LUser},
+ {<<"fn">>, FN},
+ {<<"lfn">>, LFN},
+ {<<"family">>, Family},
+ {<<"lfamily">>, LFamily},
+ {<<"given">>, Given},
+ {<<"lgiven">>, LGiven},
+ {<<"middle">>, Middle},
+ {<<"lmiddle">>, LMiddle},
+ {<<"nickname">>, Nickname},
+ {<<"lnickname">>, LNickname},
+ {<<"bday">>, BDay},
+ {<<"lbday">>, LBDay},
+ {<<"ctry">>, CTRY},
+ {<<"lctry">>, LCTRY},
+ {<<"locality">>, Locality},
+ {<<"llocality">>, LLocality},
+ {<<"email">>, EMail},
+ {<<"lemail">>, LEMail},
+ {<<"orgname">>, OrgName},
+ {<<"lorgname">>, LOrgName},
+ {<<"orgunit">>, OrgUnit},
+ {<<"lorgunit">>, LOrgUnit}]}]);
+import(_LServer, #vcard_search{}) ->
+ ok.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+vcard_schema() ->
+ {record_info(fields, vcard), #vcard{}}.
diff --git a/src/mod_vcard_sql.erl b/src/mod_vcard_sql.erl
new file mode 100644
index 00000000..9fcd5cd5
--- /dev/null
+++ b/src/mod_vcard_sql.erl
@@ -0,0 +1,268 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_vcard_sql).
+
+-compile([{parse_transform, ejabberd_sql_pt}]).
+
+-behaviour(mod_vcard).
+
+%% API
+-export([init/2, get_vcard/2, set_vcard/4, search/4, remove_user/2,
+ import/1, import/2, export/1]).
+
+-include("jlib.hrl").
+-include("mod_vcard.hrl").
+-include("logger.hrl").
+-include("ejabberd_sql_pt.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+get_vcard(LUser, LServer) ->
+ case catch sql_queries:get_vcard(LServer, LUser) of
+ {selected, [{SVCARD}]} ->
+ case fxml_stream:parse_element(SVCARD) of
+ {error, _Reason} -> error;
+ VCARD -> [VCARD]
+ end;
+ {selected, []} -> [];
+ _ -> error
+ end.
+
+set_vcard(LUser, LServer, VCARD,
+ #vcard_search{user = {User, _},
+ fn = FN,
+ lfn = LFN,
+ family = Family,
+ lfamily = LFamily,
+ given = Given,
+ lgiven = LGiven,
+ middle = Middle,
+ lmiddle = LMiddle,
+ nickname = Nickname,
+ lnickname = LNickname,
+ bday = BDay,
+ lbday = LBDay,
+ ctry = CTRY,
+ lctry = LCTRY,
+ locality = Locality,
+ llocality = LLocality,
+ email = EMail,
+ lemail = LEMail,
+ orgname = OrgName,
+ lorgname = LOrgName,
+ orgunit = OrgUnit,
+ lorgunit = LOrgUnit}) ->
+ SVCARD = fxml:element_to_binary(VCARD),
+ sql_queries:set_vcard(LServer, LUser, BDay, CTRY,
+ EMail, FN, Family, Given, LBDay,
+ LCTRY, LEMail, LFN, LFamily,
+ LGiven, LLocality, LMiddle,
+ LNickname, LOrgName, LOrgUnit,
+ Locality, Middle, Nickname, OrgName,
+ OrgUnit, SVCARD, User).
+
+search(LServer, Data, AllowReturnAll, MaxMatch) ->
+ MatchSpec = make_matchspec(LServer, Data),
+ if (MatchSpec == <<"">>) and not AllowReturnAll -> [];
+ true ->
+ Limit = case MaxMatch of
+ infinity ->
+ <<"">>;
+ Val ->
+ [<<" LIMIT ">>, jlib:integer_to_binary(Val)]
+ end,
+ case catch ejabberd_sql:sql_query(
+ LServer,
+ [<<"select username, fn, family, given, "
+ "middle, nickname, bday, ctry, "
+ "locality, email, orgname, orgunit "
+ "from vcard_search ">>,
+ MatchSpec, Limit, <<";">>]) of
+ {selected,
+ [<<"username">>, <<"fn">>, <<"family">>, <<"given">>,
+ <<"middle">>, <<"nickname">>, <<"bday">>, <<"ctry">>,
+ <<"locality">>, <<"email">>, <<"orgname">>,
+ <<"orgunit">>], Rs} when is_list(Rs) ->
+ Rs;
+ Error ->
+ ?ERROR_MSG("~p", [Error]), []
+ end
+ end.
+
+remove_user(LUser, LServer) ->
+ ejabberd_sql:sql_transaction(
+ LServer,
+ fun() ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from vcard where username=%(LUser)s")),
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from vcard_search where lusername=%(LUser)s"))
+ end).
+
+export(_Server) ->
+ [{vcard,
+ fun(Host, #vcard{us = {LUser, LServer}, vcard = VCARD})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ SVCARD =
+ ejabberd_sql:escape(fxml:element_to_binary(VCARD)),
+ [[<<"delete from vcard where username='">>, Username, <<"';">>],
+ [<<"insert into vcard(username, vcard) values ('">>,
+ Username, <<"', '">>, SVCARD, <<"');">>]];
+ (_Host, _R) ->
+ []
+ end},
+ {vcard_search,
+ fun(Host, #vcard_search{user = {User, LServer}, luser = LUser,
+ fn = FN, lfn = LFN, family = Family,
+ lfamily = LFamily, given = Given,
+ lgiven = LGiven, middle = Middle,
+ lmiddle = LMiddle, nickname = Nickname,
+ lnickname = LNickname, bday = BDay,
+ lbday = LBDay, ctry = CTRY, lctry = LCTRY,
+ locality = Locality, llocality = LLocality,
+ email = EMail, lemail = LEMail,
+ orgname = OrgName, lorgname = LOrgName,
+ orgunit = OrgUnit, lorgunit = LOrgUnit})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(User),
+ LUsername = ejabberd_sql:escape(LUser),
+ SFN = ejabberd_sql:escape(FN),
+ SLFN = ejabberd_sql:escape(LFN),
+ SFamily = ejabberd_sql:escape(Family),
+ SLFamily = ejabberd_sql:escape(LFamily),
+ SGiven = ejabberd_sql:escape(Given),
+ SLGiven = ejabberd_sql:escape(LGiven),
+ SMiddle = ejabberd_sql:escape(Middle),
+ SLMiddle = ejabberd_sql:escape(LMiddle),
+ SNickname = ejabberd_sql:escape(Nickname),
+ SLNickname = ejabberd_sql:escape(LNickname),
+ SBDay = ejabberd_sql:escape(BDay),
+ SLBDay = ejabberd_sql:escape(LBDay),
+ SCTRY = ejabberd_sql:escape(CTRY),
+ SLCTRY = ejabberd_sql:escape(LCTRY),
+ SLocality = ejabberd_sql:escape(Locality),
+ SLLocality = ejabberd_sql:escape(LLocality),
+ SEMail = ejabberd_sql:escape(EMail),
+ SLEMail = ejabberd_sql:escape(LEMail),
+ SOrgName = ejabberd_sql:escape(OrgName),
+ SLOrgName = ejabberd_sql:escape(LOrgName),
+ SOrgUnit = ejabberd_sql:escape(OrgUnit),
+ SLOrgUnit = ejabberd_sql:escape(LOrgUnit),
+ [[<<"delete from vcard_search where lusername='">>,
+ LUsername, <<"';">>],
+ [<<"insert into vcard_search( username, "
+ "lusername, fn, lfn, family, lfamily, "
+ " given, lgiven, middle, lmiddle, "
+ "nickname, lnickname, bday, lbday, "
+ "ctry, lctry, locality, llocality, "
+ " email, lemail, orgname, lorgname, "
+ "orgunit, lorgunit)values (">>,
+ <<" '">>, Username, <<"', '">>, LUsername,
+ <<"', '">>, SFN, <<"', '">>, SLFN,
+ <<"', '">>, SFamily, <<"', '">>, SLFamily,
+ <<"', '">>, SGiven, <<"', '">>, SLGiven,
+ <<"', '">>, SMiddle, <<"', '">>, SLMiddle,
+ <<"', '">>, SNickname, <<"', '">>, SLNickname,
+ <<"', '">>, SBDay, <<"', '">>, SLBDay,
+ <<"', '">>, SCTRY, <<"', '">>, SLCTRY,
+ <<"', '">>, SLocality, <<"', '">>, SLLocality,
+ <<"', '">>, SEMail, <<"', '">>, SLEMail,
+ <<"', '">>, SOrgName, <<"', '">>, SLOrgName,
+ <<"', '">>, SOrgUnit, <<"', '">>, SLOrgUnit,
+ <<"');">>]];
+ (_Host, _R) ->
+ []
+ end}].
+
+import(LServer) ->
+ [{<<"select username, vcard from vcard;">>,
+ fun([LUser, SVCard]) ->
+ #xmlel{} = VCARD = fxml_stream:parse_element(SVCard),
+ #vcard{us = {LUser, LServer}, vcard = VCARD}
+ end},
+ {<<"select username, lusername, fn, lfn, family, lfamily, "
+ "given, lgiven, middle, lmiddle, nickname, lnickname, "
+ "bday, lbday, ctry, lctry, locality, llocality, email, "
+ "lemail, orgname, lorgname, orgunit, lorgunit from vcard_search;">>,
+ fun([User, LUser, FN, LFN,
+ Family, LFamily, Given, LGiven,
+ Middle, LMiddle, Nickname, LNickname,
+ BDay, LBDay, CTRY, LCTRY, Locality, LLocality,
+ EMail, LEMail, OrgName, LOrgName, OrgUnit, LOrgUnit]) ->
+ #vcard_search{us = {LUser, LServer},
+ user = {User, LServer}, luser = LUser,
+ fn = FN, lfn = LFN, family = Family,
+ lfamily = LFamily, given = Given,
+ lgiven = LGiven, middle = Middle,
+ lmiddle = LMiddle, nickname = Nickname,
+ lnickname = LNickname, bday = BDay,
+ lbday = LBDay, ctry = CTRY, lctry = LCTRY,
+ locality = Locality, llocality = LLocality,
+ email = EMail, lemail = LEMail,
+ orgname = OrgName, lorgname = LOrgName,
+ orgunit = OrgUnit, lorgunit = LOrgUnit}
+ end}].
+
+import(_, _) ->
+ pass.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+make_matchspec(LServer, Data) ->
+ filter_fields(Data, <<"">>, LServer).
+
+filter_fields([], Match, _LServer) ->
+ case Match of
+ <<"">> -> <<"">>;
+ _ -> [<<" where ">>, Match]
+ end;
+filter_fields([{SVar, [Val]} | Ds], Match, LServer)
+ when is_binary(Val) and (Val /= <<"">>) ->
+ LVal = mod_vcard:string2lower(Val),
+ NewMatch = case SVar of
+ <<"user">> -> make_val(Match, <<"lusername">>, LVal);
+ <<"fn">> -> make_val(Match, <<"lfn">>, LVal);
+ <<"last">> -> make_val(Match, <<"lfamily">>, LVal);
+ <<"first">> -> make_val(Match, <<"lgiven">>, LVal);
+ <<"middle">> -> make_val(Match, <<"lmiddle">>, LVal);
+ <<"nick">> -> make_val(Match, <<"lnickname">>, LVal);
+ <<"bday">> -> make_val(Match, <<"lbday">>, LVal);
+ <<"ctry">> -> make_val(Match, <<"lctry">>, LVal);
+ <<"locality">> ->
+ make_val(Match, <<"llocality">>, LVal);
+ <<"email">> -> make_val(Match, <<"lemail">>, LVal);
+ <<"orgname">> -> make_val(Match, <<"lorgname">>, LVal);
+ <<"orgunit">> -> make_val(Match, <<"lorgunit">>, LVal);
+ _ -> Match
+ end,
+ filter_fields(Ds, NewMatch, LServer);
+filter_fields([_ | Ds], Match, LServer) ->
+ filter_fields(Ds, Match, LServer).
+
+make_val(Match, Field, Val) ->
+ Condition = case str:suffix(<<"*">>, Val) of
+ true ->
+ Val1 = str:substr(Val, 1, byte_size(Val) - 1),
+ SVal = <<(ejabberd_sql:escape_like(Val1))/binary,
+ "%">>,
+ [Field, <<" LIKE '">>, SVal, <<"'">>];
+ _ ->
+ SVal = ejabberd_sql:escape(Val),
+ [Field, <<" = '">>, SVal, <<"'">>]
+ end,
+ case Match of
+ <<"">> -> Condition;
+ _ -> [Match, <<" and ">>, Condition]
+ end.
diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl
index 96ee09d8..198312c3 100644
--- a/src/mod_vcard_xupdate.erl
+++ b/src/mod_vcard_xupdate.erl
@@ -17,26 +17,22 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-
+-include("mod_vcard_xupdate.hrl").
-include("jlib.hrl").
--record(vcard_xupdate, {us = {<<>>, <<>>} :: {binary(), binary()},
- hash = <<>> :: binary()}).
+-callback init(binary(), gen_mod:opts()) -> any().
+-callback import(binary(), #vcard_xupdate{}) -> ok | pass.
+-callback add_xupdate(binary(), binary(), binary()) -> {atomic, any()}.
+-callback get_xupdate(binary(), binary()) -> binary() | undefined.
+-callback remove_xupdate(binary(), binary()) -> {atomic, any()}.
%%====================================================================
%% gen_mod callbacks
%%====================================================================
start(Host, Opts) ->
- case gen_mod:db_type(Host, Opts) of
- mnesia ->
- mnesia:create_table(vcard_xupdate,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, vcard_xupdate)}]),
- update_table();
- _ -> ok
- end,
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
ejabberd_hooks:add(c2s_update_presence, Host, ?MODULE,
update_presence, 100),
ejabberd_hooks:add(vcard_set, Host, ?MODULE, vcard_set,
@@ -56,7 +52,7 @@ stop(Host) ->
update_presence(#xmlel{name = <<"presence">>, attrs = Attrs} = Packet,
User, Host) ->
- case xml:get_attr_s(<<"type">>, Attrs) of
+ case fxml:get_attr_s(<<"type">>, Attrs) of
<<>> -> presence_with_xupdate(Packet, User, Host);
_ -> Packet
end;
@@ -64,7 +60,7 @@ update_presence(Packet, _User, _Host) -> Packet.
vcard_set(LUser, LServer, VCARD) ->
US = {LUser, LServer},
- case xml:get_path_s(VCARD,
+ case fxml:get_path_s(VCARD,
[{elem, <<"PHOTO">>}, {elem, <<"BINVAL">>}, cdata])
of
<<>> -> remove_xupdate(LUser, LServer);
@@ -79,75 +75,16 @@ vcard_set(LUser, LServer, VCARD) ->
%%====================================================================
add_xupdate(LUser, LServer, Hash) ->
- add_xupdate(LUser, LServer, Hash,
- gen_mod:db_type(LServer, ?MODULE)).
-
-add_xupdate(LUser, LServer, Hash, mnesia) ->
- F = fun () ->
- mnesia:write(#vcard_xupdate{us = {LUser, LServer},
- hash = Hash})
- end,
- mnesia:transaction(F);
-add_xupdate(LUser, LServer, Hash, riak) ->
- {atomic, ejabberd_riak:put(#vcard_xupdate{us = {LUser, LServer},
- hash = Hash},
- vcard_xupdate_schema())};
-add_xupdate(LUser, LServer, Hash, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- SHash = ejabberd_odbc:escape(Hash),
- F = fun () ->
- odbc_queries:update_t(<<"vcard_xupdate">>,
- [<<"username">>, <<"hash">>],
- [Username, SHash],
- [<<"username='">>, Username, <<"'">>])
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:add_xupdate(LUser, LServer, Hash).
get_xupdate(LUser, LServer) ->
- get_xupdate(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-get_xupdate(LUser, LServer, mnesia) ->
- case mnesia:dirty_read(vcard_xupdate, {LUser, LServer})
- of
- [#vcard_xupdate{hash = Hash}] -> Hash;
- _ -> undefined
- end;
-get_xupdate(LUser, LServer, riak) ->
- case ejabberd_riak:get(vcard_xupdate, vcard_xupdate_schema(),
- {LUser, LServer}) of
- {ok, #vcard_xupdate{hash = Hash}} -> Hash;
- _ -> undefined
- end;
-get_xupdate(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- case ejabberd_odbc:sql_query(LServer,
- [<<"select hash from vcard_xupdate where "
- "username='">>,
- Username, <<"';">>])
- of
- {selected, [<<"hash">>], [[Hash]]} -> Hash;
- _ -> undefined
- end.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_xupdate(LUser, LServer).
remove_xupdate(LUser, LServer) ->
- remove_xupdate(LUser, LServer,
- gen_mod:db_type(LServer, ?MODULE)).
-
-remove_xupdate(LUser, LServer, mnesia) ->
- F = fun () ->
- mnesia:delete({vcard_xupdate, {LUser, LServer}})
- end,
- mnesia:transaction(F);
-remove_xupdate(LUser, LServer, riak) ->
- {atomic, ejabberd_riak:delete(vcard_xupdate, {LUser, LServer})};
-remove_xupdate(LUser, LServer, odbc) ->
- Username = ejabberd_odbc:escape(LUser),
- F = fun () ->
- ejabberd_odbc:sql_query_t([<<"delete from vcard_xupdate where username='">>,
- Username, <<"';">>])
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_xupdate(LUser, LServer).
%%%----------------------------------------------------------------------
%%% Presence stanza rebuilding
@@ -184,53 +121,17 @@ build_xphotoel(User, Host) ->
attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}],
children = PhotoEl}.
-vcard_xupdate_schema() ->
- {record_info(fields, vcard_xupdate), #vcard_xupdate{}}.
-
-update_table() ->
- Fields = record_info(fields, vcard_xupdate),
- case mnesia:table_info(vcard_xupdate, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- vcard_xupdate, Fields, set,
- fun(#vcard_xupdate{us = {U, _}}) -> U end,
- fun(#vcard_xupdate{us = {U, S}, hash = Hash} = R) ->
- R#vcard_xupdate{us = {iolist_to_binary(U),
- iolist_to_binary(S)},
- hash = iolist_to_binary(Hash)}
- end);
- _ ->
- ?INFO_MSG("Recreating vcard_xupdate table", []),
- mnesia:transform_table(vcard_xupdate, ignore, Fields)
- end.
-
-export(_Server) ->
- [{vcard_xupdate,
- fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash})
- when LServer == Host ->
- Username = ejabberd_odbc:escape(LUser),
- SHash = ejabberd_odbc:escape(Hash),
- [[<<"delete from vcard_xupdate where username='">>,
- Username, <<"';">>],
- [<<"insert into vcard_xupdate(username, "
- "hash) values ('">>,
- Username, <<"', '">>, SHash, <<"');">>]];
- (_Host, _R) ->
- []
- end}].
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
import(LServer) ->
- [{<<"select username, hash from vcard_xupdate;">>,
- fun([LUser, Hash]) ->
- #vcard_xupdate{us = {LUser, LServer}, hash = Hash}
- end}].
-
-import(_LServer, mnesia, #vcard_xupdate{} = R) ->
- mnesia:dirty_write(R);
-import(_LServer, riak, #vcard_xupdate{} = R) ->
- ejabberd_riak:put(R, vcard_xupdate_schema());
-import(_, _, _) ->
- pass.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
+
+import(LServer, DBType, LA) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:import(LServer, LA).
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
mod_opt_type(_) -> [db_type].
diff --git a/src/mod_vcard_xupdate_mnesia.erl b/src/mod_vcard_xupdate_mnesia.erl
new file mode 100644
index 00000000..f1b1693e
--- /dev/null
+++ b/src/mod_vcard_xupdate_mnesia.erl
@@ -0,0 +1,69 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_vcard_xupdate_mnesia).
+-behaviour(mod_vcard_xupdate).
+
+%% API
+-export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2]).
+
+-include("mod_vcard_xupdate.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ mnesia:create_table(vcard_xupdate,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, vcard_xupdate)}]),
+ update_table().
+
+add_xupdate(LUser, LServer, Hash) ->
+ F = fun () ->
+ mnesia:write(#vcard_xupdate{us = {LUser, LServer},
+ hash = Hash})
+ end,
+ mnesia:transaction(F).
+
+get_xupdate(LUser, LServer) ->
+ case mnesia:dirty_read(vcard_xupdate, {LUser, LServer})
+ of
+ [#vcard_xupdate{hash = Hash}] -> Hash;
+ _ -> undefined
+ end.
+
+remove_xupdate(LUser, LServer) ->
+ F = fun () ->
+ mnesia:delete({vcard_xupdate, {LUser, LServer}})
+ end,
+ mnesia:transaction(F).
+
+import(_LServer, #vcard_xupdate{} = R) ->
+ mnesia:dirty_write(R).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+update_table() ->
+ Fields = record_info(fields, vcard_xupdate),
+ case mnesia:table_info(vcard_xupdate, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ vcard_xupdate, Fields, set,
+ fun(#vcard_xupdate{us = {U, _}}) -> U end,
+ fun(#vcard_xupdate{us = {U, S}, hash = Hash} = R) ->
+ R#vcard_xupdate{us = {iolist_to_binary(U),
+ iolist_to_binary(S)},
+ hash = iolist_to_binary(Hash)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating vcard_xupdate table", []),
+ mnesia:transform_table(vcard_xupdate, ignore, Fields)
+ end.
diff --git a/src/mod_vcard_xupdate_riak.erl b/src/mod_vcard_xupdate_riak.erl
new file mode 100644
index 00000000..129a0c6a
--- /dev/null
+++ b/src/mod_vcard_xupdate_riak.erl
@@ -0,0 +1,44 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_vcard_xupdate_riak).
+
+%% API
+-export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2]).
+
+-include("mod_vcard_xupdate.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+add_xupdate(LUser, LServer, Hash) ->
+ {atomic, ejabberd_riak:put(#vcard_xupdate{us = {LUser, LServer},
+ hash = Hash},
+ vcard_xupdate_schema())}.
+
+get_xupdate(LUser, LServer) ->
+ case ejabberd_riak:get(vcard_xupdate, vcard_xupdate_schema(),
+ {LUser, LServer}) of
+ {ok, #vcard_xupdate{hash = Hash}} -> Hash;
+ _ -> undefined
+ end.
+
+remove_xupdate(LUser, LServer) ->
+ {atomic, ejabberd_riak:delete(vcard_xupdate, {LUser, LServer})}.
+
+import(_LServer, #vcard_xupdate{} = R) ->
+ ejabberd_riak:put(R, vcard_xupdate_schema()).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+vcard_xupdate_schema() ->
+ {record_info(fields, vcard_xupdate), #vcard_xupdate{}}.
diff --git a/src/mod_vcard_xupdate_sql.erl b/src/mod_vcard_xupdate_sql.erl
new file mode 100644
index 00000000..7f0079dd
--- /dev/null
+++ b/src/mod_vcard_xupdate_sql.erl
@@ -0,0 +1,79 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_vcard_xupdate_sql).
+
+%% API
+-export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2,
+ import/1, export/1]).
+
+-include("mod_vcard_xupdate.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+add_xupdate(LUser, LServer, Hash) ->
+ Username = ejabberd_sql:escape(LUser),
+ SHash = ejabberd_sql:escape(Hash),
+ F = fun () ->
+ sql_queries:update_t(<<"vcard_xupdate">>,
+ [<<"username">>, <<"hash">>],
+ [Username, SHash],
+ [<<"username='">>, Username, <<"'">>])
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+get_xupdate(LUser, LServer) ->
+ Username = ejabberd_sql:escape(LUser),
+ case ejabberd_sql:sql_query(LServer,
+ [<<"select hash from vcard_xupdate where "
+ "username='">>,
+ Username, <<"';">>])
+ of
+ {selected, [<<"hash">>], [[Hash]]} -> Hash;
+ _ -> undefined
+ end.
+
+remove_xupdate(LUser, LServer) ->
+ Username = ejabberd_sql:escape(LUser),
+ F = fun () ->
+ ejabberd_sql:sql_query_t([<<"delete from vcard_xupdate where username='">>,
+ Username, <<"';">>])
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+export(_Server) ->
+ [{vcard_xupdate,
+ fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash})
+ when LServer == Host ->
+ Username = ejabberd_sql:escape(LUser),
+ SHash = ejabberd_sql:escape(Hash),
+ [[<<"delete from vcard_xupdate where username='">>,
+ Username, <<"';">>],
+ [<<"insert into vcard_xupdate(username, "
+ "hash) values ('">>,
+ Username, <<"', '">>, SHash, <<"');">>]];
+ (_Host, _R) ->
+ []
+ end}].
+
+import(LServer) ->
+ [{<<"select username, hash from vcard_xupdate;">>,
+ fun([LUser, Hash]) ->
+ #vcard_xupdate{us = {LUser, LServer}, hash = Hash}
+ end}].
+
+import(_LServer, _) ->
+ pass.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/mod_version.erl b/src/mod_version.erl
index 0e3b96bd..7f7759f2 100644
--- a/src/mod_version.erl
+++ b/src/mod_version.erl
@@ -52,11 +52,12 @@ stop(Host) ->
process_local_iq(_From, To,
#iq{id = _ID, type = Type, xmlns = _XMLNS,
- sub_el = SubEl} =
+ sub_el = SubEl, lang = Lang} =
IQ) ->
case Type of
set ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+ Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+ IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
Host = To#jid.lserver,
OS = case gen_mod:get_module_opt(Host, ?MODULE, show_os,
diff --git a/src/node_buddy.erl b/src/node_buddy.erl
index 1ebef4e1..aeacacda 100644
--- a/src/node_buddy.erl
+++ b/src/node_buddy.erl
@@ -65,7 +65,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, true},
- {presence_based_delivery, false}].
+ {presence_based_delivery, false},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
diff --git a/src/node_club.erl b/src/node_club.erl
index a7ef35bd..64eff779 100644
--- a/src/node_club.erl
+++ b/src/node_club.erl
@@ -65,7 +65,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, true},
- {presence_based_delivery, false}].
+ {presence_based_delivery, false},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
diff --git a/src/node_dag.erl b/src/node_dag.erl
index cbb8e18c..09ee85f9 100644
--- a/src/node_dag.erl
+++ b/src/node_dag.erl
@@ -77,8 +77,9 @@ publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
#pubsub_node{options = Options} ->
case find_opt(node_type, Options) of
collection ->
+ Txt = <<"Publishing items to collection node is not allowed">>,
{error,
- ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"publish">>)};
+ ?ERR_EXTENDED(?ERRT_NOT_ALLOWED(?MYLANG, Txt), <<"publish">>)};
_ ->
node_hometree:publish_item(Nidx, Publisher, Model,
MaxItems, ItemId, Payload)
diff --git a/src/node_dispatch.erl b/src/node_dispatch.erl
index f9bfaf4f..178292d3 100644
--- a/src/node_dispatch.erl
+++ b/src/node_dispatch.erl
@@ -71,7 +71,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, true},
- {presence_based_delivery, false}].
+ {presence_based_delivery, false},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
diff --git a/src/node_flat.erl b/src/node_flat.erl
index 3687bb64..c2efe3e1 100644
--- a/src/node_flat.erl
+++ b/src/node_flat.erl
@@ -84,7 +84,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
- {presence_based_delivery, false}].
+ {presence_based_delivery, false},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
diff --git a/src/node_flat_odbc.erl b/src/node_flat_sql.erl
index 794d3f98..5baabcfc 100644
--- a/src/node_flat_odbc.erl
+++ b/src/node_flat_sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : node_flat_odbc.erl
+%%% File : node_flat_sql.erl
%%% Author : Christophe Romain <christophe.romain@process-one.net>
%%% Purpose : Standard PubSub node plugin with ODBC backend
%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
@@ -29,7 +29,7 @@
%%% types.</p>
%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
--module(node_flat_odbc).
+-module(node_flat_sql).
-behaviour(gen_pubsub_node).
-author('christophe.romain@process-one.net').
@@ -56,14 +56,14 @@
encode_host/1]).
init(_Host, _ServerHost, _Opts) ->
- %%pubsub_subscription_odbc:init(),
+ %%pubsub_subscription_sql:init(),
ok.
terminate(_Host, _ServerHost) ->
ok.
options() ->
- [{odbc, true}, {rsm, true} | node_flat:options()].
+ [{sql, true}, {rsm, true} | node_flat:options()].
features() ->
[<<"rsm">> | node_flat:features()].
@@ -74,14 +74,14 @@ create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
create_node(Nidx, Owner) ->
{_U, _S, _R} = OwnerKey = jid:tolower(jid:remove_resource(Owner)),
State = #pubsub_state{stateid = {OwnerKey, Nidx}, affiliation = owner},
- catch ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ catch ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
"values(">>, state_to_raw(Nidx, State), <<");">>]),
{result, {default, broadcast}}.
delete_node(Nodes) ->
Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
Subscriptions = case catch
- ejabberd_odbc:sql_query_t([<<"select jid, subscriptions "
+ ejabberd_sql:sql_query_t([<<"select jid, subscriptions "
"from pubsub_state where nodeid='">>, Nidx, <<"';">>])
of
{selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
@@ -130,12 +130,12 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
%% % Requesting entity is anonymous
%% {error, ?ERR_FORBIDDEN};
true ->
- %%{result, SubId} = pubsub_subscription_odbc:subscribe_node(Subscriber, Nidx, Options),
+ %%{result, SubId} = pubsub_subscription_sql:subscribe_node(Subscriber, Nidx, Options),
{NewSub, SubId} = case Subscriptions of
[{subscribed, Id}|_] ->
{subscribed, Id};
[] ->
- Id = pubsub_subscription_odbc:make_subid(),
+ Id = pubsub_subscription_sql:make_subid(),
Sub = case AccessModel of
authorize -> pending;
_ -> subscribed
@@ -209,7 +209,7 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
delete_subscription(SubKey, Nidx, {Subscription, SubId}, Affiliation, Subscriptions) ->
NewSubs = Subscriptions -- [{Subscription, SubId}],
- %%pubsub_subscription_odbc:unsubscribe_node(SubKey, Nidx, SubId),
+ %%pubsub_subscription_sql:unsubscribe_node(SubKey, Nidx, SubId),
case {Affiliation, NewSubs} of
{none, []} -> del_state(Nidx, SubKey);
_ -> update_subscription(Nidx, SubKey, NewSubs)
@@ -296,12 +296,12 @@ get_entity_affiliations(Host, Owner) ->
H = encode_host(Host),
J = encode_jid(GenKey),
Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select node, type, i.nodeid, affiliation "
+ ejabberd_sql:sql_query_t([<<"select node, type, i.nodeid, affiliation "
"from pubsub_state i, pubsub_node n where "
"i.nodeid = n.nodeid and jid='">>, J, <<"' and host='">>, H, <<"';">>])
of
{selected, [<<"node">>, <<"type">>, <<"nodeid">>, <<"affiliation">>], RItems} ->
- [{nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]), decode_affiliation(A)}
+ [{nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]), decode_affiliation(A)}
|| [N, T, I, A] <- RItems];
_ ->
[]
@@ -310,7 +310,7 @@ get_entity_affiliations(Host, Owner) ->
get_node_affiliations(Nidx) ->
Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select jid, affiliation from pubsub_state "
+ ejabberd_sql:sql_query_t([<<"select jid, affiliation from pubsub_state "
"where nodeid='">>, Nidx, <<"';">>])
of
{selected, [<<"jid">>, <<"affiliation">>], RItems} ->
@@ -325,7 +325,7 @@ get_affiliation(Nidx, Owner) ->
GenKey = jid:remove_resource(SubKey),
J = encode_jid(GenKey),
Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select affiliation from pubsub_state "
+ ejabberd_sql:sql_query_t([<<"select affiliation from pubsub_state "
"where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
of
{selected, [<<"affiliation">>], [[A]]} ->
@@ -360,11 +360,11 @@ get_entity_subscriptions(Host, Owner) ->
"from pubsub_state i, pubsub_node n "
"where i.nodeid = n.nodeid and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
end,
- Reply = case catch ejabberd_odbc:sql_query_t(Query) of
+ Reply = case catch ejabberd_sql:sql_query_t(Query) of
{selected,
[<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
lists:foldl(fun ([N, T, I, J, S], Acc) ->
- Node = nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]),
+ Node = nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]),
Jid = decode_jid(J),
case decode_subscriptions(S) of
[] ->
@@ -411,11 +411,11 @@ get_entity_subscriptions_for_send_last(Host, Owner) ->
"where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
"and val='on_sub_and_presence' and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
end,
- Reply = case catch ejabberd_odbc:sql_query_t(Query) of
+ Reply = case catch ejabberd_sql:sql_query_t(Query) of
{selected,
[<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
lists:foldl(fun ([N, T, I, J, S], Acc) ->
- Node = nodetree_tree_odbc:raw_to_node(Host, [N, <<"">>, T, I]),
+ Node = nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]),
Jid = decode_jid(J),
case decode_subscriptions(S) of
[] ->
@@ -435,7 +435,7 @@ get_entity_subscriptions_for_send_last(Host, Owner) ->
get_node_subscriptions(Nidx) ->
Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select jid, subscriptions from pubsub_state "
+ ejabberd_sql:sql_query_t([<<"select jid, subscriptions from pubsub_state "
"where nodeid='">>, Nidx, <<"';">>])
of
{selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
@@ -461,7 +461,7 @@ get_subscriptions(Nidx, Owner) ->
SubKey = jid:tolower(Owner),
J = encode_jid(SubKey),
Reply = case catch
- ejabberd_odbc:sql_query_t([<<"select subscriptions from pubsub_state where "
+ ejabberd_sql:sql_query_t([<<"select subscriptions from pubsub_state where "
"nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
of
{selected, [<<"subscriptions">>], [[S]]} ->
@@ -507,14 +507,14 @@ replace_subscription({Sub, SubId}, [{_, SubId} | T], Acc) ->
replace_subscription({Sub, SubId}, T, [{Sub, SubId} | Acc]).
new_subscription(_Nidx, _Owner, Subscription, SubState) ->
- %%{result, SubId} = pubsub_subscription_odbc:subscribe_node(Owner, Nidx, []),
- SubId = pubsub_subscription_odbc:make_subid(),
+ %%{result, SubId} = pubsub_subscription_sql:subscribe_node(Owner, Nidx, []),
+ SubId = pubsub_subscription_sql:make_subid(),
Subscriptions = [{Subscription, SubId} | SubState#pubsub_state.subscriptions],
set_state(SubState#pubsub_state{subscriptions = Subscriptions}),
{Subscription, SubId}.
unsub_with_subid(Nidx, SubId, SubState) ->
- %%pubsub_subscription_odbc:unsubscribe_node(SubState#pubsub_state.stateid, Nidx, SubId),
+ %%pubsub_subscription_sql:unsubscribe_node(SubState#pubsub_state.stateid, Nidx, SubId),
NewSubs = [{S, Sid}
|| {S, Sid} <- SubState#pubsub_state.subscriptions,
SubId =/= Sid],
@@ -561,7 +561,7 @@ get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}
get_states(Nidx) ->
case catch
- ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
+ ejabberd_sql:sql_query_t([<<"select jid, affiliation, subscriptions "
"from pubsub_state where nodeid='">>, Nidx, <<"';">>])
of
{selected,
@@ -591,7 +591,7 @@ get_state(Nidx, JID) ->
get_state_without_itemids(Nidx, JID) ->
J = encode_jid(JID),
case catch
- ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions "
+ ejabberd_sql:sql_query_t([<<"select jid, affiliation, subscriptions "
"from pubsub_state where jid='">>, J, <<"' and nodeid='">>, Nidx, <<"';">>])
of
{selected,
@@ -613,14 +613,14 @@ set_state(Nidx, State) ->
S = encode_subscriptions(State#pubsub_state.subscriptions),
A = encode_affiliation(State#pubsub_state.affiliation),
case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, <<"', affiliation='">>, A,
+ ejabberd_sql:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, <<"', affiliation='">>, A,
<<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
of
{updated, 1} ->
ok;
_ ->
catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
"values('">>,
Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"');">>])
end,
@@ -628,13 +628,13 @@ set_state(Nidx, State) ->
del_state(Nidx, JID) ->
J = encode_jid(JID),
- catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_state where jid='">>,
+ catch ejabberd_sql:sql_query_t([<<"delete from pubsub_state where jid='">>,
J, <<"' and nodeid='">>, Nidx, <<"';">>]),
ok.
%get_items(Nidx, _From) ->
% case catch
-% ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
+% ejabberd_sql:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
% "from pubsub_item where nodeid='">>, Nidx,
% <<"' order by modification desc;">>])
% of
@@ -647,7 +647,7 @@ del_state(Nidx, JID) ->
get_items(Nidx, From, none) ->
MaxItems = case catch
- ejabberd_odbc:sql_query_t([<<"select val from pubsub_node_option "
+ ejabberd_sql:sql_query_t([<<"select val from pubsub_node_option "
"where nodeid='">>, Nidx, <<"' and name='max_items';">>])
of
{selected, [<<"val">>], [[Value]]} ->
@@ -659,7 +659,7 @@ get_items(Nidx, From, none) ->
get_items(Nidx, From, #rsm_in{max = MaxItems});
get_items(Nidx, _From,
#rsm_in{max = M, direction = Direction, id = I, index = IncIndex}) ->
- Max = ejabberd_odbc:escape(jlib:i2l(M)),
+ Max = ejabberd_sql:escape(jlib:i2l(M)),
{Way, Order} = case Direction of
% aft -> {<<"<">>, <<"desc">>};
% before when I == <<>> -> {<<"is not">>, <<"asc">>};
@@ -672,11 +672,11 @@ get_items(Nidx, _From,
[AttrName, Id] = case I of
undefined when IncIndex =/= undefined ->
case catch
- ejabberd_odbc:sql_query_t([<<"select modification from pubsub_item pi "
+ ejabberd_sql:sql_query_t([<<"select modification from pubsub_item pi "
"where exists ( select count(*) as count1 "
"from pubsub_item where nodeid='">>, Nidx,
<<"' and modification > pi.modification having count1 = ">>,
- ejabberd_odbc:escape(jlib:i2l(IncIndex)), <<" );">>])
+ ejabberd_sql:escape(jlib:i2l(IncIndex)), <<" );">>])
of
{selected, [_], [[O]]} ->
[<<"modification">>, <<"'", O/binary, "'">>];
@@ -688,27 +688,36 @@ get_items(Nidx, _From,
<<>> ->
[<<"modification">>, <<"null">>];
I ->
- [A, B] = str:tokens(ejabberd_odbc:escape(jlib:i2l(I)), <<"@">>),
+ [A, B] = str:tokens(ejabberd_sql:escape(jlib:i2l(I)), <<"@">>),
[A, <<"'", B/binary, "'">>]
end,
Count = case catch
- ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, Nidx, <<"';">>])
+ ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, Nidx, <<"';">>])
of
{selected, [_], [[C]]} -> C;
_ -> <<"0">>
end,
- case catch
- ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
- "from pubsub_item where nodeid='">>, Nidx,
- <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
- AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>])
- of
+ Query = fun(mssql, _) ->
+ ejabberd_sql:sql_query_t(
+ [<<"select top ">>, jlib:i2l(Max),
+ <<" itemid, publisher, creation, modification, payload "
+ "from pubsub_item where nodeid='">>, Nidx,
+ <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
+ AttrName, <<" ">>, Order, <<";">>]);
+ (_, _) ->
+ ejabberd_sql:sql_query_t(
+ [<<"select itemid, publisher, creation, modification, payload "
+ "from pubsub_item where nodeid='">>, Nidx,
+ <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
+ AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>])
+ end,
+ case catch ejabberd_sql:sql_query_t(Query) of
{selected,
[<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
case RItems of
[[_, _, _, F, _]|_] ->
Index = case catch
- ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item "
+ ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item "
"where nodeid='">>, Nidx, <<"' and ">>,
AttrName, <<" > '">>, F, <<"';">>])
of
@@ -760,11 +769,20 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM
end.
get_last_items(Nidx, _From, Count) ->
- case catch
- ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, modification, payload "
- "from pubsub_item where nodeid='">>, Nidx,
- <<"' order by modification desc limit ">>, jlib:i2l(Count), <<";">>])
- of
+ Limit = jlib:i2l(Count),
+ Query = fun(mssql, _) ->
+ ejabberd_sql:sql_query_t(
+ [<<"select top ">>, Limit,
+ <<" itemid, publisher, creation, modification, payload "
+ "from pubsub_item where nodeid='">>, Nidx,
+ <<"' order by modification desc ;">>]);
+ (_, _) ->
+ ejabberd_sql:sql_query_t(
+ [<<"select itemid, publisher, creation, modification, payload "
+ "from pubsub_item where nodeid='">>, Nidx,
+ <<"' order by modification desc limit ">>, Limit, <<";">>])
+ end,
+ case catch ejabberd_sql:sql_query_t(Query) of
{selected,
[<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
{result, [raw_to_item(Nidx, RItem) || RItem <- RItems]};
@@ -773,17 +791,19 @@ get_last_items(Nidx, _From, Count) ->
end.
get_item(Nidx, ItemId) ->
- I = ejabberd_odbc:escape(ItemId),
+ I = ejabberd_sql:escape(ItemId),
case catch
- ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, "
+ ejabberd_sql:sql_query_t([<<"select itemid, publisher, creation, "
"modification, payload from pubsub_item "
"where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
of
{selected,
[<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], [RItem]} ->
{result, raw_to_item(Nidx, RItem)};
- _ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ {selected, _, []} ->
+ {error, ?ERR_ITEM_NOT_FOUND};
+ {'EXIT', _} ->
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)}
end.
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
@@ -819,17 +839,17 @@ get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _Sub
set_item(Item) ->
{ItemId, Nidx} = Item#pubsub_item.itemid,
- I = ejabberd_odbc:escape(ItemId),
+ I = ejabberd_sql:escape(ItemId),
{C, _} = Item#pubsub_item.creation,
{M, JID} = Item#pubsub_item.modification,
P = encode_jid(JID),
Payload = Item#pubsub_item.payload,
- XML = ejabberd_odbc:escape(str:join([xml:element_to_binary(X) || X<-Payload], <<>>)),
+ XML = ejabberd_sql:escape(str:join([fxml:element_to_binary(X) || X<-Payload], <<>>)),
S = fun ({T1, T2, T3}) ->
str:join([jlib:i2l(T1, 6), jlib:i2l(T2, 6), jlib:i2l(T3, 6)], <<":">>)
end,
case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_item set publisher='">>, P,
+ ejabberd_sql:sql_query_t([<<"update pubsub_item set publisher='">>, P,
<<"', modification='">>, S(M),
<<"', payload='">>, XML,
<<"' where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
@@ -838,7 +858,7 @@ set_item(Item) ->
ok;
_ ->
catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, "
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, "
"publisher, creation, modification, payload) "
"values('">>, Nidx, <<"', '">>, I, <<"', '">>, P,
<<"', '">>, S(C), <<"', '">>, S(M),
@@ -847,8 +867,8 @@ set_item(Item) ->
ok.
del_item(Nidx, ItemId) ->
- I = ejabberd_odbc:escape(ItemId),
- catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid='">>,
+ I = ejabberd_sql:escape(ItemId),
+ catch ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid='">>,
I, <<"' and nodeid='">>, Nidx, <<"';">>]).
del_items(_, []) ->
@@ -856,9 +876,9 @@ del_items(_, []) ->
del_items(Nidx, [ItemId]) ->
del_item(Nidx, ItemId);
del_items(Nidx, ItemIds) ->
- I = str:join([[<<"'">>, ejabberd_odbc:escape(X), <<"'">>] || X <- ItemIds], <<",">>),
+ I = str:join([[<<"'">>, ejabberd_sql:escape(X), <<"'">>] || X <- ItemIds], <<",">>),
catch
- ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid in (">>,
+ ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid in (">>,
I, <<") and nodeid='">>, Nidx, <<"';">>]).
get_item_name(_Host, _Node, Id) ->
@@ -883,7 +903,7 @@ itemids(Nidx, {U, S, R}) ->
itemids(Nidx, encode_jid({U, S, R}));
itemids(Nidx, SJID) ->
case catch
- ejabberd_odbc:sql_query_t([<<"select itemid from pubsub_item where "
+ ejabberd_sql:sql_query_t([<<"select itemid from pubsub_item where "
"nodeid='">>, Nidx, <<"' and publisher like '">>, SJID,
<<"%' order by modification desc;">>])
of
@@ -896,7 +916,7 @@ itemids(Nidx, SJID) ->
select_affiliation_subscriptions(Nidx, JID) ->
J = encode_jid(JID),
case catch
- ejabberd_odbc:sql_query_t([<<"select affiliation,subscriptions from "
+ ejabberd_sql:sql_query_t([<<"select affiliation,subscriptions from "
"pubsub_state where nodeid='">>,
Nidx, <<"' and jid='">>, J, <<"';">>])
of
@@ -917,7 +937,7 @@ update_affiliation(Nidx, JID, Affiliation) ->
J = encode_jid(JID),
A = encode_affiliation(Affiliation),
case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_state set affiliation='">>,
+ ejabberd_sql:sql_query_t([<<"update pubsub_state set affiliation='">>,
A, <<"' where nodeid='">>, Nidx,
<<"' and jid='">>, J, <<"';">>])
of
@@ -925,7 +945,7 @@ update_affiliation(Nidx, JID, Affiliation) ->
ok;
_ ->
catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
"values('">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '');">>])
end.
@@ -933,14 +953,14 @@ update_subscription(Nidx, JID, Subscription) ->
J = encode_jid(JID),
S = encode_subscriptions(Subscription),
case catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, S,
+ ejabberd_sql:sql_query_t([<<"update pubsub_state set subscriptions='">>, S,
<<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
of
{updated, 1} ->
ok;
_ ->
catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
"values('">>, Nidx, <<"', '">>, J, <<"', 'n', '">>, S, <<"');">>])
end.
@@ -989,14 +1009,14 @@ decode_subscriptions(Subscriptions) ->
-> binary()
).
encode_jid(JID) ->
- ejabberd_odbc:escape(jid:to_string(JID)).
+ ejabberd_sql:escape(jid:to_string(JID)).
-spec(encode_host/1 ::
( Host :: host())
-> binary()
).
encode_host({_U, _S, _R} = LJID) -> encode_jid(LJID);
-encode_host(Host) -> ejabberd_odbc:escape(Host).
+encode_host(Host) -> ejabberd_sql:escape(Host).
-spec(encode_affiliation/1 ::
( Arg :: atom())
@@ -1041,7 +1061,7 @@ raw_to_item(Nidx, [ItemId, SJID, Creation, Modification, XML]) ->
[T1, T2, T3] = str:tokens(Str, <<":">>),
{jlib:l2i(T1), jlib:l2i(T2), jlib:l2i(T3)}
end,
- Payload = case xml_stream:parse_element(XML) of
+ Payload = case fxml_stream:parse_element(XML) of
{error, _Reason} -> [];
El -> [El]
end,
diff --git a/src/node_hometree_odbc.erl b/src/node_hometree_sql.erl
index 6ac5c37b..4ce6f855 100644
--- a/src/node_hometree_odbc.erl
+++ b/src/node_hometree_sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : node_hometree_odbc.erl
+%%% File : node_hometree_sql.erl
%%% Author : Christophe Romain <christophe.romain@process-one.net>
%%% Purpose : Standard tree ordered node plugin with ODBC backend
%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
@@ -23,7 +23,7 @@
%%%
%%%----------------------------------------------------------------------
--module(node_hometree_odbc).
+-module(node_hometree_sql).
-behaviour(gen_pubsub_node).
-author('christophe.romain@process-one.net').
@@ -45,17 +45,17 @@
get_entity_subscriptions_for_send_last/2, get_last_items/3]).
init(Host, ServerHost, Opts) ->
- node_flat_odbc:init(Host, ServerHost, Opts),
+ node_flat_sql:init(Host, ServerHost, Opts),
Owner = mod_pubsub:service_jid(Host),
mod_pubsub:create_node(Host, ServerHost, <<"/home">>, Owner, <<"hometree">>),
mod_pubsub:create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, Owner, <<"hometree">>),
ok.
terminate(Host, ServerHost) ->
- node_flat_odbc:terminate(Host, ServerHost).
+ node_flat_sql:terminate(Host, ServerHost).
options() ->
- [{odbc, true}, {rsm, true} | node_hometree:options()].
+ [{sql, true}, {rsm, true} | node_hometree:options()].
features() ->
[<<"rsm">> | node_hometree:features()].
@@ -64,92 +64,92 @@ create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_flat_odbc:create_node(Nidx, Owner).
+ node_flat_sql:create_node(Nidx, Owner).
delete_node(Nodes) ->
- node_flat_odbc:delete_node(Nodes).
+ node_flat_sql:delete_node(Nodes).
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_flat_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat_sql:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- node_flat_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+ node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_flat_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_flat_odbc:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_flat_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat_sql:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_flat_odbc:purge_node(Nidx, Owner).
+ node_flat_sql:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
- node_flat_odbc:get_entity_affiliations(Host, Owner).
+ node_flat_sql:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
- node_flat_odbc:get_node_affiliations(Nidx).
+ node_flat_sql:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_flat_odbc:get_affiliation(Nidx, Owner).
+ node_flat_sql:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_flat_odbc:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat_sql:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
- node_flat_odbc:get_entity_subscriptions(Host, Owner).
+ node_flat_sql:get_entity_subscriptions(Host, Owner).
get_entity_subscriptions_for_send_last(Host, Owner) ->
- node_flat_odbc:get_entity_subscriptions_for_send_last(Host, Owner).
+ node_flat_sql:get_entity_subscriptions_for_send_last(Host, Owner).
get_node_subscriptions(Nidx) ->
- node_flat_odbc:get_node_subscriptions(Nidx).
+ node_flat_sql:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_flat_odbc:get_subscriptions(Nidx, Owner).
+ node_flat_sql:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_flat_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat_sql:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_flat_odbc:get_pending_nodes(Host, Owner).
+ node_flat_sql:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_flat_odbc:get_states(Nidx).
+ node_flat_sql:get_states(Nidx).
get_state(Nidx, JID) ->
- node_flat_odbc:get_state(Nidx, JID).
+ node_flat_sql:get_state(Nidx, JID).
set_state(State) ->
- node_flat_odbc:set_state(State).
+ node_flat_sql:set_state(State).
get_items(Nidx, From, RSM) ->
- node_flat_odbc:get_items(Nidx, From, RSM).
+ node_flat_sql:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_flat_odbc:get_items(Nidx, JID, AccessModel,
+ node_flat_sql:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
- node_flat_odbc:get_item(Nidx, ItemId).
+ node_flat_sql:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_flat_odbc:get_item(Nidx, ItemId, JID,
+ node_flat_sql:get_item(Nidx, ItemId, JID,
AccessModel, PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_flat_odbc:set_item(Item).
+ node_flat_sql:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_flat_odbc:get_item_name(Host, Node, Id).
+ node_flat_sql:get_item_name(Host, Node, Id).
get_last_items(Nidx, From, Count) ->
- node_flat_odbc:get_last_items(Nidx, From, Count).
+ node_flat_sql:get_last_items(Nidx, From, Count).
node_to_path(Node) ->
node_hometree:node_to_path(Node).
diff --git a/src/node_mb.erl b/src/node_mb.erl
index 1213805c..0bfb51ec 100644
--- a/src/node_mb.erl
+++ b/src/node_mb.erl
@@ -78,7 +78,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
- {presence_based_delivery, true}].
+ {presence_based_delivery, true},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
diff --git a/src/node_mix.erl b/src/node_mix.erl
new file mode 100644
index 00000000..b57d5849
--- /dev/null
+++ b/src/node_mix.erl
@@ -0,0 +1,168 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 8 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(node_mix).
+
+-behaviour(gen_pubsub_node).
+
+%% API
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/7, get_items/3, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1]).
+
+-include("pubsub.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(Host, ServerHost, Opts) ->
+ node_flat:init(Host, ServerHost, Opts).
+
+terminate(Host, ServerHost) ->
+ node_flat:terminate(Host, ServerHost).
+
+options() ->
+ [{deliver_payloads, true},
+ {notify_config, false},
+ {notify_delete, false},
+ {notify_retract, true},
+ {purge_offline, false},
+ {persist_items, true},
+ {max_items, ?MAXITEMS},
+ {subscribe, true},
+ {access_model, open},
+ {roster_groups_allowed, []},
+ {publish_model, open},
+ {notification_type, headline},
+ {max_payload_size, ?MAX_PAYLOAD_SIZE},
+ {send_last_published_item, never},
+ {deliver_notifications, true},
+ {broadcast_all_resources, true},
+ {presence_based_delivery, false},
+ {itemreply, none}].
+
+features() ->
+ [<<"create-nodes">>,
+ <<"delete-nodes">>,
+ <<"delete-items">>,
+ <<"instant-nodes">>,
+ <<"item-ids">>,
+ <<"outcast-affiliation">>,
+ <<"persistent-items">>,
+ <<"publish">>,
+ <<"purge-nodes">>,
+ <<"retract-items">>,
+ <<"retrieve-affiliations">>,
+ <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>,
+ <<"subscribe">>,
+ <<"subscription-notifications">>].
+
+create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
+ node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+
+create_node(Nidx, Owner) ->
+ node_flat:create_node(Nidx, Owner).
+
+delete_node(Removed) ->
+ node_flat:delete_node(Removed).
+
+subscribe_node(Nidx, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ PresenceSubscription, RosterGroup, Options).
+
+unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
+ node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+
+publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+
+remove_extra_items(Nidx, MaxItems, ItemIds) ->
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
+
+delete_item(Nidx, Publisher, PublishModel, ItemId) ->
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
+
+purge_node(Nidx, Owner) ->
+ node_flat:purge_node(Nidx, Owner).
+
+get_entity_affiliations(Host, Owner) ->
+ node_flat:get_entity_affiliations(Host, Owner).
+
+get_node_affiliations(Nidx) ->
+ node_flat:get_node_affiliations(Nidx).
+
+get_affiliation(Nidx, Owner) ->
+ node_flat:get_affiliation(Nidx, Owner).
+
+set_affiliation(Nidx, Owner, Affiliation) ->
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
+
+get_entity_subscriptions(Host, Owner) ->
+ node_flat:get_entity_subscriptions(Host, Owner).
+
+get_node_subscriptions(Nidx) ->
+ node_flat:get_node_subscriptions(Nidx).
+
+get_subscriptions(Nidx, Owner) ->
+ node_flat:get_subscriptions(Nidx, Owner).
+
+set_subscriptions(Nidx, Owner, Subscription, SubId) ->
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
+
+get_pending_nodes(Host, Owner) ->
+ node_flat:get_pending_nodes(Host, Owner).
+
+get_states(Nidx) ->
+ node_flat:get_states(Nidx).
+
+get_state(Nidx, JID) ->
+ node_flat:get_state(Nidx, JID).
+
+set_state(State) ->
+ node_flat:set_state(State).
+
+get_items(Nidx, From, RSM) ->
+ node_flat:get_items(Nidx, From, RSM).
+
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
+ node_flat:get_items(Nidx, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM).
+
+get_item(Nidx, ItemId) ->
+ node_flat:get_item(Nidx, ItemId).
+
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
+
+set_item(Item) ->
+ node_flat:set_item(Item).
+
+get_item_name(Host, Node, Id) ->
+ node_flat:get_item_name(Host, Node, Id).
+
+node_to_path(Node) ->
+ node_flat:node_to_path(Node).
+
+path_to_node(Path) ->
+ node_flat:path_to_node(Path).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/node_mix_sql.erl b/src/node_mix_sql.erl
new file mode 100644
index 00000000..b5b9a94e
--- /dev/null
+++ b/src/node_mix_sql.erl
@@ -0,0 +1,171 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 8 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(node_mix_sql).
+
+-behaviour(gen_pubsub_node).
+
+%% API
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/7, get_items/3, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1, get_entity_subscriptions_for_send_last/2]).
+
+-include("pubsub.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(Host, ServerHost, Opts) ->
+ node_flat_sql:init(Host, ServerHost, Opts).
+
+terminate(Host, ServerHost) ->
+ node_flat_sql:terminate(Host, ServerHost).
+
+options() ->
+ [{deliver_payloads, true},
+ {notify_config, false},
+ {notify_delete, false},
+ {notify_retract, true},
+ {purge_offline, false},
+ {persist_items, true},
+ {max_items, ?MAXITEMS},
+ {subscribe, true},
+ {access_model, open},
+ {roster_groups_allowed, []},
+ {publish_model, open},
+ {notification_type, headline},
+ {max_payload_size, ?MAX_PAYLOAD_SIZE},
+ {send_last_published_item, never},
+ {deliver_notifications, true},
+ {broadcast_all_resources, true},
+ {presence_based_delivery, false},
+ {itemreply, none}].
+
+features() ->
+ [<<"create-nodes">>,
+ <<"delete-nodes">>,
+ <<"delete-items">>,
+ <<"instant-nodes">>,
+ <<"item-ids">>,
+ <<"outcast-affiliation">>,
+ <<"persistent-items">>,
+ <<"publish">>,
+ <<"purge-nodes">>,
+ <<"retract-items">>,
+ <<"retrieve-affiliations">>,
+ <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>,
+ <<"subscribe">>,
+ <<"subscription-notifications">>].
+
+create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
+ node_flat_sql:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+
+create_node(Nidx, Owner) ->
+ node_flat_sql:create_node(Nidx, Owner).
+
+delete_node(Removed) ->
+ node_flat_sql:delete_node(Removed).
+
+subscribe_node(Nidx, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_flat_sql:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ PresenceSubscription, RosterGroup, Options).
+
+unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
+ node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+
+publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
+ node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+
+remove_extra_items(Nidx, MaxItems, ItemIds) ->
+ node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds).
+
+delete_item(Nidx, Publisher, PublishModel, ItemId) ->
+ node_flat_sql:delete_item(Nidx, Publisher, PublishModel, ItemId).
+
+purge_node(Nidx, Owner) ->
+ node_flat_sql:purge_node(Nidx, Owner).
+
+get_entity_affiliations(Host, Owner) ->
+ node_flat_sql:get_entity_affiliations(Host, Owner).
+
+get_node_affiliations(Nidx) ->
+ node_flat_sql:get_node_affiliations(Nidx).
+
+get_affiliation(Nidx, Owner) ->
+ node_flat_sql:get_affiliation(Nidx, Owner).
+
+set_affiliation(Nidx, Owner, Affiliation) ->
+ node_flat_sql:set_affiliation(Nidx, Owner, Affiliation).
+
+get_entity_subscriptions(Host, Owner) ->
+ node_flat_sql:get_entity_subscriptions(Host, Owner).
+
+get_node_subscriptions(Nidx) ->
+ node_flat_sql:get_node_subscriptions(Nidx).
+
+get_subscriptions(Nidx, Owner) ->
+ node_flat_sql:get_subscriptions(Nidx, Owner).
+
+set_subscriptions(Nidx, Owner, Subscription, SubId) ->
+ node_flat_sql:set_subscriptions(Nidx, Owner, Subscription, SubId).
+
+get_pending_nodes(Host, Owner) ->
+ node_flat_sql:get_pending_nodes(Host, Owner).
+
+get_states(Nidx) ->
+ node_flat_sql:get_states(Nidx).
+
+get_state(Nidx, JID) ->
+ node_flat_sql:get_state(Nidx, JID).
+
+set_state(State) ->
+ node_flat_sql:set_state(State).
+
+get_items(Nidx, From, RSM) ->
+ node_flat_sql:get_items(Nidx, From, RSM).
+
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
+ node_flat_sql:get_items(Nidx, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM).
+
+get_item(Nidx, ItemId) ->
+ node_flat_sql:get_item(Nidx, ItemId).
+
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
+ node_flat_sql:get_item(Nidx, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
+
+set_item(Item) ->
+ node_flat_sql:set_item(Item).
+
+get_item_name(Host, Node, Id) ->
+ node_flat_sql:get_item_name(Host, Node, Id).
+
+node_to_path(Node) ->
+ node_flat_sql:node_to_path(Node).
+
+path_to_node(Path) ->
+ node_flat_sql:path_to_node(Path).
+
+get_entity_subscriptions_for_send_last(Host, Owner) ->
+ node_flat_sql:get_entity_subscriptions_for_send_last(Host, Owner).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
diff --git a/src/node_online.erl b/src/node_online.erl
index 1e9e2953..ea492fc8 100644
--- a/src/node_online.erl
+++ b/src/node_online.erl
@@ -76,7 +76,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
- {presence_based_delivery, true}].
+ {presence_based_delivery, true},
+ {itemreply, none}].
features() ->
node_flat:features().
diff --git a/src/node_pep.erl b/src/node_pep.erl
index 726146b9..2ac4da62 100644
--- a/src/node_pep.erl
+++ b/src/node_pep.erl
@@ -72,7 +72,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
- {presence_based_delivery, true}].
+ {presence_based_delivery, true},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
@@ -257,7 +258,7 @@ complain_if_modcaps_disabled(ServerHost) ->
false ->
?WARNING_MSG("The PEP plugin is enabled in mod_pubsub "
"of host ~p. This plugin requires mod_caps "
- "to be enabled, but it isn't.",
+ "but it does not seems enabled, please check config.",
[ServerHost]);
true -> ok
end.
diff --git a/src/node_pep_odbc.erl b/src/node_pep_sql.erl
index 6eb0de04..c5b31d15 100644
--- a/src/node_pep_odbc.erl
+++ b/src/node_pep_sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : node_pep_odbc.erl
+%%% File : node_pep_sql.erl
%%% Author : Christophe Romain <christophe.romain@process-one.net>
%%% Purpose : Standard PubSub PEP plugin with ODBC backend
%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
@@ -26,7 +26,7 @@
%%% @doc The module <strong>{@module}</strong> is the pep PubSub plugin.
%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p>
--module(node_pep_odbc).
+-module(node_pep_sql).
-behaviour(gen_pubsub_node).
-author('christophe.romain@process-one.net').
@@ -49,16 +49,16 @@
get_entity_subscriptions_for_send_last/2, get_last_items/3]).
init(Host, ServerHost, Opts) ->
- node_flat_odbc:init(Host, ServerHost, Opts),
+ node_flat_sql:init(Host, ServerHost, Opts),
complain_if_modcaps_disabled(ServerHost),
ok.
terminate(Host, ServerHost) ->
- node_flat_odbc:terminate(Host, ServerHost),
+ node_flat_sql:terminate(Host, ServerHost),
ok.
options() ->
- [{odbc, true}, {rsm, true} | node_pep:options()].
+ [{sql, true}, {rsm, true} | node_pep:options()].
features() ->
[<<"rsm">> | node_pep:features()].
@@ -67,56 +67,56 @@ create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_pep:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Nidx, Owner) ->
- node_flat_odbc:create_node(Nidx, Owner),
+ node_flat_sql:create_node(Nidx, Owner),
{result, {default, broadcast}}.
delete_node(Nodes) ->
- {result, {_, _, Result}} = node_flat_odbc:delete_node(Nodes),
+ {result, {_, _, Result}} = node_flat_sql:delete_node(Nodes),
{result, {[], Result}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
- node_flat_odbc:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ node_flat_sql:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
- case node_flat_odbc:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
+ case node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId) of
{error, Error} -> {error, Error};
{result, _} -> {result, []}
end.
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
- node_flat_odbc:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+ node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
- node_flat_odbc:remove_extra_items(Nidx, MaxItems, ItemIds).
+ node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
- node_flat_odbc:delete_item(Nidx, Publisher, PublishModel, ItemId).
+ node_flat_sql:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
- node_flat_odbc:purge_node(Nidx, Owner).
+ node_flat_sql:purge_node(Nidx, Owner).
get_entity_affiliations(_Host, Owner) ->
OwnerKey = jid:tolower(jid:remove_resource(Owner)),
- node_flat_odbc:get_entity_affiliations(OwnerKey, Owner).
+ node_flat_sql:get_entity_affiliations(OwnerKey, Owner).
get_node_affiliations(Nidx) ->
- node_flat_odbc:get_node_affiliations(Nidx).
+ node_flat_sql:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
- node_flat_odbc:get_affiliation(Nidx, Owner).
+ node_flat_sql:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
- node_flat_odbc:set_affiliation(Nidx, Owner, Affiliation).
+ node_flat_sql:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(_Host, Owner) ->
SubKey = jid:tolower(Owner),
GenKey = jid:remove_resource(SubKey),
- Host = node_flat_odbc:encode_host(element(2, SubKey)),
- SJ = node_flat_odbc:encode_jid(SubKey),
- GJ = node_flat_odbc:encode_jid(GenKey),
+ Host = node_flat_sql:encode_host(element(2, SubKey)),
+ SJ = node_flat_sql:encode_jid(SubKey),
+ GJ = node_flat_sql:encode_jid(GenKey),
Query = case SubKey of
GenKey ->
[<<"select host, node, type, i.nodeid, jid, "
@@ -129,16 +129,16 @@ get_entity_subscriptions(_Host, Owner) ->
"where i.nodeid = n.nodeid and jid "
"in ('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, <<"';">>]
end,
- Reply = case catch ejabberd_odbc:sql_query_t(Query) of
+ Reply = case catch ejabberd_sql:sql_query_t(Query) of
{selected,
[<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>],
RItems} ->
lists:map(fun ([H, N, T, I, J, S]) ->
- O = node_flat_odbc:decode_jid(H),
- Node = nodetree_tree_odbc:raw_to_node(O, [N, <<"">>, T, I]),
+ O = node_flat_sql:decode_jid(H),
+ Node = nodetree_tree_sql:raw_to_node(O, [N, <<"">>, T, I]),
{Node,
- node_flat_odbc:decode_subscriptions(S),
- node_flat_odbc:decode_jid(J)}
+ node_flat_sql:decode_subscriptions(S),
+ node_flat_sql:decode_jid(J)}
end,
RItems);
_ ->
@@ -149,9 +149,9 @@ get_entity_subscriptions(_Host, Owner) ->
get_entity_subscriptions_for_send_last(_Host, Owner) ->
SubKey = jid:tolower(Owner),
GenKey = jid:remove_resource(SubKey),
- Host = node_flat_odbc:encode_host(element(2, SubKey)),
- SJ = node_flat_odbc:encode_jid(SubKey),
- GJ = node_flat_odbc:encode_jid(GenKey),
+ Host = node_flat_sql:encode_host(element(2, SubKey)),
+ SJ = node_flat_sql:encode_jid(SubKey),
+ GJ = node_flat_sql:encode_jid(GenKey),
Query = case SubKey of
GenKey ->
[<<"select host, node, type, i.nodeid, jid, "
@@ -168,16 +168,16 @@ get_entity_subscriptions_for_send_last(_Host, Owner) ->
"val='on_sub_and_presence' and jid in ",
"('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, <<"';">>]
end,
- Reply = case catch ejabberd_odbc:sql_query_t(Query) of
+ Reply = case catch ejabberd_sql:sql_query_t(Query) of
{selected,
[<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>],
RItems} ->
lists:map(fun ([H, N, T, I, J, S]) ->
- O = node_flat_odbc:decode_jid(H),
- Node = nodetree_tree_odbc:raw_to_node(O, [N, <<"">>, T, I]),
+ O = node_flat_sql:decode_jid(H),
+ Node = nodetree_tree_sql:raw_to_node(O, [N, <<"">>, T, I]),
{Node,
- node_flat_odbc:decode_subscriptions(S),
- node_flat_odbc:decode_jid(J)}
+ node_flat_sql:decode_subscriptions(S),
+ node_flat_sql:decode_jid(J)}
end,
RItems);
_ ->
@@ -186,54 +186,54 @@ get_entity_subscriptions_for_send_last(_Host, Owner) ->
{result, Reply}.
get_node_subscriptions(Nidx) ->
- node_flat_odbc:get_node_subscriptions(Nidx).
+ node_flat_sql:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
- node_flat_odbc:get_subscriptions(Nidx, Owner).
+ node_flat_sql:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
- node_flat_odbc:set_subscriptions(Nidx, Owner, Subscription, SubId).
+ node_flat_sql:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
- node_flat_odbc:get_pending_nodes(Host, Owner).
+ node_flat_sql:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
- node_flat_odbc:get_states(Nidx).
+ node_flat_sql:get_states(Nidx).
get_state(Nidx, JID) ->
- node_flat_odbc:get_state(Nidx, JID).
+ node_flat_sql:get_state(Nidx, JID).
set_state(State) ->
- node_flat_odbc:set_state(State).
+ node_flat_sql:set_state(State).
get_items(Nidx, From, RSM) ->
- node_flat_odbc:get_items(Nidx, From, RSM).
+ node_flat_sql:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
- node_flat_odbc:get_items(Nidx, JID, AccessModel,
+ node_flat_sql:get_items(Nidx, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId, RSM).
get_last_items(Nidx, JID, Count) ->
- node_flat_odbc:get_last_items(Nidx, JID, Count).
+ node_flat_sql:get_last_items(Nidx, JID, Count).
get_item(Nidx, ItemId) ->
- node_flat_odbc:get_item(Nidx, ItemId).
+ node_flat_sql:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
- node_flat_odbc:get_item(Nidx, ItemId, JID, AccessModel,
+ node_flat_sql:get_item(Nidx, ItemId, JID, AccessModel,
PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
- node_flat_odbc:set_item(Item).
+ node_flat_sql:set_item(Item).
get_item_name(Host, Node, Id) ->
- node_flat_odbc:get_item_name(Host, Node, Id).
+ node_flat_sql:get_item_name(Host, Node, Id).
node_to_path(Node) ->
- node_flat_odbc:node_to_path(Node).
+ node_flat_sql:node_to_path(Node).
path_to_node(Path) ->
- node_flat_odbc:path_to_node(Path).
+ node_flat_sql:path_to_node(Path).
%%%
%%% Internal
diff --git a/src/node_private.erl b/src/node_private.erl
index de80d870..79dc5ed8 100644
--- a/src/node_private.erl
+++ b/src/node_private.erl
@@ -65,7 +65,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, false},
- {presence_based_delivery, false}].
+ {presence_based_delivery, false},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
diff --git a/src/node_public.erl b/src/node_public.erl
index df4f1c08..cdb8abca 100644
--- a/src/node_public.erl
+++ b/src/node_public.erl
@@ -65,7 +65,8 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, true},
- {presence_based_delivery, false}].
+ {presence_based_delivery, false},
+ {itemreply, none}].
features() ->
[<<"create-nodes">>,
diff --git a/src/nodetree_dag.erl b/src/nodetree_dag.erl
index 8ac56b27..a105db83 100644
--- a/src/nodetree_dag.erl
+++ b/src/nodetree_dag.erl
@@ -69,13 +69,13 @@ create_node(Key, Node, Type, Owner, Options, Parents) ->
Other -> Other
end;
_ ->
- {error, ?ERR_CONFLICT}
+ {error, ?ERRT_CONFLICT(?MYLANG, <<"Node already exists">>)}
end.
delete_node(Key, Node) ->
case find_node(Key, Node) of
false ->
- {error, ?ERR_ITEM_NOT_FOUND};
+ {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
Record ->
lists:foreach(fun (#pubsub_node{options = Opts} = Child) ->
NewOpts = remove_config_parent(Node, Opts),
@@ -99,7 +99,7 @@ get_node(Host, Node, _From) ->
get_node(Host, Node) ->
case find_node(Host, Node) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
Record -> Record
end.
@@ -115,7 +115,7 @@ get_nodes(Key) ->
get_parentnodes(Host, Node, _From) ->
case find_node(Host, Node) of
false ->
- {error, ?ERR_ITEM_NOT_FOUND};
+ {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
#pubsub_node{parents = Parents} ->
Q = qlc:q([N
|| #pubsub_node{nodeid = {NHost, NNode}} = N
@@ -139,7 +139,7 @@ get_subnodes(Host, <<>>) ->
get_subnodes_helper(Host, <<>>);
get_subnodes(Host, Node) ->
case find_node(Host, Node) of
- false -> {error, ?ERR_ITEM_NOT_FOUND};
+ false -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
_ -> get_subnodes_helper(Host, Node)
end.
@@ -238,7 +238,7 @@ validate_parentage(Key, Owners, [<<>> | T]) ->
validate_parentage(Key, Owners, [ParentID | T]) ->
case find_node(Key, ParentID) of
false ->
- {error, ?ERR_ITEM_NOT_FOUND};
+ {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
#pubsub_node{owners = POwners, options = POptions} ->
NodeType = find_opt(node_type, ?DEFAULT_NODETYPE, POptions),
MutualOwners = [O || O <- Owners, PO <- POwners, O == PO],
diff --git a/src/nodetree_tree.erl b/src/nodetree_tree.erl
index e3f7e251..69b50ff9 100644
--- a/src/nodetree_tree.erl
+++ b/src/nodetree_tree.erl
@@ -74,15 +74,15 @@ get_node(Host, Node, _From) ->
get_node(Host, Node).
get_node(Host, Node) ->
- case catch mnesia:read({pubsub_node, {Host, Node}}) of
+ case mnesia:read({pubsub_node, {Host, Node}}) of
[Record] when is_record(Record, pubsub_node) -> Record;
- _ -> {error, ?ERR_ITEM_NOT_FOUND}
+ _ -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
end.
get_node(Nidx) ->
- case catch mnesia:index_read(pubsub_node, Nidx, #pubsub_node.id) of
+ case mnesia:index_read(pubsub_node, Nidx, #pubsub_node.id) of
[Record] when is_record(Record, pubsub_node) -> Record;
- _ -> {error, ?ERR_ITEM_NOT_FOUND}
+ _ -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
end.
get_nodes(Host, _From) ->
@@ -147,7 +147,7 @@ get_subnodes_tree(Host, Node) ->
create_node(Host, Node, Type, Owner, Options, Parents) ->
BJID = jid:tolower(jid:remove_resource(Owner)),
- case catch mnesia:read({pubsub_node, {Host, Node}}) of
+ case mnesia:read({pubsub_node, {Host, Node}}) of
[] ->
ParentExists = case Host of
{_U, _S, _R} ->
@@ -160,7 +160,7 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
true;
[Parent | _] ->
case catch mnesia:read({pubsub_node, {Host, Parent}}) of
- [#pubsub_node{owners = [{[], Host, []}]}] ->
+ [#pubsub_node{owners = [{<<>>, Host, <<>>}]}] ->
true;
[#pubsub_node{owners = Owners}] ->
lists:member(BJID, Owners);
@@ -183,7 +183,7 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
{error, ?ERR_FORBIDDEN}
end;
_ ->
- {error, ?ERR_CONFLICT}
+ {error, ?ERRT_CONFLICT(?MYLANG, <<"Node already exists">>)}
end.
delete_node(Host, Node) ->
diff --git a/src/nodetree_tree_odbc.erl b/src/nodetree_tree_sql.erl
index ef1c20b6..b5654339 100644
--- a/src/nodetree_tree_odbc.erl
+++ b/src/nodetree_tree_sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : nodetree_tree_odbc.erl
+%%% File : nodetree_tree_sql.erl
%%% Author : Christophe Romain <christophe.romain@process-one.net>
%%% Purpose : Standard node tree plugin with ODBC backend
%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net>
@@ -33,7 +33,7 @@
%%% useable and useful as is. Please, send us comments, feedback and
%%% improvements.</p>
--module(nodetree_tree_odbc).
+-module(nodetree_tree_sql).
-behaviour(gen_pubsub_nodetree).
-author('christophe.romain@process-one.net').
@@ -55,7 +55,7 @@ terminate(_Host, _ServerHost) ->
ok.
options() ->
- [{odbc, true} | nodetree_tree:options()].
+ [{sql, true} | nodetree_tree:options()].
set_node(Record) when is_record(Record, pubsub_node) ->
{Host, Node} = Record#pubsub_node.nodeid,
@@ -64,16 +64,16 @@ set_node(Record) when is_record(Record, pubsub_node) ->
[First | _] -> First
end,
Type = Record#pubsub_node.type,
- H = node_flat_odbc:encode_host(Host),
- N = ejabberd_odbc:escape(Node),
- P = ejabberd_odbc:escape(Parent),
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
+ P = ejabberd_sql:escape(Parent),
Nidx = case nodeidx(Host, Node) of
{result, OldNidx} ->
catch
- ejabberd_odbc:sql_query_t([<<"delete from pubsub_node_option where "
+ ejabberd_sql:sql_query_t([<<"delete from pubsub_node_option where "
"nodeid='">>, OldNidx, <<"';">>]),
catch
- ejabberd_odbc:sql_query_t([<<"update pubsub_node set host='">>,
+ ejabberd_sql:sql_query_t([<<"update pubsub_node set host='">>,
H, <<"' node='">>, N,
<<"' parent='">>, P,
<<"' type='">>, Type,
@@ -82,7 +82,7 @@ set_node(Record) when is_record(Record, pubsub_node) ->
OldNidx;
_ ->
catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_node(host, node, "
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_node(host, node, "
"parent, type) values('">>,
H, <<"', '">>, N, <<"', '">>, P,
<<"', '">>, Type, <<"');">>]),
@@ -93,15 +93,16 @@ set_node(Record) when is_record(Record, pubsub_node) ->
end,
case Nidx of
none ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
+ Txt = <<"Node index not found">>,
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, Txt)};
_ ->
lists:foreach(fun ({Key, Value}) ->
SKey = iolist_to_binary(atom_to_list(Key)),
- SValue = ejabberd_odbc:escape(
+ SValue = ejabberd_sql:escape(
list_to_binary(
lists:flatten(io_lib:fwrite("~p", [Value])))),
catch
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_node_option(nodeid, "
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_node_option(nodeid, "
"name, val) values('">>,
Nidx, <<"', '">>,
SKey, <<"', '">>, SValue, <<"');">>])
@@ -114,10 +115,10 @@ get_node(Host, Node, _From) ->
get_node(Host, Node).
get_node(Host, Node) ->
- H = node_flat_odbc:encode_host(Host),
- N = ejabberd_odbc:escape(Node),
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
case catch
- ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
+ ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
"pubsub_node where host='">>,
H, <<"' and node='">>, N, <<"';">>])
of
@@ -125,14 +126,14 @@ get_node(Host, Node) ->
[<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], [RItem]} ->
raw_to_node(Host, RItem);
{'EXIT', _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)};
_ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
end.
get_node(Nidx) ->
case catch
- ejabberd_odbc:sql_query_t([<<"select host, node, parent, type from "
+ ejabberd_sql:sql_query_t([<<"select host, node, parent, type from "
"pubsub_node where nodeid='">>,
Nidx, <<"';">>])
of
@@ -140,18 +141,18 @@ get_node(Nidx) ->
[<<"host">>, <<"node">>, <<"parent">>, <<"type">>], [[Host, Node, Parent, Type]]} ->
raw_to_node(Host, [Node, Parent, Type, Nidx]);
{'EXIT', _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)};
_ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
end.
get_nodes(Host, _From) ->
get_nodes(Host).
get_nodes(Host) ->
- H = node_flat_odbc:encode_host(Host),
+ H = node_flat_sql:encode_host(Host),
case catch
- ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
+ ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
"pubsub_node where host='">>, H, <<"';">>])
of
{selected,
@@ -176,10 +177,10 @@ get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
get_subnodes(Host, Node) ->
- H = node_flat_odbc:encode_host(Host),
- N = ejabberd_odbc:escape(Node),
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
case catch
- ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
+ ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
"pubsub_node where host='">>,
H, <<"' and parent='">>, N, <<"';">>])
of
@@ -194,10 +195,10 @@ get_subnodes_tree(Host, Node, _From) ->
get_subnodes_tree(Host, Node).
get_subnodes_tree(Host, Node) ->
- H = node_flat_odbc:encode_host(Host),
- N = ejabberd_odbc:escape(Node),
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
case catch
- ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from "
+ ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
"pubsub_node where host='">>,
H, <<"' and node like '">>, N, <<"%';">>])
of
@@ -211,7 +212,7 @@ get_subnodes_tree(Host, Node) ->
create_node(Host, Node, Type, Owner, Options, Parents) ->
BJID = jid:tolower(jid:remove_resource(Owner)),
case nodeidx(Host, Node) of
- {error, ?ERR_ITEM_NOT_FOUND} ->
+ {error, not_found} ->
ParentExists = case Host of
{_U, _S, _R} ->
%% This is special case for PEP handling
@@ -248,23 +249,23 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
{error, ?ERR_FORBIDDEN}
end;
{result, _} ->
- {error, ?ERR_CONFLICT};
- Error ->
- Error
+ {error, ?ERRT_CONFLICT(?MYLANG, <<"Node already exists">>)};
+ {error, db_fail} ->
+ {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)}
end.
delete_node(Host, Node) ->
- H = node_flat_odbc:encode_host(Host),
- N = ejabberd_odbc:escape(Node),
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
Removed = get_subnodes_tree(Host, Node),
- catch ejabberd_odbc:sql_query_t([<<"delete from pubsub_node where host='">>,
+ catch ejabberd_sql:sql_query_t([<<"delete from pubsub_node where host='">>,
H, <<"' and node like '">>, N, <<"%';">>]),
Removed.
%% helpers
raw_to_node(Host, [Node, Parent, Type, Nidx]) ->
Options = case catch
- ejabberd_odbc:sql_query_t([<<"select name,val from pubsub_node_option "
+ ejabberd_sql:sql_query_t([<<"select name,val from pubsub_node_option "
"where nodeid='">>, Nidx, <<"';">>])
of
{selected, [<<"name">>, <<"val">>], ROptions} ->
@@ -275,7 +276,7 @@ raw_to_node(Host, [Node, Parent, Type, Nidx]) ->
{RKey, RValue}
end,
ROptions),
- Module = jlib:binary_to_atom(<<"node_", Type/binary, "_odbc">>),
+ Module = jlib:binary_to_atom(<<"node_", Type/binary, "_sql">>),
StdOpts = Module:options(),
lists:foldl(fun ({Key, Value}, Acc) ->
lists:keyreplace(Key, 1, Acc, {Key, Value})
@@ -293,21 +294,21 @@ raw_to_node(Host, [Node, Parent, Type, Nidx]) ->
id = Nidx, type = Type, options = Options}.
nodeidx(Host, Node) ->
- H = node_flat_odbc:encode_host(Host),
- N = ejabberd_odbc:escape(Node),
+ H = node_flat_sql:encode_host(Host),
+ N = ejabberd_sql:escape(Node),
case catch
- ejabberd_odbc:sql_query_t([<<"select nodeid from pubsub_node where "
+ ejabberd_sql:sql_query_t([<<"select nodeid from pubsub_node where "
"host='">>,
H, <<"' and node='">>, N, <<"';">>])
of
{selected, [<<"nodeid">>], [[Nidx]]} ->
{result, Nidx};
{'EXIT', _Reason} ->
- {error, ?ERR_INTERNAL_SERVER_ERROR};
+ {error, db_fail};
_ ->
- {error, ?ERR_ITEM_NOT_FOUND}
+ {error, not_found}
end.
nodeowners(Nidx) ->
- {result, Res} = node_flat_odbc:get_node_affiliations(Nidx),
+ {result, Res} = node_flat_sql:get_node_affiliations(Nidx),
[LJID || {LJID, Aff} <- Res, Aff =:= owner].
diff --git a/src/odbc_queries.erl b/src/odbc_queries.erl
deleted file mode 100644
index 2f488a0b..00000000
--- a/src/odbc_queries.erl
+++ /dev/null
@@ -1,659 +0,0 @@
-%%%----------------------------------------------------------------------
-%%% File : odbc_queries.erl
-%%% Author : Mickael Remond <mremond@process-one.net>
-%%% Purpose : ODBC queries dependind on back-end
-%%% Created : by Mickael Remond <mremond@process-one.net>
-%%%
-%%%
-%%% ejabberd, Copyright (C) 2002-2016 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(odbc_queries).
-
--behaviour(ejabberd_config).
-
--author("mremond@process-one.net").
-
--export([get_db_type/0, update/5, update_t/4,
- sql_transaction/2, get_last/2, set_last_t/4, del_last/2,
- get_password/2, get_password_scram/2, set_password_t/3,
- set_password_scram_t/6, add_user/3, add_user_scram/6,
- del_user/2, del_user_return_password/3, list_users/1,
- list_users/2, users_number/1, users_number/2,
- add_spool_sql/2, add_spool/2, get_and_del_spool_msg_t/2,
- del_spool_msg/2, get_roster/2, get_roster_jid_groups/2,
- get_roster_groups/3, del_user_roster_t/2,
- get_roster_by_jid/3, get_rostergroup_by_jid/3,
- del_roster/3, del_roster_sql/2, update_roster/5,
- update_roster_sql/4, roster_subscribe/4,
- get_subscription/3, set_private_data/4,
- set_private_data_sql/3, get_private_data/3,
- get_private_data/2, del_user_private_storage/2,
- get_default_privacy_list/2,
- get_default_privacy_list_t/1, get_privacy_list_names/2,
- get_privacy_list_names_t/1, get_privacy_list_id/3,
- get_privacy_list_id_t/2, get_privacy_list_data/3,
- get_privacy_list_data_by_id/2,
- get_privacy_list_data_t/2,
- get_privacy_list_data_by_id_t/1,
- set_default_privacy_list/2,
- unset_default_privacy_list/2, remove_privacy_list/2,
- add_privacy_list/2, set_privacy_list/2,
- del_privacy_lists/3, set_vcard/26, get_vcard/2,
- escape/1, count_records_where/3, get_roster_version/2,
- set_roster_version/2, opt_type/1]).
-
--include("ejabberd.hrl").
--include("logger.hrl").
-
-%% Almost a copy of string:join/2.
-%% We use this version because string:join/2 is relatively
-%% new function (introduced in R12B-0).
-join([], _Sep) -> [];
-join([H | T], Sep) -> [H, [[Sep, X] || X <- T]].
-
-get_db_type() -> generic.
-
-%% Safe atomic update.
-update_t(Table, Fields, Vals, Where) ->
- UPairs = lists:zipwith(fun (A, B) ->
- <<A/binary, "='", B/binary, "'">>
- end,
- Fields, Vals),
- case ejabberd_odbc:sql_query_t([<<"update ">>, Table,
- <<" set ">>, join(UPairs, <<", ">>),
- <<" where ">>, Where, <<";">>])
- of
- {updated, 1} -> ok;
- _ ->
- Res = ejabberd_odbc:sql_query_t([<<"insert into ">>, Table,
- <<"(">>, join(Fields, <<", ">>),
- <<") values ('">>, join(Vals, <<"', '">>),
- <<"');">>]),
- case Res of
- {updated,1} -> ok;
- _ -> Res
- end
- end.
-
-update(LServer, Table, Fields, Vals, Where) ->
- UPairs = lists:zipwith(fun (A, B) ->
- <<A/binary, "='", B/binary, "'">>
- end,
- Fields, Vals),
- case ejabberd_odbc:sql_query(LServer,
- [<<"update ">>, Table, <<" set ">>,
- join(UPairs, <<", ">>), <<" where ">>, Where,
- <<";">>])
- of
- {updated, 1} -> ok;
- _ ->
- Res = ejabberd_odbc:sql_query(LServer,
- [<<"insert into ">>, Table, <<"(">>,
- join(Fields, <<", ">>), <<") values ('">>,
- join(Vals, <<"', '">>), <<"');">>]),
- case Res of
- {updated,1} -> ok;
- _ -> Res
- end
- end.
-
-%% F can be either a fun or a list of queries
-%% TODO: We should probably move the list of queries transaction
-%% wrapper from the ejabberd_odbc module to this one (odbc_queries)
-sql_transaction(LServer, F) ->
- ejabberd_odbc:sql_transaction(LServer, F).
-
-get_last(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select seconds, state from last where "
- "username='">>,
- Username, <<"'">>]).
-
-set_last_t(LServer, Username, Seconds, State) ->
- update(LServer, <<"last">>,
- [<<"username">>, <<"seconds">>, <<"state">>],
- [Username, Seconds, State],
- [<<"username='">>, Username, <<"'">>]).
-
-del_last(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from last where username='">>, Username,
- <<"'">>]).
-
-get_password(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select password from users where username='">>,
- Username, <<"';">>]).
-
-get_password_scram(LServer, Username) ->
- ejabberd_odbc:sql_query(
- LServer,
- [<<"select password, serverkey, salt, iterationcount from users where "
- "username='">>, Username, <<"';">>]).
-
-set_password_t(LServer, Username, Pass) ->
- ejabberd_odbc:sql_transaction(LServer,
- fun () ->
- update_t(<<"users">>,
- [<<"username">>,
- <<"password">>],
- [Username, Pass],
- [<<"username='">>, Username,
- <<"'">>])
- end).
-
-set_password_scram_t(LServer, Username,
- StoredKey, ServerKey, Salt, IterationCount) ->
- ejabberd_odbc:sql_transaction(LServer,
- fun () ->
- update_t(<<"users">>,
- [<<"username">>,
- <<"password">>,
- <<"serverkey">>,
- <<"salt">>,
- <<"iterationcount">>],
- [Username, StoredKey,
- ServerKey, Salt,
- IterationCount],
- [<<"username='">>, Username,
- <<"'">>])
- end).
-
-add_user(LServer, Username, Pass) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"insert into users(username, password) "
- "values ('">>,
- Username, <<"', '">>, Pass, <<"');">>]).
-
-add_user_scram(LServer, Username,
- StoredKey, ServerKey, Salt, IterationCount) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"insert into users(username, password, serverkey, salt, iterationcount) "
- "values ('">>,
- Username, <<"', '">>, StoredKey, <<"', '">>,
- ServerKey, <<"', '">>,
- Salt, <<"', '">>,
- IterationCount, <<"');">>]).
-
-del_user(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from users where username='">>, Username,
- <<"';">>]).
-
-del_user_return_password(_LServer, Username, Pass) ->
- P =
- ejabberd_odbc:sql_query_t([<<"select password from users where username='">>,
- Username, <<"';">>]),
- ejabberd_odbc:sql_query_t([<<"delete from users where username='">>,
- Username, <<"' and password='">>, Pass,
- <<"';">>]),
- P.
-
-list_users(LServer) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select username from users">>]).
-
-list_users(LServer, [{from, Start}, {to, End}])
- when is_integer(Start) and is_integer(End) ->
- list_users(LServer,
- [{limit, End - Start + 1}, {offset, Start - 1}]);
-list_users(LServer,
- [{prefix, Prefix}, {from, Start}, {to, End}])
- when is_binary(Prefix) and is_integer(Start) and
- is_integer(End) ->
- list_users(LServer,
- [{prefix, Prefix}, {limit, End - Start + 1},
- {offset, Start - 1}]);
-list_users(LServer, [{limit, Limit}, {offset, Offset}])
- when is_integer(Limit) and is_integer(Offset) ->
- ejabberd_odbc:sql_query(LServer,
- [list_to_binary(
- io_lib:format(
- "select username from users " ++
- "order by username " ++
- "limit ~w offset ~w",
- [Limit, Offset]))]);
-list_users(LServer,
- [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
- when is_binary(Prefix) and is_integer(Limit) and
- is_integer(Offset) ->
- ejabberd_odbc:sql_query(LServer,
- [list_to_binary(
- io_lib:format(
- "select username from users " ++
- "where username like '~s%' " ++
- "order by username " ++
- "limit ~w offset ~w ",
- [Prefix, Limit, Offset]))]).
-
-users_number(LServer) ->
- Type = ejabberd_config:get_option({odbc_type, LServer},
- fun(pgsql) -> pgsql;
- (mysql) -> mysql;
- (sqlite) -> sqlite;
- (odbc) -> odbc
- end, odbc),
- case Type of
- pgsql ->
- case
- ejabberd_config:get_option(
- {pgsql_users_number_estimate, LServer},
- fun(V) when is_boolean(V) -> V end,
- false)
- of
- true ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select reltuples from pg_class where "
- "oid = 'users'::regclass::oid">>]);
- _ ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select count(*) from users">>])
- end;
- _ ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select count(*) from users">>])
- end.
-
-users_number(LServer, [{prefix, Prefix}])
- when is_binary(Prefix) ->
- ejabberd_odbc:sql_query(LServer,
- [list_to_binary(
- io_lib:fwrite(
- "select count(*) from users " ++
- %% Warning: Escape prefix at higher level to prevent SQL
- %% injection.
- "where username like '~s%'",
- [Prefix]))]);
-users_number(LServer, []) ->
- users_number(LServer).
-
-
-add_spool_sql(Username, XML) ->
- [<<"insert into spool(username, xml) values ('">>,
- Username, <<"', '">>, XML, <<"');">>].
-
-add_spool(LServer, Queries) ->
- ejabberd_odbc:sql_transaction(LServer, Queries).
-
-get_and_del_spool_msg_t(LServer, Username) ->
- F = fun () ->
- Result =
- ejabberd_odbc:sql_query_t([<<"select username, xml from spool where "
- "username='">>,
- Username,
- <<"' order by seq;">>]),
- ejabberd_odbc:sql_query_t([<<"delete from spool where username='">>,
- Username, <<"';">>]),
- Result
- end,
- ejabberd_odbc:sql_transaction(LServer, F).
-
-del_spool_msg(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from spool where username='">>, Username,
- <<"';">>]).
-
-get_roster(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select username, jid, nick, subscription, "
- "ask, askmessage, server, subscribe, "
- "type from rosterusers where username='">>,
- Username, <<"'">>]).
-
-get_roster_jid_groups(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select jid, grp from rostergroups where "
- "username='">>,
- Username, <<"'">>]).
-
-get_roster_groups(_LServer, Username, SJID) ->
- ejabberd_odbc:sql_query_t([<<"select grp from rostergroups where username='">>,
- Username, <<"' and jid='">>, SJID, <<"';">>]).
-
-del_user_roster_t(LServer, Username) ->
- ejabberd_odbc:sql_transaction(LServer,
- fun () ->
- ejabberd_odbc:sql_query_t([<<"delete from rosterusers where "
- "username='">>,
- Username,
- <<"';">>]),
- ejabberd_odbc:sql_query_t([<<"delete from rostergroups where "
- "username='">>,
- Username,
- <<"';">>])
- end).
-
-get_roster_by_jid(_LServer, Username, SJID) ->
- ejabberd_odbc:sql_query_t([<<"select username, jid, nick, subscription, "
- "ask, askmessage, server, subscribe, "
- "type from rosterusers where username='">>,
- Username, <<"' and jid='">>, SJID, <<"';">>]).
-
-get_rostergroup_by_jid(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select grp from rostergroups where username='">>,
- Username, <<"' and jid='">>, SJID, <<"'">>]).
-
-del_roster(_LServer, Username, SJID) ->
- ejabberd_odbc:sql_query_t([<<"delete from rosterusers where "
- "username='">>,
- Username, <<"' and jid='">>, SJID,
- <<"';">>]),
- ejabberd_odbc:sql_query_t([<<"delete from rostergroups where "
- "username='">>,
- Username, <<"' and jid='">>, SJID,
- <<"';">>]).
-
-del_roster_sql(Username, SJID) ->
- [[<<"delete from rosterusers where "
- "username='">>,
- Username, <<"' and jid='">>, SJID, <<"';">>],
- [<<"delete from rostergroups where "
- "username='">>,
- Username, <<"' and jid='">>, SJID, <<"';">>]].
-
-update_roster(_LServer, Username, SJID, ItemVals,
- ItemGroups) ->
- update_t(<<"rosterusers">>,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- ItemVals,
- [<<"username='">>, Username, <<"' and jid='">>, SJID,
- <<"'">>]),
- ejabberd_odbc:sql_query_t([<<"delete from rostergroups where "
- "username='">>,
- Username, <<"' and jid='">>, SJID,
- <<"';">>]),
- lists:foreach(fun (ItemGroup) ->
- ejabberd_odbc:sql_query_t([<<"insert into rostergroups( "
- " username, jid, grp) values ('">>,
- join(ItemGroup,
- <<"', '">>),
- <<"');">>])
- end,
- ItemGroups).
-
-update_roster_sql(Username, SJID, ItemVals,
- ItemGroups) ->
- [[<<"delete from rosterusers where "
- "username='">>,
- Username, <<"' and jid='">>, SJID, <<"';">>],
- [<<"insert into rosterusers( "
- " username, jid, nick, "
- " subscription, ask, askmessage, "
- " server, subscribe, type) "
- "values ('">>,
- join(ItemVals, <<"', '">>), <<"');">>],
- [<<"delete from rostergroups where "
- "username='">>,
- Username, <<"' and jid='">>, SJID, <<"';">>]]
- ++
- [[<<"insert into rostergroups( "
- " username, jid, grp) values ('">>,
- join(ItemGroup, <<"', '">>), <<"');">>]
- || ItemGroup <- ItemGroups].
-
-roster_subscribe(_LServer, Username, SJID, ItemVals) ->
- update_t(<<"rosterusers">>,
- [<<"username">>, <<"jid">>, <<"nick">>,
- <<"subscription">>, <<"ask">>, <<"askmessage">>,
- <<"server">>, <<"subscribe">>, <<"type">>],
- ItemVals,
- [<<"username='">>, Username, <<"' and jid='">>, SJID,
- <<"'">>]).
-
-get_subscription(LServer, Username, SJID) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select subscription from rosterusers "
- "where username='">>,
- Username, <<"' and jid='">>, SJID, <<"'">>]).
-
-set_private_data(_LServer, Username, LXMLNS, SData) ->
- update_t(<<"private_storage">>,
- [<<"username">>, <<"namespace">>, <<"data">>],
- [Username, LXMLNS, SData],
- [<<"username='">>, Username, <<"' and namespace='">>,
- LXMLNS, <<"'">>]).
-
-set_private_data_sql(Username, LXMLNS, SData) ->
- [[<<"delete from private_storage where username='">>,
- Username, <<"' and namespace='">>, LXMLNS, <<"';">>],
- [<<"insert into private_storage(username, "
- "namespace, data) values ('">>,
- Username, <<"', '">>, LXMLNS, <<"', '">>, SData,
- <<"');">>]].
-
-get_private_data(LServer, Username, LXMLNS) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select data from private_storage where "
- "username='">>,
- Username, <<"' and namespace='">>, LXMLNS,
- <<"';">>]).
-
-get_private_data(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select namespace, data from private_storage "
- "where username='">>, Username, <<"';">>]).
-
-del_user_private_storage(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from private_storage where username='">>,
- Username, <<"';">>]).
-
-set_vcard(LServer, LUsername, SBDay, SCTRY, SEMail, SFN,
- SFamily, SGiven, SLBDay, SLCTRY, SLEMail, SLFN,
- SLFamily, SLGiven, SLLocality, SLMiddle, SLNickname,
- SLOrgName, SLOrgUnit, SLocality, SMiddle, SNickname,
- SOrgName, SOrgUnit, SVCARD, Username) ->
- ejabberd_odbc:sql_transaction(LServer,
- fun () ->
- update_t(<<"vcard">>,
- [<<"username">>,
- <<"vcard">>],
- [LUsername, SVCARD],
- [<<"username='">>, LUsername,
- <<"'">>]),
- update_t(<<"vcard_search">>,
- [<<"username">>,
- <<"lusername">>, <<"fn">>,
- <<"lfn">>, <<"family">>,
- <<"lfamily">>, <<"given">>,
- <<"lgiven">>, <<"middle">>,
- <<"lmiddle">>,
- <<"nickname">>,
- <<"lnickname">>, <<"bday">>,
- <<"lbday">>, <<"ctry">>,
- <<"lctry">>, <<"locality">>,
- <<"llocality">>,
- <<"email">>, <<"lemail">>,
- <<"orgname">>,
- <<"lorgname">>,
- <<"orgunit">>,
- <<"lorgunit">>],
- [Username, LUsername, SFN,
- SLFN, SFamily, SLFamily,
- SGiven, SLGiven, SMiddle,
- SLMiddle, SNickname,
- SLNickname, SBDay, SLBDay,
- SCTRY, SLCTRY, SLocality,
- SLLocality, SEMail, SLEMail,
- SOrgName, SLOrgName,
- SOrgUnit, SLOrgUnit],
- [<<"lusername='">>,
- LUsername, <<"'">>])
- end).
-
-get_vcard(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select vcard from vcard where username='">>,
- Username, <<"';">>]).
-
-get_default_privacy_list(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select name from privacy_default_list "
- "where username='">>,
- Username, <<"';">>]).
-
-get_default_privacy_list_t(Username) ->
- ejabberd_odbc:sql_query_t([<<"select name from privacy_default_list "
- "where username='">>,
- Username, <<"';">>]).
-
-get_privacy_list_names(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select name from privacy_list where "
- "username='">>,
- Username, <<"';">>]).
-
-get_privacy_list_names_t(Username) ->
- ejabberd_odbc:sql_query_t([<<"select name from privacy_list where "
- "username='">>,
- Username, <<"';">>]).
-
-get_privacy_list_id(LServer, Username, SName) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select id from privacy_list where username='">>,
- Username, <<"' and name='">>, SName, <<"';">>]).
-
-get_privacy_list_id_t(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"select id from privacy_list where username='">>,
- Username, <<"' and name='">>, SName, <<"';">>]).
-
-get_privacy_list_data(LServer, Username, SName) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select t, value, action, ord, match_all, "
- "match_iq, match_message, match_presence_in, "
- "match_presence_out from privacy_list_data "
- "where id = (select id from privacy_list "
- "where username='">>,
- Username, <<"' and name='">>, SName,
- <<"') order by ord;">>]).
-
-get_privacy_list_data_t(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"select t, value, action, ord, match_all, "
- "match_iq, match_message, match_presence_in, "
- "match_presence_out from privacy_list_data "
- "where id = (select id from privacy_list "
- "where username='">>,
- Username, <<"' and name='">>, SName,
- <<"') order by ord;">>]).
-
-get_privacy_list_data_by_id(LServer, ID) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select t, value, action, ord, match_all, "
- "match_iq, match_message, match_presence_in, "
- "match_presence_out from privacy_list_data "
- "where id='">>,
- ID, <<"' order by ord;">>]).
-
-get_privacy_list_data_by_id_t(ID) ->
- ejabberd_odbc:sql_query_t([<<"select t, value, action, ord, match_all, "
- "match_iq, match_message, match_presence_in, "
- "match_presence_out from privacy_list_data "
- "where id='">>,
- ID, <<"' order by ord;">>]).
-
-set_default_privacy_list(Username, SName) ->
- update_t(<<"privacy_default_list">>,
- [<<"username">>, <<"name">>], [Username, SName],
- [<<"username='">>, Username, <<"'">>]).
-
-unset_default_privacy_list(LServer, Username) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from privacy_default_list "
- " where username='">>,
- Username, <<"';">>]).
-
-remove_privacy_list(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"delete from privacy_list where username='">>,
- Username, <<"' and name='">>, SName, <<"';">>]).
-
-add_privacy_list(Username, SName) ->
- ejabberd_odbc:sql_query_t([<<"insert into privacy_list(username, name) "
- "values ('">>,
- Username, <<"', '">>, SName, <<"');">>]).
-
-set_privacy_list(ID, RItems) ->
- ejabberd_odbc:sql_query_t([<<"delete from privacy_list_data where "
- "id='">>,
- ID, <<"';">>]),
- lists:foreach(fun (Items) ->
- ejabberd_odbc:sql_query_t([<<"insert into privacy_list_data(id, t, "
- "value, action, ord, match_all, match_iq, "
- "match_message, match_presence_in, match_prese"
- "nce_out ) values ('">>,
- ID, <<"', '">>,
- join(Items, <<"', '">>),
- <<"');">>])
- end,
- RItems).
-
-del_privacy_lists(LServer, Server, Username) ->
-%% Characters to escape
-%% Count number of records in a table given a where clause
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from privacy_list where username='">>,
- Username, <<"';">>]),
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from privacy_list_data where "
- "value='">>,
- <<Username/binary, "@", Server/binary>>,
- <<"';">>]),
- ejabberd_odbc:sql_query(LServer,
- [<<"delete from privacy_default_list where "
- "username='">>,
- Username, <<"';">>]).
-
-escape($\000) -> <<"\\0">>;
-escape($\n) -> <<"\\n">>;
-escape($\t) -> <<"\\t">>;
-escape($\b) -> <<"\\b">>;
-escape($\r) -> <<"\\r">>;
-escape($') -> <<"''">>;
-escape($") -> <<"\\\"">>;
-escape($\\) -> <<"\\\\">>;
-escape(C) -> <<C>>.
-
-count_records_where(LServer, Table, WhereClause) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select count(*) from ">>, Table, <<" ">>,
- WhereClause, <<";">>]).
-
-get_roster_version(LServer, LUser) ->
- ejabberd_odbc:sql_query(LServer,
- [<<"select version from roster_version where "
- "username = '">>,
- LUser, <<"'">>]).
-
-set_roster_version(LUser, Version) ->
- update_t(<<"roster_version">>,
- [<<"username">>, <<"version">>], [LUser, Version],
- [<<"username = '">>, LUser, <<"'">>]).
-
-opt_type(odbc_type) ->
- fun (pgsql) -> pgsql;
- (mysql) -> mysql;
- (sqlite) -> sqlite;
- (mssql) -> mssql;
- (odbc) -> odbc
- end;
-opt_type(pgsql_users_number_estimate) ->
- fun (V) when is_boolean(V) -> V end;
-opt_type(_) -> [odbc_type, pgsql_users_number_estimate].
diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl
new file mode 100644
index 00000000..204cfec2
--- /dev/null
+++ b/src/prosody2ejabberd.erl
@@ -0,0 +1,341 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 20 Jan 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(prosody2ejabberd).
+
+%% API
+-export([from_dir/1]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("logger.hrl").
+-include("mod_roster.hrl").
+-include("mod_offline.hrl").
+-include("mod_privacy.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+from_dir(ProsodyDir) ->
+ case file:list_dir(ProsodyDir) of
+ {ok, HostDirs} ->
+ lists:foreach(
+ fun(HostDir) ->
+ Host = list_to_binary(HostDir),
+ lists:foreach(
+ fun(SubDir) ->
+ Path = filename:join(
+ [ProsodyDir, HostDir, SubDir]),
+ convert_dir(Path, Host, SubDir)
+ end, ["vcard", "accounts", "roster",
+ "private", "config", "offline",
+ "privacy"])
+ end, HostDirs);
+ {error, Why} = Err ->
+ ?ERROR_MSG("failed to list ~s: ~s",
+ [ProsodyDir, file:format_error(Why)]),
+ Err
+ end.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+convert_dir(Path, Host, Type) ->
+ case file:list_dir(Path) of
+ {ok, Files} ->
+ lists:foreach(
+ fun(File) ->
+ FilePath = filename:join(Path, File),
+ case eval_file(FilePath) of
+ {ok, Data} ->
+ Name = iolist_to_binary(filename:rootname(File)),
+ convert_data(Host, Type, Name, Data);
+ Err ->
+ Err
+ end
+ end, Files);
+ {error, enoent} ->
+ ok;
+ {error, Why} = Err ->
+ ?ERROR_MSG("failed to list ~s: ~s",
+ [Path, file:format_error(Why)]),
+ Err
+ end.
+
+eval_file(Path) ->
+ case file:read_file(Path) of
+ {ok, Data} ->
+ State0 = luerl:init(),
+ State1 = luerl:set_table([item],
+ fun([X], State) -> {[X], State} end,
+ State0),
+ NewData = case filename:extension(Path) of
+ ".list" ->
+ <<"return {", Data/binary, "};">>;
+ _ ->
+ Data
+ end,
+ case luerl:eval(NewData, State1) of
+ {ok, _} = Res ->
+ Res;
+ {error, Why} = Err ->
+ ?ERROR_MSG("failed to eval ~s: ~p", [Path, Why]),
+ Err
+ end;
+ {error, Why} = Err ->
+ ?ERROR_MSG("failed to read file ~s: ~s",
+ [Path, file:format_error(Why)]),
+ Err
+ end.
+
+convert_data(Host, "accounts", User, [Data]) ->
+ Password = proplists:get_value(<<"password">>, Data, <<>>),
+ case ejabberd_auth:try_register(User, Host, Password) of
+ {atomic, ok} ->
+ ok;
+ Err ->
+ ?ERROR_MSG("failed to register user ~s@~s: ~p",
+ [User, Host, Err]),
+ Err
+ end;
+convert_data(Host, "roster", User, [Data]) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Host),
+ Rosters =
+ lists:flatmap(
+ fun({<<"pending">>, L}) ->
+ convert_pending_item(LUser, LServer, L);
+ ({S, L}) when is_binary(S) ->
+ convert_roster_item(LUser, LServer, S, L);
+ (_) ->
+ []
+ 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
+ [El] ->
+ XMLNS = fxml:get_tag_attr_s(<<"xmlns">>, El),
+ [{XMLNS, El}];
+ _ ->
+ []
+ end
+ end, Data),
+ mod_private:set_data(LUser, LServer, PrivData);
+convert_data(Host, "vcard", User, [Data]) ->
+ LServer = jid:nameprep(Host),
+ case deserialize(Data) of
+ [VCard] ->
+ mod_vcard:set_vcard(User, LServer, VCard);
+ _ ->
+ ok
+ end;
+convert_data(_Host, "config", _User, [Data]) ->
+ RoomJID = jid:from_string(proplists:get_value(<<"jid">>, Data, <<"">>)),
+ Config = proplists:get_value(<<"_data">>, Data, []),
+ RoomCfg = convert_room_config(Data),
+ case proplists:get_bool(<<"persistent">>, Config) of
+ true when RoomJID /= error ->
+ mod_muc:store_room(?MYNAME, RoomJID#jid.lserver,
+ RoomJID#jid.luser, RoomCfg);
+ _ ->
+ ok
+ end;
+convert_data(Host, "offline", User, [Data]) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Host),
+ Msgs = lists:flatmap(
+ fun({_, RawXML}) ->
+ case deserialize(RawXML) of
+ [El] -> el_to_offline_msg(LUser, LServer, El);
+ _ -> []
+ end
+ end, Data),
+ mod_offline:store_offline_msg(
+ LServer, {LUser, LServer}, Msgs, length(Msgs), infinity);
+convert_data(Host, "privacy", User, [Data]) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Host),
+ Lists = proplists:get_value(<<"lists">>, Data, []),
+ Priv = #privacy{
+ us = {LUser, LServer},
+ default = proplists:get_value(<<"default">>, Data, none),
+ lists = lists:flatmap(
+ fun({Name, Vals}) ->
+ Items = proplists:get_value(<<"items">>, Vals, []),
+ case lists:map(fun convert_privacy_item/1,
+ Items) of
+ [] -> [];
+ ListItems -> [{Name, ListItems}]
+ end
+ end, Lists)},
+ mod_privacy:set_privacy_list(Priv);
+convert_data(_Host, _Type, _User, _Data) ->
+ ok.
+
+convert_pending_item(LUser, LServer, LuaList) ->
+ lists:flatmap(
+ fun({S, true}) ->
+ case jid:from_string(S) of
+ #jid{} = J ->
+ LJID = jid:tolower(J),
+ [#roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer},
+ jid = LJID,
+ ask = in}];
+ error ->
+ []
+ end;
+ (_) ->
+ []
+ end, LuaList).
+
+convert_roster_item(LUser, LServer, JIDstring, LuaList) ->
+ case jid:from_string(JIDstring) of
+ #jid{} = JID ->
+ LJID = jid:tolower(JID),
+ InitR = #roster{usj = {LUser, LServer, LJID},
+ us = {LUser, LServer},
+ jid = LJID},
+ Roster =
+ 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 = jlib:binary_to_atom(Sub)};
+ ({<<"ask">>, <<"subscribe">>}, R) ->
+ R#roster{ask = out};
+ ({<<"name">>, Name}, R) ->
+ R#roster{name = Name}
+ end, InitR, LuaList),
+ [Roster];
+ error ->
+ []
+ end.
+
+convert_room_affiliations(Data) ->
+ lists:flatmap(
+ fun({J, Aff}) ->
+ case jid:from_string(J) of
+ #jid{luser = U, lserver = S} ->
+ [{{U, S, <<>>}, jlib:binary_to_atom(Aff)}];
+ error ->
+ []
+ end
+ end, proplists:get_value(<<"_affiliations">>, Data, [])).
+
+convert_room_config(Data) ->
+ Config = proplists:get_value(<<"_data">>, Data, []),
+ Pass = case proplists:get_value(<<"password">>, Config, <<"">>) of
+ <<"">> ->
+ [];
+ Password ->
+ [{password_protected, true},
+ {password, Password}]
+ end,
+ Subj = case jid:from_string(
+ proplists:get_value(
+ <<"subject_from">>, Config, <<"">>)) of
+ #jid{lresource = Nick} when Nick /= <<"">> ->
+ [{subject, proplists:get_value(<<"subject">>, Config, <<"">>)},
+ {subject_author, Nick}];
+ _ ->
+ []
+ end,
+ Anonymous = case proplists:get_value(<<"whois">>, Config, <<"moderators">>) of
+ <<"moderators">> -> true;
+ _ -> false
+ end,
+ [{affiliations, convert_room_affiliations(Data)},
+ {allow_change_subj, proplists:get_bool(<<"changesubject">>, Config)},
+ {description, proplists:get_value(<<"description">>, Config, <<"">>)},
+ {members_only, proplists:get_bool(<<"members_only">>, Config)},
+ {moderated, proplists:get_bool(<<"moderated">>, Config)},
+ {anonymous, Anonymous}] ++ Pass ++ Subj.
+
+convert_privacy_item({_, Item}) ->
+ Action = proplists:get_value(<<"action">>, Item, <<"allow">>),
+ Order = proplists:get_value(<<"order">>, Item, 0),
+ T = jlib:binary_to_atom(proplists:get_value(<<"type">>, Item, <<"none">>)),
+ V = proplists:get_value(<<"value">>, Item, <<"">>),
+ MatchIQ = proplists:get_bool(<<"iq">>, Item),
+ MatchMsg = proplists:get_bool(<<"message">>, Item),
+ MatchPresIn = proplists:get_bool(<<"presence-in">>, Item),
+ MatchPresOut = proplists:get_bool(<<"presence-out">>, Item),
+ MatchAll = if (MatchIQ == false) and (MatchMsg == false) and
+ (MatchPresIn == false) and (MatchPresOut == false) ->
+ true;
+ true ->
+ false
+ end,
+ {Type, Value} = try case T of
+ none -> {T, none};
+ group -> {T, V};
+ jid -> {T, jid:tolower(jid:from_string(V))};
+ subscription -> {T, jlib:binary_to_atom(V)}
+ end
+ catch _:_ ->
+ {none, none}
+ end,
+ #listitem{type = Type,
+ value = Value,
+ action = jlib:binary_to_atom(Action),
+ order = erlang:trunc(Order),
+ match_all = MatchAll,
+ match_iq = MatchIQ,
+ match_message = MatchMsg,
+ match_presence_in = MatchPresIn,
+ match_presence_out = MatchPresOut}.
+
+el_to_offline_msg(LUser, LServer, #xmlel{attrs = Attrs} = El) ->
+ case jlib:datetime_string_to_timestamp(
+ fxml:get_attr_s(<<"stamp">>, Attrs)) of
+ {_, _, _} = TS ->
+ Attrs1 = lists:filter(
+ fun(<<"stamp">>) -> false;
+ (<<"stamp_legacy">>) -> false;
+ (_) -> true
+ end, Attrs),
+ Packet = El#xmlel{attrs = Attrs1},
+ case {jid:from_string(fxml:get_attr_s(<<"from">>, Attrs)),
+ jid:from_string(fxml:get_attr_s(<<"to">>, Attrs))} of
+ {#jid{} = From, #jid{} = To} ->
+ [#offline_msg{
+ us = {LUser, LServer},
+ timestamp = TS,
+ expire = never,
+ from = From,
+ to = To,
+ packet = Packet}];
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end.
+
+deserialize(L) ->
+ deserialize(L, #xmlel{}, []).
+
+deserialize([{<<"attr">>, Attrs}|T], El, Acc) ->
+ deserialize(T, El#xmlel{attrs = Attrs ++ El#xmlel.attrs}, Acc);
+deserialize([{<<"name">>, Name}|T], El, Acc) ->
+ deserialize(T, El#xmlel{name = Name}, Acc);
+deserialize([{_, S}|T], #xmlel{children = Els} = El, Acc) when is_binary(S) ->
+ deserialize(T, El#xmlel{children = [{xmlcdata, S}|Els]}, Acc);
+deserialize([{_, L}|T], #xmlel{children = Els} = El, Acc) when is_list(L) ->
+ deserialize(T, El#xmlel{children = deserialize(L) ++ Els}, Acc);
+deserialize([], El, Acc) ->
+ [El|Acc].
diff --git a/src/pubsub_db_odbc.erl b/src/pubsub_db_sql.erl
index cfdeda1e..b910a5e7 100644
--- a/src/pubsub_db_odbc.erl
+++ b/src/pubsub_db_sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : pubsub_db_odbc.erl
+%%% File : pubsub_db_sql.erl
%%% Author : Pablo Polvorin <pablo.polvorin@process-one.net>
%%% Purpose : Provide helpers for PubSub ODBC backend
%%% Created : 7 Aug 2009 by Pablo Polvorin <pablo.polvorin@process-one.net>
@@ -23,7 +23,7 @@
%%%
%%%----------------------------------------------------------------------
--module(pubsub_db_odbc).
+-module(pubsub_db_sql).
-author("pablo.polvorin@process-one.net").
@@ -37,16 +37,16 @@
%% -spec read_subscription(SubID :: string()) -> {ok, #pubsub_subscription{}} | notfound.
read_subscription(SubID) ->
case
- ejabberd_odbc:sql_query_t([<<"select opt_name, opt_value from pubsub_subscr"
+ ejabberd_sql:sql_query_t([<<"select opt_name, opt_value from pubsub_subscr"
"iption_opt where subid = '">>,
- ejabberd_odbc:escape(SubID), <<"'">>])
+ ejabberd_sql:escape(SubID), <<"'">>])
of
{selected, [<<"opt_name">>, <<"opt_value">>], []} ->
notfound;
{selected, [<<"opt_name">>, <<"opt_value">>], Options} ->
{ok,
#pubsub_subscription{subid = SubID,
- options = lists:map(fun subscription_opt_from_odbc/1, Options)}}
+ options = lists:map(fun subscription_opt_from_sql/1, Options)}}
end.
%% -spec delete_subscription(SubID :: string()) -> ok.
@@ -54,19 +54,19 @@ delete_subscription(SubID) ->
%% -spec update_subscription(#pubsub_subscription{}) -> ok .
%% -spec add_subscription(#pubsub_subscription{}) -> ok.
%% -------------- Internal utilities -----------------------
- ejabberd_odbc:sql_query_t([<<"delete from pubsub_subscription_opt "
+ ejabberd_sql:sql_query_t([<<"delete from pubsub_subscription_opt "
"where subid = '">>,
- ejabberd_odbc:escape(SubID), <<"'">>]),
+ ejabberd_sql:escape(SubID), <<"'">>]),
ok.
update_subscription(#pubsub_subscription{subid = SubId} = Sub) ->
delete_subscription(SubId), add_subscription(Sub).
add_subscription(#pubsub_subscription{subid = SubId, options = Opts}) ->
- EscapedSubId = ejabberd_odbc:escape(SubId),
+ EscapedSubId = ejabberd_sql:escape(SubId),
lists:foreach(fun (Opt) ->
- {OdbcOptName, OdbcOptValue} = subscription_opt_to_odbc(Opt),
- ejabberd_odbc:sql_query_t([<<"insert into pubsub_subscription_opt(subid, "
+ {OdbcOptName, OdbcOptValue} = subscription_opt_to_sql(Opt),
+ ejabberd_sql:sql_query_t([<<"insert into pubsub_subscription_opt(subid, "
"opt_name, opt_value)values ('">>,
EscapedSubId, <<"','">>,
OdbcOptName, <<"','">>,
@@ -75,67 +75,67 @@ add_subscription(#pubsub_subscription{subid = SubId, options = Opts}) ->
Opts),
ok.
-subscription_opt_from_odbc({<<"DELIVER">>, Value}) ->
- {deliver, odbc_to_boolean(Value)};
-subscription_opt_from_odbc({<<"DIGEST">>, Value}) ->
- {digest, odbc_to_boolean(Value)};
-subscription_opt_from_odbc({<<"DIGEST_FREQUENCY">>, Value}) ->
- {digest_frequency, odbc_to_integer(Value)};
-subscription_opt_from_odbc({<<"EXPIRE">>, Value}) ->
- {expire, odbc_to_timestamp(Value)};
-subscription_opt_from_odbc({<<"INCLUDE_BODY">>, Value}) ->
- {include_body, odbc_to_boolean(Value)};
+subscription_opt_from_sql({<<"DELIVER">>, Value}) ->
+ {deliver, sql_to_boolean(Value)};
+subscription_opt_from_sql({<<"DIGEST">>, Value}) ->
+ {digest, sql_to_boolean(Value)};
+subscription_opt_from_sql({<<"DIGEST_FREQUENCY">>, Value}) ->
+ {digest_frequency, sql_to_integer(Value)};
+subscription_opt_from_sql({<<"EXPIRE">>, Value}) ->
+ {expire, sql_to_timestamp(Value)};
+subscription_opt_from_sql({<<"INCLUDE_BODY">>, Value}) ->
+ {include_body, sql_to_boolean(Value)};
%%TODO: might be > than 1 show_values value??.
%% need to use compact all in only 1 opt.
-subscription_opt_from_odbc({<<"SHOW_VALUES">>, Value}) ->
+subscription_opt_from_sql({<<"SHOW_VALUES">>, Value}) ->
{show_values, Value};
-subscription_opt_from_odbc({<<"SUBSCRIPTION_TYPE">>, Value}) ->
+subscription_opt_from_sql({<<"SUBSCRIPTION_TYPE">>, Value}) ->
{subscription_type,
case Value of
<<"items">> -> items;
<<"nodes">> -> nodes
end};
-subscription_opt_from_odbc({<<"SUBSCRIPTION_DEPTH">>, Value}) ->
+subscription_opt_from_sql({<<"SUBSCRIPTION_DEPTH">>, Value}) ->
{subscription_depth,
case Value of
<<"all">> -> all;
- N -> odbc_to_integer(N)
+ N -> sql_to_integer(N)
end}.
-subscription_opt_to_odbc({deliver, Bool}) ->
- {<<"DELIVER">>, boolean_to_odbc(Bool)};
-subscription_opt_to_odbc({digest, Bool}) ->
- {<<"DIGEST">>, boolean_to_odbc(Bool)};
-subscription_opt_to_odbc({digest_frequency, Int}) ->
- {<<"DIGEST_FREQUENCY">>, integer_to_odbc(Int)};
-subscription_opt_to_odbc({expire, Timestamp}) ->
- {<<"EXPIRE">>, timestamp_to_odbc(Timestamp)};
-subscription_opt_to_odbc({include_body, Bool}) ->
- {<<"INCLUDE_BODY">>, boolean_to_odbc(Bool)};
-subscription_opt_to_odbc({show_values, Values}) ->
+subscription_opt_to_sql({deliver, Bool}) ->
+ {<<"DELIVER">>, boolean_to_sql(Bool)};
+subscription_opt_to_sql({digest, Bool}) ->
+ {<<"DIGEST">>, boolean_to_sql(Bool)};
+subscription_opt_to_sql({digest_frequency, Int}) ->
+ {<<"DIGEST_FREQUENCY">>, integer_to_sql(Int)};
+subscription_opt_to_sql({expire, Timestamp}) ->
+ {<<"EXPIRE">>, timestamp_to_sql(Timestamp)};
+subscription_opt_to_sql({include_body, Bool}) ->
+ {<<"INCLUDE_BODY">>, boolean_to_sql(Bool)};
+subscription_opt_to_sql({show_values, Values}) ->
{<<"SHOW_VALUES">>, Values};
-subscription_opt_to_odbc({subscription_type, Type}) ->
+subscription_opt_to_sql({subscription_type, Type}) ->
{<<"SUBSCRIPTION_TYPE">>,
case Type of
items -> <<"items">>;
nodes -> <<"nodes">>
end};
-subscription_opt_to_odbc({subscription_depth, Depth}) ->
+subscription_opt_to_sql({subscription_depth, Depth}) ->
{<<"SUBSCRIPTION_DEPTH">>,
case Depth of
all -> <<"all">>;
- N -> integer_to_odbc(N)
+ N -> integer_to_sql(N)
end}.
-integer_to_odbc(N) -> iolist_to_binary(integer_to_list(N)).
+integer_to_sql(N) -> iolist_to_binary(integer_to_list(N)).
-boolean_to_odbc(true) -> <<"1">>;
-boolean_to_odbc(false) -> <<"0">>.
+boolean_to_sql(true) -> <<"1">>;
+boolean_to_sql(false) -> <<"0">>.
-timestamp_to_odbc(T) -> jlib:now_to_utc_string(T).
+timestamp_to_sql(T) -> jlib:now_to_utc_string(T).
-odbc_to_integer(N) -> jlib:binary_to_integer(N).
+sql_to_integer(N) -> jlib:binary_to_integer(N).
-odbc_to_boolean(B) -> B == <<"1">>.
+sql_to_boolean(B) -> B == <<"1">>.
-odbc_to_timestamp(T) -> jlib:datetime_string_to_timestamp(T).
+sql_to_timestamp(T) -> jlib:datetime_string_to_timestamp(T).
diff --git a/src/pubsub_subscription.erl b/src/pubsub_subscription.erl
index 9918a2c3..f990f6e3 100644
--- a/src/pubsub_subscription.erl
+++ b/src/pubsub_subscription.erl
@@ -126,7 +126,7 @@ get_options_xform(Lang, Options) ->
++ XFields}}.
parse_options_xform(XFields) ->
- case xml:remove_cdata(XFields) of
+ case fxml:remove_cdata(XFields) of
[#xmlel{name = <<"x">>} = XEl] ->
case jlib:parse_xdata_submit(XEl) of
XData when is_list(XData) ->
@@ -237,31 +237,40 @@ var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type;
var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth;
var_xfield(_) -> {error, badarg}.
-val_xfield(deliver, [Val]) -> xopt_to_bool(Val);
-val_xfield(digest, [Val]) -> xopt_to_bool(Val);
-val_xfield(digest_frequency, [Val]) ->
+val_xfield(deliver = Opt, [Val]) -> xopt_to_bool(Opt, Val);
+val_xfield(digest = Opt, [Val]) -> xopt_to_bool(Opt, Val);
+val_xfield(digest_frequency = Opt, [Val]) ->
case catch jlib:binary_to_integer(Val) of
N when is_integer(N) -> N;
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ ->
+ Txt = <<"Value of '~s' should be integer">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
end;
val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val);
-val_xfield(include_body, [Val]) -> xopt_to_bool(Val);
+val_xfield(include_body = Opt, [Val]) -> xopt_to_bool(Opt, Val);
val_xfield(show_values, Vals) -> Vals;
val_xfield(subscription_type, [<<"items">>]) -> items;
val_xfield(subscription_type, [<<"nodes">>]) -> nodes;
val_xfield(subscription_depth, [<<"all">>]) -> all;
-val_xfield(subscription_depth, [Depth]) ->
+val_xfield(subscription_depth = Opt, [Depth]) ->
case catch jlib:binary_to_integer(Depth) of
N when is_integer(N) -> N;
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ ->
+ Txt = <<"Value of '~s' should be integer">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
end.
%% Convert XForm booleans to Erlang booleans.
-xopt_to_bool(<<"0">>) -> false;
-xopt_to_bool(<<"1">>) -> true;
-xopt_to_bool(<<"false">>) -> false;
-xopt_to_bool(<<"true">>) -> true;
-xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}.
+xopt_to_bool(_, <<"0">>) -> false;
+xopt_to_bool(_, <<"1">>) -> true;
+xopt_to_bool(_, <<"false">>) -> false;
+xopt_to_bool(_, <<"true">>) -> true;
+xopt_to_bool(Option, _) ->
+ Txt = <<"Value of '~s' should be boolean">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Option])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}.
-spec(get_option_xfield/3 ::
(
diff --git a/src/pubsub_subscription_odbc.erl b/src/pubsub_subscription_sql.erl
index 6791c4ac..1f82aa00 100644
--- a/src/pubsub_subscription_odbc.erl
+++ b/src/pubsub_subscription_sql.erl
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
-%%% File : pubsub_subscription_odbc.erl
+%%% File : pubsub_subscription_sql.erl
%%% Author : Pablo Polvorin <pablo.polvorin@process-one.net>
%%% Purpose : Handle pubsub subscriptions options with ODBC backend
%%% based on pubsub_subscription.erl by Brian Cully <bjc@kublai.com>
@@ -24,7 +24,7 @@
%%%
%%%----------------------------------------------------------------------
--module(pubsub_subscription_odbc).
+-module(pubsub_subscription_sql).
-author("pablo.polvorin@process-one.net").
%% API
@@ -66,7 +66,7 @@
-define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL, <<"Receive notification from direct child nodes only">>).
-define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL, <<"Receive notification from all descendent nodes">>).
--define(DB_MOD, pubsub_db_odbc).
+-define(DB_MOD, pubsub_db_sql).
%%====================================================================
%% API
%%====================================================================
@@ -151,7 +151,7 @@ get_options_xform(Lang, Options) ->
++ XFields}}.
parse_options_xform(XFields) ->
- case xml:remove_cdata(XFields) of
+ case fxml:remove_cdata(XFields) of
[#xmlel{name = <<"x">>} = XEl] ->
case jlib:parse_xdata_submit(XEl) of
XData when is_list(XData) ->
@@ -200,31 +200,40 @@ var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type;
var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth;
var_xfield(_) -> {error, badarg}.
-val_xfield(deliver, [Val]) -> xopt_to_bool(Val);
-val_xfield(digest, [Val]) -> xopt_to_bool(Val);
-val_xfield(digest_frequency, [Val]) ->
+val_xfield(deliver = Opt, [Val]) -> xopt_to_bool(Opt, Val);
+val_xfield(digest = Opt, [Val]) -> xopt_to_bool(Opt, Val);
+val_xfield(digest_frequency = Opt, [Val]) ->
case catch jlib:binary_to_integer(Val) of
N when is_integer(N) -> N;
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ ->
+ Txt = <<"Value of '~s' should be integer">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
end;
val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val);
-val_xfield(include_body, [Val]) -> xopt_to_bool(Val);
+val_xfield(include_body = Opt, [Val]) -> xopt_to_bool(Opt, Val);
val_xfield(show_values, Vals) -> Vals;
val_xfield(subscription_type, [<<"items">>]) -> items;
val_xfield(subscription_type, [<<"nodes">>]) -> nodes;
val_xfield(subscription_depth, [<<"all">>]) -> all;
-val_xfield(subscription_depth, [Depth]) ->
+val_xfield(subscription_depth = Opt, [Depth]) ->
case catch jlib:binary_to_integer(Depth) of
N when is_integer(N) -> N;
- _ -> {error, ?ERR_NOT_ACCEPTABLE}
+ _ ->
+ Txt = <<"Value of '~s' should be integer">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
end.
%% Convert XForm booleans to Erlang booleans.
-xopt_to_bool(<<"0">>) -> false;
-xopt_to_bool(<<"1">>) -> true;
-xopt_to_bool(<<"false">>) -> false;
-xopt_to_bool(<<"true">>) -> true;
-xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}.
+xopt_to_bool(_, <<"0">>) -> false;
+xopt_to_bool(_, <<"1">>) -> true;
+xopt_to_bool(_, <<"false">>) -> false;
+xopt_to_bool(_, <<"true">>) -> true;
+xopt_to_bool(Option, _) ->
+ Txt = <<"Value of '~s' should be boolean">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Option])),
+ {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}.
%% Return a field for an XForm for Key, with data filled in, if
%% applicable, from Options.
diff --git a/src/sql_queries.erl b/src/sql_queries.erl
new file mode 100644
index 00000000..98530a4c
--- /dev/null
+++ b/src/sql_queries.erl
@@ -0,0 +1,644 @@
+%%%----------------------------------------------------------------------
+%%% File : sql_queries.erl
+%%% Author : Mickael Remond <mremond@process-one.net>
+%%% Purpose : ODBC queries dependind on back-end
+%%% Created : by Mickael Remond <mremond@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2016 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(sql_queries).
+
+-compile([{parse_transform, ejabberd_sql_pt}]).
+
+-behaviour(ejabberd_config).
+
+-author("mremond@process-one.net").
+
+-export([get_db_type/0, update/5, update_t/4,
+ sql_transaction/2, get_last/2, set_last_t/4, del_last/2,
+ get_password/2, get_password_scram/2, set_password_t/3,
+ set_password_scram_t/6, add_user/3, add_user_scram/6,
+ del_user/2, del_user_return_password/3, list_users/1,
+ list_users/2, users_number/1, users_number/2,
+ add_spool_sql/2, add_spool/2, get_and_del_spool_msg_t/2,
+ del_spool_msg/2, get_roster/2, get_roster_jid_groups/2,
+ get_roster_groups/3, del_user_roster_t/2,
+ get_roster_by_jid/3, get_rostergroup_by_jid/3,
+ del_roster/3, del_roster_sql/2, update_roster/5,
+ update_roster_sql/4, roster_subscribe/1,
+ get_subscription/3, set_private_data/4,
+ set_private_data_sql/3, get_private_data/3,
+ get_private_data/2, del_user_private_storage/2,
+ get_default_privacy_list/2,
+ get_default_privacy_list_t/1, get_privacy_list_names/2,
+ get_privacy_list_names_t/1, get_privacy_list_id/3,
+ get_privacy_list_id_t/2, get_privacy_list_data/3,
+ get_privacy_list_data_by_id/2,
+ get_privacy_list_data_t/2,
+ get_privacy_list_data_by_id_t/1,
+ set_default_privacy_list/2,
+ unset_default_privacy_list/2, remove_privacy_list/2,
+ add_privacy_list/2, set_privacy_list/2,
+ del_privacy_lists/2, set_vcard/26, get_vcard/2,
+ escape/1, count_records_where/3, get_roster_version/2,
+ set_roster_version/2, opt_type/1]).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+-include("ejabberd_sql_pt.hrl").
+
+%% Almost a copy of string:join/2.
+%% We use this version because string:join/2 is relatively
+%% new function (introduced in R12B-0).
+join([], _Sep) -> [];
+join([H | T], Sep) -> [H, [[Sep, X] || X <- T]].
+
+get_db_type() -> generic.
+
+%% Safe atomic update.
+update_t(Table, Fields, Vals, Where) ->
+ UPairs = lists:zipwith(fun (A, B) ->
+ <<A/binary, "='", B/binary, "'">>
+ end,
+ Fields, Vals),
+ case ejabberd_sql:sql_query_t([<<"update ">>, Table,
+ <<" set ">>, join(UPairs, <<", ">>),
+ <<" where ">>, Where, <<";">>])
+ of
+ {updated, 1} -> ok;
+ _ ->
+ Res = ejabberd_sql:sql_query_t([<<"insert into ">>, Table,
+ <<"(">>, join(Fields, <<", ">>),
+ <<") values ('">>, join(Vals, <<"', '">>),
+ <<"');">>]),
+ case Res of
+ {updated,1} -> ok;
+ _ -> Res
+ end
+ end.
+
+update(LServer, Table, Fields, Vals, Where) ->
+ UPairs = lists:zipwith(fun (A, B) ->
+ <<A/binary, "='", B/binary, "'">>
+ end,
+ Fields, Vals),
+ case ejabberd_sql:sql_query(LServer,
+ [<<"update ">>, Table, <<" set ">>,
+ join(UPairs, <<", ">>), <<" where ">>, Where,
+ <<";">>])
+ of
+ {updated, 1} -> ok;
+ _ ->
+ Res = ejabberd_sql:sql_query(LServer,
+ [<<"insert into ">>, Table, <<"(">>,
+ join(Fields, <<", ">>), <<") values ('">>,
+ join(Vals, <<"', '">>), <<"');">>]),
+ case Res of
+ {updated,1} -> ok;
+ _ -> Res
+ end
+ end.
+
+%% F can be either a fun or a list of queries
+%% TODO: We should probably move the list of queries transaction
+%% wrapper from the ejabberd_sql module to this one (sql_queries)
+sql_transaction(LServer, F) ->
+ ejabberd_sql:sql_transaction(LServer, F).
+
+get_last(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(seconds)d, @(state)s from last"
+ " where username=%(LUser)s")).
+
+set_last_t(LServer, LUser, TimeStamp, Status) ->
+ ?SQL_UPSERT(LServer, "last",
+ ["!username=%(LUser)s",
+ "seconds=%(TimeStamp)d",
+ "state=%(Status)s"]).
+
+del_last(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from last where username=%(LUser)s")).
+
+get_password(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(password)s from users where username=%(LUser)s")).
+
+get_password_scram(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(password)s, @(serverkey)s, @(salt)s, @(iterationcount)d"
+ " from users"
+ " where username=%(LUser)s")).
+
+set_password_t(LServer, LUser, Password) ->
+ ejabberd_sql:sql_transaction(
+ LServer,
+ fun () ->
+ ?SQL_UPSERT_T(
+ "users",
+ ["!username=%(LUser)s",
+ "password=%(Password)s"])
+ end).
+
+set_password_scram_t(LServer, LUser,
+ StoredKey, ServerKey, Salt, IterationCount) ->
+ ejabberd_sql:sql_transaction(
+ LServer,
+ fun () ->
+ ?SQL_UPSERT_T(
+ "users",
+ ["!username=%(LUser)s",
+ "password=%(StoredKey)s",
+ "serverkey=%(ServerKey)s",
+ "salt=%(Salt)s",
+ "iterationcount=%(IterationCount)d"])
+ end).
+
+add_user(LServer, LUser, Password) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("insert into users(username, password) "
+ "values (%(LUser)s, %(Password)s)")).
+
+add_user_scram(LServer, LUser,
+ StoredKey, ServerKey, Salt, IterationCount) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("insert into users(username, password, serverkey, salt, "
+ "iterationcount) "
+ "values (%(LUser)s, %(StoredKey)s, %(ServerKey)s,"
+ " %(Salt)s, %(IterationCount)d)")).
+
+del_user(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from users where username=%(LUser)s")).
+
+del_user_return_password(_LServer, LUser, Password) ->
+ P =
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(password)s from users where username=%(LUser)s")),
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from users"
+ " where username=%(LUser)s and password=%(Password)s")),
+ P.
+
+list_users(LServer) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(username)s from users")).
+
+list_users(LServer, [{from, Start}, {to, End}])
+ when is_integer(Start) and is_integer(End) ->
+ list_users(LServer,
+ [{limit, End - Start + 1}, {offset, Start - 1}]);
+list_users(LServer,
+ [{prefix, Prefix}, {from, Start}, {to, End}])
+ when is_binary(Prefix) and is_integer(Start) and
+ is_integer(End) ->
+ list_users(LServer,
+ [{prefix, Prefix}, {limit, End - Start + 1},
+ {offset, Start - 1}]);
+list_users(LServer, [{limit, Limit}, {offset, Offset}])
+ when is_integer(Limit) and is_integer(Offset) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(username)s from users "
+ "order by username "
+ "limit %(Limit)d offset %(Offset)d"));
+list_users(LServer,
+ [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
+ when is_binary(Prefix) and is_integer(Limit) and
+ is_integer(Offset) ->
+ SPrefix = ejabberd_sql:escape_like_arg(Prefix),
+ SPrefix2 = <<SPrefix/binary, $%>>,
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(username)s from users "
+ "where username like %(SPrefix2)s "
+ "order by username "
+ "limit %(Limit)d offset %(Offset)d")).
+
+users_number(LServer) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ fun(pgsql, _) ->
+ case
+ ejabberd_config:get_option(
+ {pgsql_users_number_estimate, LServer},
+ fun(V) when is_boolean(V) -> V end,
+ false) of
+ true ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(reltuples :: bigint)d from pg_class"
+ " where oid = 'users'::regclass::oid"));
+ _ ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(count(*))d from users"))
+ end;
+ (_Type, _) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(count(*))d from users"))
+ end).
+
+users_number(LServer, [{prefix, Prefix}])
+ when is_binary(Prefix) ->
+ SPrefix = ejabberd_sql:escape_like_arg(Prefix),
+ SPrefix2 = <<SPrefix/binary, $%>>,
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(count(*))d from users "
+ "where username like %(SPrefix2)s"));
+users_number(LServer, []) ->
+ users_number(LServer).
+
+add_spool_sql(Username, XML) ->
+ [<<"insert into spool(username, xml) values ('">>,
+ Username, <<"', '">>, XML, <<"');">>].
+
+add_spool(LServer, Queries) ->
+ ejabberd_sql:sql_transaction(LServer, Queries).
+
+get_and_del_spool_msg_t(LServer, LUser) ->
+ F = fun () ->
+ Result =
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(username)s, @(xml)s from spool where "
+ "username=%(LUser)s order by seq;")),
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from spool where username=%(LUser)s;")),
+ Result
+ end,
+ ejabberd_sql:sql_transaction(LServer, F).
+
+del_spool_msg(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from spool where username=%(LUser)s")).
+
+get_roster(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s, "
+ "@(ask)s, @(askmessage)s, @(server)s, @(subscribe)s, "
+ "@(type)s from rosterusers where username=%(LUser)s")).
+
+get_roster_jid_groups(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(jid)s, @(grp)s from rostergroups where "
+ "username=%(LUser)s")).
+
+get_roster_groups(_LServer, LUser, SJID) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(grp)s from rostergroups"
+ " where username=%(LUser)s and jid=%(SJID)s")).
+
+del_user_roster_t(LServer, LUser) ->
+ ejabberd_sql:sql_transaction(
+ LServer,
+ fun () ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from rosterusers where username=%(LUser)s")),
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from rostergroups where username=%(LUser)s"))
+ end).
+
+get_roster_by_jid(_LServer, LUser, SJID) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s,"
+ " @(ask)s, @(askmessage)s, @(server)s, @(subscribe)s,"
+ " @(type)s from rosterusers"
+ " where username=%(LUser)s and jid=%(SJID)s")).
+
+get_rostergroup_by_jid(LServer, LUser, SJID) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(grp)s from rostergroups"
+ " where username=%(LUser)s and jid=%(SJID)s")).
+
+del_roster(_LServer, LUser, SJID) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from rosterusers"
+ " where username=%(LUser)s and jid=%(SJID)s")),
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from rostergroups"
+ " where username=%(LUser)s and jid=%(SJID)s")).
+
+del_roster_sql(Username, SJID) ->
+ [[<<"delete from rosterusers where "
+ "username='">>,
+ Username, <<"' and jid='">>, SJID, <<"';">>],
+ [<<"delete from rostergroups where "
+ "username='">>,
+ Username, <<"' and jid='">>, SJID, <<"';">>]].
+
+update_roster(_LServer, LUser, SJID, ItemVals,
+ ItemGroups) ->
+ roster_subscribe(ItemVals),
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from rostergroups"
+ " where username=%(LUser)s and jid=%(SJID)s")),
+ lists:foreach(
+ fun(ItemGroup) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("insert into rostergroups(username, jid, grp) "
+ "values (%(LUser)s, %(SJID)s, %(ItemGroup)s)"))
+ end,
+ ItemGroups).
+
+update_roster_sql(Username, SJID, ItemVals,
+ ItemGroups) ->
+ [[<<"delete from rosterusers where "
+ "username='">>,
+ Username, <<"' and jid='">>, SJID, <<"';">>],
+ [<<"insert into rosterusers( "
+ " username, jid, nick, "
+ " subscription, ask, askmessage, "
+ " server, subscribe, type) "
+ "values ('">>,
+ join(ItemVals, <<"', '">>), <<"');">>],
+ [<<"delete from rostergroups where "
+ "username='">>,
+ Username, <<"' and jid='">>, SJID, <<"';">>]]
+ ++
+ [[<<"insert into rostergroups( "
+ " username, jid, grp) values ('">>,
+ join(ItemGroup, <<"', '">>), <<"');">>]
+ || ItemGroup <- ItemGroups].
+
+roster_subscribe({LUser, SJID, Name, SSubscription, SAsk, AskMessage}) ->
+ ?SQL_UPSERT_T(
+ "rosterusers",
+ ["!username=%(LUser)s",
+ "!jid=%(SJID)s",
+ "nick=%(Name)s",
+ "subscription=%(SSubscription)s",
+ "ask=%(SAsk)s",
+ "askmessage=%(AskMessage)s",
+ "server='N'",
+ "subscribe=''",
+ "type='item'"]).
+
+get_subscription(LServer, LUser, SJID) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(subscription)s from rosterusers "
+ "where username=%(LUser)s and jid=%(SJID)s")).
+
+set_private_data(_LServer, LUser, XMLNS, SData) ->
+ ?SQL_UPSERT_T(
+ "private_storage",
+ ["!username=%(LUser)s",
+ "!namespace=%(XMLNS)s",
+ "data=%(SData)s"]).
+
+set_private_data_sql(Username, LXMLNS, SData) ->
+ [[<<"delete from private_storage where username='">>,
+ Username, <<"' and namespace='">>, LXMLNS, <<"';">>],
+ [<<"insert into private_storage(username, "
+ "namespace, data) values ('">>,
+ Username, <<"', '">>, LXMLNS, <<"', '">>, SData,
+ <<"');">>]].
+
+get_private_data(LServer, LUser, XMLNS) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(data)s from private_storage"
+ " where username=%(LUser)s and namespace=%(XMLNS)s")).
+
+get_private_data(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(namespace)s, @(data)s from private_storage"
+ " where username=%(LUser)s")).
+
+del_user_private_storage(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from private_storage"
+ " where username=%(LUser)s")).
+
+set_vcard(LServer, LUser, BDay, CTRY, EMail, FN,
+ Family, Given, LBDay, LCTRY, LEMail, LFN,
+ LFamily, LGiven, LLocality, LMiddle, LNickname,
+ LOrgName, LOrgUnit, Locality, Middle, Nickname,
+ OrgName, OrgUnit, SVCARD, User) ->
+ ejabberd_sql:sql_transaction(
+ LServer,
+ fun() ->
+ ?SQL_UPSERT(LServer, "vcard",
+ ["!username=%(LUser)s",
+ "vcard=%(SVCARD)s"]),
+ ?SQL_UPSERT(LServer, "vcard_search",
+ ["username=%(User)s",
+ "!lusername=%(LUser)s",
+ "fn=%(FN)s",
+ "lfn=%(LFN)s",
+ "family=%(Family)s",
+ "lfamily=%(LFamily)s",
+ "given=%(Given)s",
+ "lgiven=%(LGiven)s",
+ "middle=%(Middle)s",
+ "lmiddle=%(LMiddle)s",
+ "nickname=%(Nickname)s",
+ "lnickname=%(LNickname)s",
+ "bday=%(BDay)s",
+ "lbday=%(LBDay)s",
+ "ctry=%(CTRY)s",
+ "lctry=%(LCTRY)s",
+ "locality=%(Locality)s",
+ "llocality=%(LLocality)s",
+ "email=%(EMail)s",
+ "lemail=%(LEMail)s",
+ "orgname=%(OrgName)s",
+ "lorgname=%(LOrgName)s",
+ "orgunit=%(OrgUnit)s",
+ "lorgunit=%(LOrgUnit)s"])
+ end).
+
+get_vcard(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(vcard)s from vcard where username=%(LUser)s")).
+
+get_default_privacy_list(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(name)s from privacy_default_list "
+ "where username=%(LUser)s")).
+
+get_default_privacy_list_t(LUser) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(name)s from privacy_default_list "
+ "where username=%(LUser)s")).
+
+get_privacy_list_names(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(name)s from privacy_list"
+ " where username=%(LUser)s")).
+
+get_privacy_list_names_t(LUser) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(name)s from privacy_list"
+ " where username=%(LUser)s")).
+
+get_privacy_list_id(LServer, LUser, Name) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(id)d from privacy_list"
+ " where username=%(LUser)s and name=%(Name)s")).
+
+get_privacy_list_id_t(LUser, Name) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(id)d from privacy_list"
+ " where username=%(LUser)s and name=%(Name)s")).
+
+get_privacy_list_data(LServer, LUser, Name) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, "
+ "@(match_iq)b, @(match_message)b, @(match_presence_in)b, "
+ "@(match_presence_out)b from privacy_list_data "
+ "where id ="
+ " (select id from privacy_list"
+ " where username=%(LUser)s and name=%(Name)s) "
+ "order by ord")).
+
+%% Not used?
+get_privacy_list_data_t(LUser, Name) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, "
+ "@(match_iq)b, @(match_message)b, @(match_presence_in)b, "
+ "@(match_presence_out)b from privacy_list_data "
+ "where id ="
+ " (select id from privacy_list"
+ " where username=%(LUser)s and name=%(Name)s) "
+ "order by ord")).
+
+get_privacy_list_data_by_id(LServer, ID) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, "
+ "@(match_iq)b, @(match_message)b, @(match_presence_in)b, "
+ "@(match_presence_out)b from privacy_list_data "
+ "where id=%(ID)d order by ord")).
+
+get_privacy_list_data_by_id_t(ID) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, "
+ "@(match_iq)b, @(match_message)b, @(match_presence_in)b, "
+ "@(match_presence_out)b from privacy_list_data "
+ "where id=%(ID)d order by ord")).
+
+set_default_privacy_list(LUser, Name) ->
+ ?SQL_UPSERT_T(
+ "privacy_default_list",
+ ["!username=%(LUser)s",
+ "name=%(Name)s"]).
+
+unset_default_privacy_list(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from privacy_default_list"
+ " where username=%(LUser)s")).
+
+remove_privacy_list(LUser, Name) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from privacy_list where"
+ " username=%(LUser)s and name=%(Name)s")).
+
+add_privacy_list(LUser, Name) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("insert into privacy_list(username, name) "
+ "values (%(LUser)s, %(Name)s)")).
+
+set_privacy_list(ID, RItems) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("delete from privacy_list_data where id=%(ID)d")),
+ lists:foreach(
+ fun({SType, SValue, SAction, Order, MatchAll, MatchIQ,
+ MatchMessage, MatchPresenceIn, MatchPresenceOut}) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("insert into privacy_list_data(id, t, "
+ "value, action, ord, match_all, match_iq, "
+ "match_message, match_presence_in, match_presence_out) "
+ "values (%(ID)d, %(SType)s, %(SValue)s, %(SAction)s,"
+ " %(Order)d, %(MatchAll)b, %(MatchIQ)b,"
+ " %(MatchMessage)b, %(MatchPresenceIn)b,"
+ " %(MatchPresenceOut)b)"))
+ end,
+ RItems).
+
+del_privacy_lists(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from privacy_list where username=%(LUser)s")),
+ %US = <<LUser/binary, "@", LServer/binary>>,
+ %ejabberd_sql:sql_query(
+ % LServer,
+ % ?SQL("delete from privacy_list_data where value=%(US)s")),
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from privacy_default_list where username=%(LUser)s")).
+
+%% Characters to escape
+escape($\000) -> <<"\\0">>;
+escape($\n) -> <<"\\n">>;
+escape($\t) -> <<"\\t">>;
+escape($\b) -> <<"\\b">>;
+escape($\r) -> <<"\\r">>;
+escape($') -> <<"''">>;
+escape($") -> <<"\\\"">>;
+escape($\\) -> <<"\\\\">>;
+escape(C) -> <<C>>.
+
+%% Count number of records in a table given a where clause
+count_records_where(LServer, Table, WhereClause) ->
+ ejabberd_sql:sql_query(LServer,
+ [<<"select count(*) from ">>, Table, <<" ">>,
+ WhereClause, <<";">>]).
+
+get_roster_version(LServer, LUser) ->
+ ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(version)s from roster_version"
+ " where username = %(LUser)s")).
+
+set_roster_version(LUser, Version) ->
+ update_t(<<"roster_version">>,
+ [<<"username">>, <<"version">>], [LUser, Version],
+ [<<"username = '">>, LUser, <<"'">>]).
+
+opt_type(sql_type) ->
+ fun (pgsql) -> pgsql;
+ (mysql) -> mysql;
+ (sqlite) -> sqlite;
+ (mssql) -> mssql;
+ (odbc) -> odbc
+ end;
+opt_type(pgsql_users_number_estimate) ->
+ fun (V) when is_boolean(V) -> V end;
+opt_type(_) -> [sql_type, pgsql_users_number_estimate].
diff --git a/test/README-quicktest.md b/test/README-quicktest.md
new file mode 100644
index 00000000..43c71e86
--- /dev/null
+++ b/test/README-quicktest.md
@@ -0,0 +1,33 @@
+# Elixir unit tests
+
+## Running Elixir unit tests
+
+You can run Elixir unit tests with command:
+
+make quicktest
+
+You need to have ejabberd compile with Elixir and tools enabled.
+
+## Troubleshooting test
+
+To help with troubleshooting Elixir tests, we have added a special macro in ejabberd `logger.hrl` include file: ?EXUNIT_LOG
+
+To use this, in test file:
+
+1. in `setup_all, add:
+
+ ```
+ Application.start(:logger)
+ ```
+
+2. Enable log capture for the test you want to analyse by adding
+ `capture_log` tag before test implementation:
+
+ ```
+ @tag capture_log: true
+ ```
+
+In the ejabberd code, if `logger.hrl` is included, you can code adds a
+EXUNIT_LOG macro:
+
+ ?EXUNIT_LOG("My debug log:~p ~p", [Arg1, Arg2])
diff --git a/test/acl_test.exs b/test/acl_test.exs
new file mode 100644
index 00000000..db658430
--- /dev/null
+++ b/test/acl_test.exs
@@ -0,0 +1,128 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2016 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
+ :ok = :jid.start
+ :ok = :ejabberd_config.start(["domain1", "domain2"], [])
+ :ok = :acl.start
+ 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_access(:global, :basic_rule_1, [{:basic_acl_1, :allow}])
+ # 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
+ # 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("test11@domain1")) == :deny
+
+ :acl.add(:global, :basic_acl_2, {:user, {"test2", "domain1"}})
+ :acl.add_access(:global, :basic_rule_2, [{:basic_acl_2, :allow}])
+ 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, [{:ip_acl_1, :allow}])
+ # 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, [{:user_acl_1, :deny}, {:user_acl_2, :allow}])
+ 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, [{:admin_acl, 10}, {:user_acl, 5}])
+ 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, [{:user_acl_1, :allow}, {:ip_acl_1, :allow}])
+ 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, [{:user_acl_1, :deny}, {:ip_acl_1, :allow}])
+ 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 "acl:match_access can match directly on user pattern" do
+ pattern = {:user, {"test1", "domain1"}}
+ assert :acl.match_access(:global, pattern, :jid.from_string("test1@domain1"), :allow) == :allow
+ assert :acl.match_access(:global, pattern, :jid.from_string("test2@domain1"), :allow) == :deny
+ 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, [{:user_acl, :allow}])
+ :acl.add_access(:global, :ip_rule, [{:ip_acl, :allow}])
+
+ # 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 543437e0..e2f26807 100644
--- a/test/ejabberd_SUITE.erl
+++ b/test/ejabberd_SUITE.erl
@@ -12,8 +12,8 @@
-import(suite, [init_config/1, connect/1, disconnect/1,
recv/0, send/2, send_recv/2, my_jid/1, server_jid/1,
- pubsub_jid/1, proxy_jid/1, muc_jid/1,
- muc_room_jid/1, get_features/2, re_register/1,
+ pubsub_jid/1, proxy_jid/1, muc_jid/1, muc_room_jid/1,
+ mix_jid/1, mix_room_jid/1, get_features/2, re_register/1,
is_feature_advertised/2, subscribe_to_events/1,
is_feature_advertised/3, set_opt/3, auth_SASL/2,
wait_for_master/1, wait_for_slave/1,
@@ -35,20 +35,58 @@ init_per_suite(Config) ->
LDIFFile = filename:join([DataDir, "ejabberd.ldif"]),
{ok, _} = file:copy(ExtAuthScript, filename:join([CWD, "extauth.py"])),
{ok, _} = ldap_srv:start(LDIFFile),
- ok = application:start(ejabberd),
+ start_ejabberd(NewConfig),
NewConfig.
+start_ejabberd(Config) ->
+ case proplists:get_value(backends, Config) of
+ all ->
+ ok = application:start(ejabberd, transient);
+ Backends when is_list(Backends) ->
+ Hosts = lists:map(fun(Backend) -> Backend ++ ".localhost" end, Backends),
+ application:load(ejabberd),
+ AllHosts = Hosts ++ ["localhost"], %% We always need localhost for the generic no_db tests
+ application:set_env(ejabberd, hosts, AllHosts),
+ ok = application:start(ejabberd, transient)
+ end.
+
end_per_suite(_Config) ->
- ok.
+ application:stop(ejabberd).
+
+-define(BACKENDS, [mnesia,redis,mysql,pgsql,sqlite,ldap,extauth,riak]).
+
+init_per_group(Group, Config) ->
+ case lists:member(Group, ?BACKENDS) of
+ false ->
+ %% Not a backend related group, do default init:
+ do_init_per_group(Group, Config);
+ true ->
+ case proplists:get_value(backends, Config) of
+ all ->
+ %% All backends enabled
+ do_init_per_group(Group, Config);
+ Backends ->
+ %% Skipped backends that were not explicitely enabled
+ case lists:member(atom_to_list(Group), Backends) of
+ true ->
+ do_init_per_group(Group, Config);
+ false ->
+ {skip, {disabled_backend, Group}}
+ end
+ end
+ end.
-init_per_group(no_db, Config) ->
+do_init_per_group(no_db, Config) ->
re_register(Config),
Config;
-init_per_group(mnesia, Config) ->
+do_init_per_group(mnesia, Config) ->
mod_muc:shutdown_rooms(?MNESIA_VHOST),
set_opt(server, ?MNESIA_VHOST, Config);
-init_per_group(mysql, Config) ->
- case catch ejabberd_odbc:sql_query(?MYSQL_VHOST, [<<"select 1;">>]) of
+do_init_per_group(redis, Config) ->
+ mod_muc:shutdown_rooms(?REDIS_VHOST),
+ set_opt(server, ?REDIS_VHOST, Config);
+do_init_per_group(mysql, Config) ->
+ case catch ejabberd_sql:sql_query(?MYSQL_VHOST, [<<"select 1;">>]) of
{selected, _, _} ->
mod_muc:shutdown_rooms(?MYSQL_VHOST),
create_sql_tables(mysql, ?config(base_dir, Config)),
@@ -56,8 +94,8 @@ init_per_group(mysql, Config) ->
Err ->
{skip, {mysql_not_available, Err}}
end;
-init_per_group(pgsql, Config) ->
- case catch ejabberd_odbc:sql_query(?PGSQL_VHOST, [<<"select 1;">>]) of
+do_init_per_group(pgsql, Config) ->
+ case catch ejabberd_sql:sql_query(?PGSQL_VHOST, [<<"select 1;">>]) of
{selected, _, _} ->
mod_muc:shutdown_rooms(?PGSQL_VHOST),
create_sql_tables(pgsql, ?config(base_dir, Config)),
@@ -65,19 +103,19 @@ init_per_group(pgsql, Config) ->
Err ->
{skip, {pgsql_not_available, Err}}
end;
-init_per_group(sqlite, Config) ->
- case catch ejabberd_odbc:sql_query(?SQLITE_VHOST, [<<"select 1;">>]) of
+do_init_per_group(sqlite, Config) ->
+ case catch ejabberd_sql:sql_query(?SQLITE_VHOST, [<<"select 1;">>]) of
{selected, _, _} ->
mod_muc:shutdown_rooms(?SQLITE_VHOST),
set_opt(server, ?SQLITE_VHOST, Config);
Err ->
{skip, {sqlite_not_available, Err}}
end;
-init_per_group(ldap, Config) ->
+do_init_per_group(ldap, Config) ->
set_opt(server, ?LDAP_VHOST, Config);
-init_per_group(extauth, Config) ->
+do_init_per_group(extauth, Config) ->
set_opt(server, ?EXTAUTH_VHOST, Config);
-init_per_group(riak, Config) ->
+do_init_per_group(riak, Config) ->
case ejabberd_riak:is_connected() of
true ->
mod_muc:shutdown_rooms(?RIAK_VHOST),
@@ -86,12 +124,14 @@ init_per_group(riak, Config) ->
Err ->
{skip, {riak_not_available, Err}}
end;
-init_per_group(_GroupName, Config) ->
+do_init_per_group(_GroupName, Config) ->
Pid = start_event_relay(),
set_opt(event_relay, Pid, Config).
end_per_group(mnesia, _Config) ->
ok;
+end_per_group(redis, _Config) ->
+ ok;
end_per_group(mysql, _Config) ->
ok;
end_per_group(pgsql, _Config) ->
@@ -131,14 +171,14 @@ init_per_testcase(TestCase, OrigConfig) ->
true -> Resource
end,
Slave = if IsCarbons ->
- jlib:make_jid(<<"test_master">>, Server, SlaveResource);
+ jid:make(<<"test_master">>, Server, SlaveResource);
true ->
- jlib:make_jid(<<"test_slave">>, Server, Resource)
+ jid:make(<<"test_slave">>, Server, Resource)
end,
Master = if IsCarbons ->
- jlib:make_jid(<<"test_master">>, Server, MasterResource);
+ jid:make(<<"test_master">>, Server, MasterResource);
true ->
- jlib:make_jid(<<"test_master">>, Server, Resource)
+ jid:make(<<"test_master">>, Server, Resource)
end,
Config = set_opt(user, User,
set_opt(slave, Slave,
@@ -214,6 +254,8 @@ db_tests(riak) ->
{test_roster_subscribe, [parallel],
[roster_subscribe_master,
roster_subscribe_slave]},
+ {test_flex_offline, [sequence],
+ [flex_offline_master, flex_offline_slave]},
{test_offline, [sequence],
[offline_master, offline_slave]},
{test_muc, [parallel],
@@ -225,7 +267,7 @@ db_tests(riak) ->
{test_roster_remove, [parallel],
[roster_remove_master,
roster_remove_slave]}];
-db_tests(mnesia) ->
+db_tests(DB) when DB == mnesia; DB == redis ->
[{single_user, [sequence],
[test_register,
auth_plain,
@@ -242,9 +284,13 @@ db_tests(mnesia) ->
test_unregister]},
{test_muc_register, [sequence],
[muc_register_master, muc_register_slave]},
+ {test_mix, [parallel],
+ [mix_master, mix_slave]},
{test_roster_subscribe, [parallel],
[roster_subscribe_master,
roster_subscribe_slave]},
+ {test_flex_offline, [sequence],
+ [flex_offline_master, flex_offline_slave]},
{test_offline, [sequence],
[offline_master, offline_slave]},
{test_old_mam, [parallel],
@@ -282,9 +328,13 @@ db_tests(_) ->
test_unregister]},
{test_muc_register, [sequence],
[muc_register_master, muc_register_slave]},
+ {test_mix, [parallel],
+ [mix_master, mix_slave]},
{test_roster_subscribe, [parallel],
[roster_subscribe_master,
roster_subscribe_slave]},
+ {test_flex_offline, [sequence],
+ [flex_offline_master, flex_offline_slave]},
{test_offline, [sequence],
[offline_master, offline_slave]},
{test_old_mam, [parallel],
@@ -304,7 +354,8 @@ db_tests(_) ->
ldap_tests() ->
[{ldap_tests, [sequence],
[test_auth,
- vcard_get]}].
+ vcard_get,
+ ldap_shared_roster_get]}].
extauth_tests() ->
[{extauth_tests, [sequence],
@@ -316,6 +367,7 @@ groups() ->
{extauth, [sequence], extauth_tests()},
{no_db, [sequence], no_db_tests()},
{mnesia, [sequence], db_tests(mnesia)},
+ {redis, [sequence], db_tests(redis)},
{mysql, [sequence], db_tests(mysql)},
{pgsql, [sequence], db_tests(pgsql)},
{sqlite, [sequence], db_tests(sqlite)},
@@ -325,6 +377,7 @@ all() ->
[{group, ldap},
{group, no_db},
{group, mnesia},
+ {group, redis},
{group, mysql},
{group, pgsql},
{group, sqlite},
@@ -549,7 +602,7 @@ disco(Config) ->
sm(Config) ->
Server = ?config(server, Config),
- ServerJID = jlib:make_jid(<<"">>, Server, <<"">>),
+ ServerJID = jid:make(<<"">>, Server, <<"">>),
Msg = #message{to = ServerJID, body = [#text{data = <<"body">>}]},
true = ?config(sm, Config),
%% Enable the session management with resumption enabled
@@ -571,7 +624,7 @@ sm_resume(Config) ->
{sm, SMConfig} = ?config(saved_config, Config),
ID = ?config(sm_previd, SMConfig),
Server = ?config(server, Config),
- ServerJID = jlib:make_jid(<<"">>, Server, <<"">>),
+ ServerJID = jid:make(<<"">>, Server, <<"">>),
MyJID = my_jid(Config),
Txt = #text{data = <<"body">>},
Msg = #message{from = ServerJID, to = MyJID, body = [Txt]},
@@ -587,7 +640,7 @@ sm_resume(Config) ->
private(Config) ->
Conference = #bookmark_conference{name = <<"Some name">>,
autojoin = true,
- jid = jlib:make_jid(
+ jid = jid:make(
<<"some">>,
<<"some.conference.org">>,
<<>>)},
@@ -675,7 +728,7 @@ privacy(Config) ->
blocking(Config) ->
true = is_feature_advertised(Config, ?NS_BLOCKING),
- JID = jlib:make_jid(<<"romeo">>, <<"montague.net">>, <<>>),
+ JID = jid:make(<<"romeo">>, <<"montague.net">>, <<>>),
#iq{type = result, sub_els = [#block_list{}]} =
send_recv(Config, #iq{type = get, sub_els = [#block_list{}]}),
I1 = send(Config, #iq{type = set,
@@ -748,6 +801,13 @@ vcard_get(Config) ->
send_recv(Config, #iq{type = get, sub_els = [#vcard{}]}),
disconnect(Config).
+ldap_shared_roster_get(Config) ->
+ Item = #roster_item{jid = jid:from_string(<<"user2@ldap.localhost">>), name = <<"Test User 2">>,
+ groups = [<<"group1">>], subscription = both},
+ #iq{type = result, sub_els = [#roster{items = [Item]}]} =
+ send_recv(Config, #iq{type = get, sub_els = [#roster{}]}),
+ disconnect(Config).
+
vcard_xupdate_master(Config) ->
Img = <<137, "PNG\r\n", 26, $\n>>,
ImgHash = p1_sha:sha(Img),
@@ -869,12 +929,96 @@ pubsub(Config) ->
jid = my_jid(Config)}}]}),
disconnect(Config).
+mix_master(Config) ->
+ MIX = mix_jid(Config),
+ Room = mix_room_jid(Config),
+ MyJID = my_jid(Config),
+ MyBareJID = jid:remove_resource(MyJID),
+ true = is_feature_advertised(Config, ?NS_MIX_0, MIX),
+ #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],
+ I0 = send(Config, #iq{type = set, to = Room,
+ sub_els = [#mix_join{subscribe = Nodes}]}),
+ {_, #message{sub_els =
+ [#pubsub_event{
+ items = [#pubsub_event_items{
+ node = ?NS_MIX_NODES_PARTICIPANTS,
+ items = [#pubsub_event_item{
+ id = ParticipantID,
+ xml_els = [PXML]}]}]}]}} =
+ ?recv2(#iq{type = result, id = I0,
+ sub_els = [#mix_join{subscribe = Nodes, jid = MyBareJID}]},
+ #message{from = Room}),
+ #mix_participant{jid = MyBareJID} = xmpp_codec:decode(PXML),
+ %% Coming online
+ PresenceID = randoms:get_string(),
+ Presence = xmpp_codec:encode(#presence{}),
+ I1 = send(
+ Config,
+ #iq{type = set, to = Room,
+ sub_els =
+ [#pubsub{
+ publish = #pubsub_publish{
+ node = ?NS_MIX_NODES_PRESENCE,
+ items = [#pubsub_item{
+ id = PresenceID,
+ xml_els = [Presence]}]}}]}),
+ ?recv2(#iq{type = result, id = I1,
+ sub_els =
+ [#pubsub{
+ publish = #pubsub_publish{
+ node = ?NS_MIX_NODES_PRESENCE,
+ items = [#pubsub_item{id = PresenceID}]}}]},
+ #message{from = Room,
+ sub_els =
+ [#pubsub_event{
+ items = [#pubsub_event_items{
+ node = ?NS_MIX_NODES_PRESENCE,
+ items = [#pubsub_event_item{
+ id = PresenceID,
+ xml_els = [Presence]}]}]}]}),
+ %% Coming offline
+ send(Config, #presence{type = unavailable, to = Room}),
+ %% Receiving presence retract event
+ #message{from = Room,
+ sub_els = [#pubsub_event{
+ items = [#pubsub_event_items{
+ node = ?NS_MIX_NODES_PRESENCE,
+ retract = [PresenceID]}]}]} = recv(),
+ %% Leaving
+ I2 = send(Config, #iq{type = set, to = Room, sub_els = [#mix_leave{}]}),
+ ?recv2(#iq{type = result, id = I2, sub_els = []},
+ #message{from = Room,
+ sub_els =
+ [#pubsub_event{
+ items = [#pubsub_event_items{
+ node = ?NS_MIX_NODES_PARTICIPANTS,
+ retract = [ParticipantID]}]}]}),
+ disconnect(Config).
+
+mix_slave(Config) ->
+ disconnect(Config).
+
roster_subscribe_master(Config) ->
send(Config, #presence{}),
?recv1(#presence{}),
wait_for_slave(Config),
Peer = ?config(slave, Config),
- LPeer = jlib:jid_remove_resource(Peer),
+ LPeer = jid:remove_resource(Peer),
send(Config, #presence{type = subscribe, to = LPeer}),
Push1 = ?recv1(#iq{type = set,
sub_els = [#roster{items = [#roster_item{
@@ -926,7 +1070,7 @@ roster_subscribe_slave(Config) ->
?recv1(#presence{}),
wait_for_master(Config),
Peer = ?config(master, Config),
- LPeer = jlib:jid_remove_resource(Peer),
+ LPeer = jid:remove_resource(Peer),
?recv1(#presence{type = subscribe, from = LPeer}),
send(Config, #presence{type = subscribed, to = LPeer}),
Push1 = ?recv1(#iq{type = set,
@@ -955,7 +1099,7 @@ roster_subscribe_slave(Config) ->
roster_remove_master(Config) ->
MyJID = my_jid(Config),
Peer = ?config(slave, Config),
- LPeer = jlib:jid_remove_resource(Peer),
+ LPeer = jid:remove_resource(Peer),
Groups = [<<"A">>, <<"B">>],
wait_for_slave(Config),
send(Config, #presence{}),
@@ -989,7 +1133,7 @@ roster_remove_master(Config) ->
roster_remove_slave(Config) ->
MyJID = my_jid(Config),
Peer = ?config(master, Config),
- LPeer = jlib:jid_remove_resource(Peer),
+ LPeer = jid:remove_resource(Peer),
send(Config, #presence{}),
?recv1(#presence{from = MyJID, type = undefined}),
wait_for_master(Config),
@@ -1048,16 +1192,16 @@ proxy65_slave(Config) ->
muc_master(Config) ->
MyJID = my_jid(Config),
PeerJID = ?config(slave, Config),
- PeerBareJID = jlib:jid_remove_resource(PeerJID),
- PeerJIDStr = jlib:jid_to_string(PeerJID),
+ PeerBareJID = jid:remove_resource(PeerJID),
+ PeerJIDStr = jid:to_string(PeerJID),
MUC = muc_jid(Config),
Room = muc_room_jid(Config),
MyNick = ?config(master_nick, Config),
- MyNickJID = jlib:jid_replace_resource(Room, MyNick),
+ MyNickJID = jid:replace_resource(Room, MyNick),
PeerNick = ?config(slave_nick, Config),
- PeerNickJID = jlib:jid_replace_resource(Room, PeerNick),
+ PeerNickJID = jid:replace_resource(Room, PeerNick),
Subject = ?config(room_subject, Config),
- Localhost = jlib:make_jid(<<"">>, <<"localhost">>, <<"">>),
+ Localhost = jid:make(<<"">>, <<"localhost">>, <<"">>),
true = is_feature_advertised(Config, ?NS_MUC, MUC),
%% Joining
send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}),
@@ -1126,7 +1270,7 @@ muc_master(Config) ->
%% Sending messages (and thus, populating history for our peer)
lists:foreach(
fun(N) ->
- Text = #text{data = jlib:integer_to_binary(N)},
+ Text = #text{data = integer_to_binary(N)},
I = send(Config, #message{to = Room, body = [Text],
type = groupchat}),
?recv1(#message{from = MyNickJID, id = I,
@@ -1204,6 +1348,16 @@ muc_master(Config) ->
%% Receive groupchat message from the peer
?recv1(#message{type = groupchat, from = PeerNickJID,
body = [#text{data = Subject}]}),
+ %% Retrieving a member list
+ #iq{type = result, sub_els = [#muc_admin{items = MemberList}]} =
+ send_recv(Config,
+ #iq{type = get, to = Room,
+ sub_els =
+ [#muc_admin{items = [#muc_item{affiliation = member}]}]}),
+ [#muc_item{affiliation = member,
+ jid = Localhost},
+ #muc_item{affiliation = member,
+ jid = MyBareJID}] = lists:keysort(#muc_item.jid, MemberList),
%% Kick the peer
I2 = send(Config,
#iq{type = set, to = Room,
@@ -1238,16 +1392,16 @@ muc_master(Config) ->
muc_slave(Config) ->
MyJID = my_jid(Config),
- MyBareJID = jlib:jid_remove_resource(MyJID),
+ MyBareJID = jid:remove_resource(MyJID),
PeerJID = ?config(master, Config),
MUC = muc_jid(Config),
Room = muc_room_jid(Config),
MyNick = ?config(slave_nick, Config),
- MyNickJID = jlib:jid_replace_resource(Room, MyNick),
+ MyNickJID = jid:replace_resource(Room, MyNick),
PeerNick = ?config(master_nick, Config),
- PeerNickJID = jlib:jid_replace_resource(Room, PeerNick),
+ PeerNickJID = jid:replace_resource(Room, PeerNick),
Subject = ?config(room_subject, Config),
- Localhost = jlib:make_jid(<<"">>, <<"localhost">>, <<"">>),
+ Localhost = jid:make(<<"">>, <<"localhost">>, <<"">>),
%% Receive an invite from the peer
?recv1(#message{from = Room, type = normal,
sub_els =
@@ -1290,7 +1444,7 @@ muc_slave(Config) ->
%% Receive MUC history
lists:foreach(
fun(N) ->
- Text = #text{data = jlib:integer_to_binary(N)},
+ Text = #text{data = integer_to_binary(N)},
?recv1(#message{from = PeerNickJID,
type = groupchat,
body = [Text],
@@ -1323,16 +1477,6 @@ muc_slave(Config) ->
#muc_user{
items = [#muc_item{role = participant,
affiliation = member}]}]}),
- %% Retrieving a member list
- #iq{type = result, sub_els = [#muc_admin{items = MemberList}]} =
- send_recv(Config,
- #iq{type = get, to = Room,
- sub_els =
- [#muc_admin{items = [#muc_item{affiliation = member}]}]}),
- [#muc_item{affiliation = member,
- jid = Localhost},
- #muc_item{affiliation = member,
- jid = MyBareJID}] = lists:keysort(#muc_item.jid, MemberList),
%% Sending groupchat message
send(Config, #message{to = Room, type = groupchat,
body = [#text{data = Subject}]}),
@@ -1411,7 +1555,7 @@ muc_register_slave(Config) ->
announce_master(Config) ->
MyJID = my_jid(Config),
ServerJID = server_jid(Config),
- MotdJID = jlib:jid_replace_resource(ServerJID, <<"announce/motd">>),
+ MotdJID = jid:replace_resource(ServerJID, <<"announce/motd">>),
MotdText = #text{data = <<"motd">>},
send(Config, #presence{}),
?recv1(#presence{from = MyJID}),
@@ -1424,7 +1568,7 @@ announce_master(Config) ->
announce_slave(Config) ->
MyJID = my_jid(Config),
ServerJID = server_jid(Config),
- MotdDelJID = jlib:jid_replace_resource(ServerJID, <<"announce/motd/delete">>),
+ MotdDelJID = jid:replace_resource(ServerJID, <<"announce/motd/delete">>),
MotdText = #text{data = <<"motd">>},
send(Config, #presence{}),
?recv2(#presence{from = MyJID},
@@ -1433,9 +1577,138 @@ announce_slave(Config) ->
send(Config, #message{to = MotdDelJID}),
disconnect(Config).
+flex_offline_master(Config) ->
+ Peer = ?config(slave, Config),
+ LPeer = jid:remove_resource(Peer),
+ lists:foreach(
+ fun(I) ->
+ Body = integer_to_binary(I),
+ send(Config, #message{to = LPeer,
+ body = [#text{data = Body}],
+ subject = [#text{data = <<"subject">>}]})
+ end, lists:seq(1, 5)),
+ disconnect(Config).
+
+flex_offline_slave(Config) ->
+ MyJID = my_jid(Config),
+ MyBareJID = jid:remove_resource(MyJID),
+ Peer = ?config(master, Config),
+ Peer_s = jid:to_string(Peer),
+ true = is_feature_advertised(Config, ?NS_FLEX_OFFLINE),
+ %% Request disco#info
+ #iq{type = result,
+ sub_els = [#disco_info{
+ node = ?NS_FLEX_OFFLINE,
+ identities = Ids,
+ features = Fts,
+ xdata = [X]}]} =
+ send_recv(Config, #iq{type = get,
+ sub_els = [#disco_info{
+ node = ?NS_FLEX_OFFLINE}]}),
+ %% Check if we have correct identities
+ true = lists:any(
+ fun(#identity{category = <<"automation">>,
+ type = <<"message-list">>}) -> true;
+ (_) -> false
+ end, Ids),
+ %% Check if we have needed feature
+ true = lists:member(?NS_FLEX_OFFLINE, Fts),
+ %% Check xdata, the 'number_of_messages' should be 5
+ #xdata{type = result,
+ fields = [#xdata_field{type = hidden,
+ var = <<"FORM_TYPE">>},
+ #xdata_field{var = <<"number_of_messages">>,
+ values = [<<"5">>]}]} = X,
+ %% Fetch headers,
+ #iq{type = result,
+ sub_els = [#disco_items{
+ node = ?NS_FLEX_OFFLINE,
+ items = DiscoItems}]} =
+ send_recv(Config, #iq{type = get,
+ sub_els = [#disco_items{
+ node = ?NS_FLEX_OFFLINE}]}),
+ %% Check if headers are correct
+ Nodes = lists:sort(
+ lists:map(
+ fun(#disco_item{jid = J, name = P, node = N})
+ when (J == MyBareJID) and (P == Peer_s) ->
+ N
+ end, DiscoItems)),
+ %% Since headers are received we can send initial presence without a risk
+ %% of getting offline messages flood
+ send(Config, #presence{}),
+ ?recv1(#presence{from = MyJID}),
+ %% Check full fetch
+ I0 = send(Config, #iq{type = get, sub_els = [#offline{fetch = true}]}),
+ lists:foreach(
+ fun({I, N}) ->
+ Text = integer_to_binary(I),
+ ?recv1(#message{body = Body, sub_els = SubEls}),
+ [#text{data = Text}] = Body,
+ #offline{items = [#offline_item{node = N}]} =
+ lists:keyfind(offline, 1, SubEls),
+ #delay{} = lists:keyfind(delay, 1, SubEls)
+ end, lists:zip(lists:seq(1, 5), Nodes)),
+ ?recv1(#iq{type = result, id = I0, sub_els = []}),
+ %% Fetch 2nd and 4th message
+ I1 = send(Config,
+ #iq{type = get,
+ sub_els = [#offline{
+ items = [#offline_item{
+ action = view,
+ node = lists:nth(2, Nodes)},
+ #offline_item{
+ action = view,
+ node = lists:nth(4, Nodes)}]}]}),
+ lists:foreach(
+ fun({I, N}) ->
+ Text = integer_to_binary(I),
+ ?recv1(#message{body = [#text{data = Text}], sub_els = SubEls}),
+ #offline{items = [#offline_item{node = N}]} =
+ lists:keyfind(offline, 1, SubEls)
+ end, lists:zip([2, 4], [lists:nth(2, Nodes), lists:nth(4, Nodes)])),
+ ?recv1(#iq{type = result, id = I1, sub_els = []}),
+ %% Delete 2nd and 4th message
+ #iq{type = result, sub_els = []} =
+ send_recv(
+ Config,
+ #iq{type = set,
+ sub_els = [#offline{
+ items = [#offline_item{
+ action = remove,
+ node = lists:nth(2, Nodes)},
+ #offline_item{
+ action = remove,
+ node = lists:nth(4, Nodes)}]}]}),
+ %% Check if messages were deleted
+ #iq{type = result,
+ sub_els = [#disco_items{
+ node = ?NS_FLEX_OFFLINE,
+ items = RemainedItems}]} =
+ send_recv(Config, #iq{type = get,
+ sub_els = [#disco_items{
+ node = ?NS_FLEX_OFFLINE}]}),
+ RemainedNodes = [lists:nth(1, Nodes),
+ lists:nth(3, Nodes),
+ lists:nth(5, Nodes)],
+ RemainedNodes = lists:sort(
+ lists:map(
+ fun(#disco_item{node = N}) -> N end,
+ RemainedItems)),
+ %% Purge everything left
+ #iq{type = result, sub_els = []} =
+ send_recv(Config, #iq{type = set, sub_els = [#offline{purge = true}]}),
+ %% Check if there is no offline messages
+ #iq{type = result,
+ sub_els = [#disco_items{node = ?NS_FLEX_OFFLINE, items = []}]} =
+ send_recv(Config, #iq{type = get,
+ sub_els = [#disco_items{
+ node = ?NS_FLEX_OFFLINE}]}),
+ disconnect(Config).
+
offline_master(Config) ->
Peer = ?config(slave, Config),
- LPeer = jlib:jid_remove_resource(Peer),
+ LPeer = jid:remove_resource(Peer),
send(Config, #message{to = LPeer,
body = [#text{data = <<"body">>}],
subject = [#text{data = <<"subject">>}]}),
@@ -1454,7 +1727,7 @@ offline_slave(Config) ->
carbons_master(Config) ->
MyJID = my_jid(Config),
- MyBareJID = jlib:jid_remove_resource(MyJID),
+ MyBareJID = jid:remove_resource(MyJID),
Peer = ?config(slave, Config),
Txt = #text{data = <<"body">>},
true = is_feature_advertised(Config, ?NS_CARBONS_2),
@@ -1508,7 +1781,7 @@ carbons_master(Config) ->
carbons_slave(Config) ->
MyJID = my_jid(Config),
- MyBareJID = jlib:jid_remove_resource(MyJID),
+ MyBareJID = jid:remove_resource(MyJID),
Peer = ?config(master, Config),
Txt = #text{data = <<"body">>},
wait_for_master(Config),
@@ -1576,13 +1849,13 @@ mam_new_master(Config) ->
mam_master(Config, NS) ->
true = is_feature_advertised(Config, NS),
MyJID = my_jid(Config),
- BareMyJID = jlib:jid_remove_resource(MyJID),
+ BareMyJID = jid:remove_resource(MyJID),
Peer = ?config(slave, Config),
send(Config, #presence{}),
?recv1(#presence{}),
wait_for_slave(Config),
?recv1(#presence{from = Peer}),
- #iq{type = result, sub_els = []} =
+ #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = roster}]} =
send_recv(Config,
#iq{type = set,
sub_els = [#mam_prefs{xmlns = NS,
@@ -1607,16 +1880,16 @@ mam_master(Config, NS) ->
wait_for_slave(Config),
lists:foreach(
fun(N) ->
- Text = #text{data = jlib:integer_to_binary(N)},
+ Text = #text{data = integer_to_binary(N)},
send(Config,
#message{to = Peer, body = [Text]})
end, lists:seq(1, 5)),
?recv1(#presence{type = unavailable, from = Peer}),
mam_query_all(Config, NS),
mam_query_with(Config, Peer, NS),
- %% mam_query_with(Config, jlib:jid_remove_resource(Peer)),
+ %% mam_query_with(Config, jid:remove_resource(Peer)),
mam_query_rsm(Config, NS),
- #iq{type = result, sub_els = []} =
+ #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = never}]} =
send_recv(Config, #iq{type = set,
sub_els = [#mam_prefs{xmlns = NS,
default = never}]}),
@@ -1634,18 +1907,18 @@ mam_slave(Config, NS) ->
wait_for_master(Config),
send(Config, #presence{}),
?recv2(#presence{}, #presence{from = Peer}),
- #iq{type = result, sub_els = []} =
+ #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = always}]} =
send_recv(Config,
#iq{type = set,
sub_els = [#mam_prefs{xmlns = NS, default = always}]}),
wait_for_master(Config),
lists:foreach(
fun(N) ->
- Text = #text{data = jlib:integer_to_binary(N)},
+ Text = #text{data = integer_to_binary(N)},
?recv1(#message{from = Peer, body = [Text],
sub_els = [#mam_archived{by = ServerJID}]})
end, lists:seq(1, 5)),
- #iq{type = result, sub_els = []} =
+ #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = never}]} =
send_recv(Config, #iq{type = set,
sub_els = [#mam_prefs{xmlns = NS, default = never}]}),
disconnect(Config).
@@ -1665,7 +1938,7 @@ mam_query_all(Config, NS) ->
end,
lists:foreach(
fun(N) ->
- Text = #text{data = jlib:integer_to_binary(N)},
+ Text = #text{data = integer_to_binary(N)},
?recv1(#message{to = MyJID,
sub_els =
[#mam_result{
@@ -1692,7 +1965,7 @@ mam_query_with(Config, JID, NS) ->
{#mam_query{xmlns = NS, with = JID}, get};
true ->
Fs = [#xdata_field{var = <<"jid">>,
- values = [jlib:jid_to_string(JID)]}],
+ values = [jid:to_string(JID)]}],
{#mam_query{xmlns = NS,
xdata = #xdata{type = submit, fields = Fs}}, set}
end,
@@ -1703,7 +1976,7 @@ mam_query_with(Config, JID, NS) ->
maybe_recv_iq_result(NS, I),
lists:foreach(
fun(N) ->
- Text = #text{data = jlib:integer_to_binary(N)},
+ Text = #text{data = integer_to_binary(N)},
?recv1(#message{to = MyJID,
sub_els =
[#mam_result{
@@ -1741,7 +2014,7 @@ mam_query_rsm(Config, NS) ->
maybe_recv_iq_result(NS, I1),
lists:foreach(
fun(N) ->
- Text = #text{data = jlib:integer_to_binary(N)},
+ Text = #text{data = integer_to_binary(N)},
?recv1(#message{to = MyJID,
sub_els =
[#mam_result{
@@ -1773,7 +2046,7 @@ mam_query_rsm(Config, NS) ->
maybe_recv_iq_result(NS, I2),
lists:foreach(
fun(N) ->
- Text = #text{data = jlib:integer_to_binary(N)},
+ Text = #text{data = integer_to_binary(N)},
?recv1(#message{to = MyJID,
sub_els =
[#mam_result{
@@ -1810,7 +2083,7 @@ mam_query_rsm(Config, NS) ->
maybe_recv_iq_result(NS, I3),
lists:foreach(
fun(N) ->
- Text = #text{data = jlib:integer_to_binary(N)},
+ Text = #text{data = integer_to_binary(N)},
?recv1(#message{to = MyJID,
sub_els =
[#mam_result{
@@ -1861,7 +2134,7 @@ mam_query_rsm(Config, NS) ->
maybe_recv_iq_result(NS, I5),
lists:foreach(
fun(N) ->
- Text = #text{data = jlib:integer_to_binary(N)},
+ Text = #text{data = integer_to_binary(N)},
?recv1(#message{to = MyJID,
sub_els =
[#mam_result{
@@ -1932,14 +2205,14 @@ change_client_state(Config, NewState) ->
bookmark_conference() ->
#bookmark_conference{name = <<"Some name">>,
autojoin = true,
- jid = jlib:make_jid(
+ jid = jid:make(
<<"some">>,
<<"some.conference.org">>,
<<>>)}.
socks5_connect(#streamhost{host = Host, port = Port},
{SID, JID1, JID2}) ->
- Hash = p1_sha:sha([SID, jlib:jid_to_string(JID1), jlib:jid_to_string(JID2)]),
+ Hash = p1_sha:sha([SID, jid:to_string(JID1), jid:to_string(JID2)]),
{ok, Sock} = gen_tcp:connect(binary_to_list(Host), Port,
[binary, {active, false}]),
Init = <<?VERSION_5, 1, ?AUTH_ANONYMOUS>>,
@@ -1975,7 +2248,7 @@ create_sql_tables(Type, BaseDir) ->
SQLFile = filename:join([BaseDir, "sql", File]),
CreationQueries = read_sql_queries(SQLFile),
DropTableQueries = drop_table_queries(CreationQueries),
- case ejabberd_odbc:sql_transaction(
+ case ejabberd_sql:sql_transaction(
VHost, DropTableQueries ++ CreationQueries) of
{atomic, ok} ->
ok;
@@ -2041,7 +2314,7 @@ clear_riak_tables(Config) ->
User = ?config(user, Config),
Server = ?config(server, Config),
Room = muc_room_jid(Config),
- {URoom, SRoom, _} = jlib:jid_tolower(Room),
+ {URoom, SRoom, _} = jid:tolower(Room),
ejabberd_auth:remove_user(User, Server),
ejabberd_auth:remove_user(<<"test_slave">>, Server),
ejabberd_auth:remove_user(<<"test_master">>, Server),
diff --git a/test/ejabberd_SUITE_data/ejabberd.ldif b/test/ejabberd_SUITE_data/ejabberd.ldif
index 0d2d6063..863a07e4 100644
--- a/test/ejabberd_SUITE_data/ejabberd.ldif
+++ b/test/ejabberd_SUITE_data/ejabberd.ldif
@@ -10,6 +10,10 @@ dn: ou=users,dc=localhost
ou: users
objectClass: organizationalUnit
+dn: ou=groups,dc=localhost
+ou: groups
+objectClass: organizationalUnit
+
dn: uid=test_single,ou=users,dc=localhost
uid: test_single
mail: test_single@localhost
@@ -33,3 +37,16 @@ objectClass: person
jpegPhoto:: /9g=
cn: Test Slave
password: password
+
+dn: uid=user2,ou=users,dc=localhost
+uid: user2
+mail: user2@localhost
+objectClass: person
+cn: Test User 2
+password: password
+
+dn: cn=group1,ou=groups,dc=localhost
+objectClass: posixGroup
+memberUid: test_single
+memberUid: user2
+cn: group1
diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml
index c0d3aa8c..bd7f958d 100644
--- a/test/ejabberd_SUITE_data/ejabberd.yml
+++ b/test/ejabberd_SUITE_data/ejabberd.yml
@@ -1,32 +1,33 @@
host_config:
"pgsql.localhost":
- odbc_username: "@@pgsql_user@@"
- odbc_type: pgsql
- odbc_server: "@@pgsql_server@@"
- odbc_port: @@pgsql_port@@
- odbc_pool_size: 1
- odbc_password: "@@pgsql_pass@@"
- odbc_database: "@@pgsql_db@@"
- auth_method: odbc
+ sql_username: "@@pgsql_user@@"
+ sql_type: pgsql
+ sql_server: "@@pgsql_server@@"
+ sql_port: @@pgsql_port@@
+ sql_pool_size: 1
+ sql_password: "@@pgsql_pass@@"
+ sql_database: "@@pgsql_db@@"
+ auth_method: sql
+ sm_db_type: sql
modules:
mod_announce:
- db_type: odbc
+ db_type: sql
access: local
mod_blocking: []
mod_caps:
- db_type: odbc
+ db_type: sql
mod_last:
- db_type: odbc
+ db_type: sql
mod_muc:
- db_type: odbc
+ db_type: sql
mod_offline:
- db_type: odbc
+ db_type: sql
mod_privacy:
- db_type: odbc
+ db_type: sql
mod_private:
- db_type: odbc
+ db_type: sql
mod_pubsub:
- db_type: odbc
+ db_type: sql
access_createnode: pubsub_createnode
ignore_pep_from_offline: true
last_item_cache: false
@@ -34,16 +35,17 @@ host_config:
- "flat"
- "hometree"
- "pep"
+ mod_mix: []
mod_roster:
versioning: true
store_current_id: true
- db_type: odbc
+ db_type: sql
mod_mam:
- db_type: odbc
+ db_type: sql
mod_vcard:
- db_type: odbc
+ db_type: sql
mod_vcard_xupdate:
- db_type: odbc
+ db_type: sql
mod_adhoc: []
mod_configure: []
mod_disco: []
@@ -58,27 +60,28 @@ Welcome to this XMPP server."
mod_time: []
mod_version: []
"sqlite.localhost":
- odbc_type: sqlite
- auth_method: odbc
+ sql_type: sqlite
+ auth_method: sql
+ sm_db_type: sql
modules:
mod_announce:
- db_type: odbc
+ db_type: sql
access: local
mod_blocking: []
mod_caps:
- db_type: odbc
+ db_type: sql
mod_last:
- db_type: odbc
+ db_type: sql
mod_muc:
- db_type: odbc
+ db_type: sql
mod_offline:
- db_type: odbc
+ db_type: sql
mod_privacy:
- db_type: odbc
+ db_type: sql
mod_private:
- db_type: odbc
+ db_type: sql
mod_pubsub:
- db_type: odbc
+ db_type: sql
access_createnode: pubsub_createnode
ignore_pep_from_offline: true
last_item_cache: false
@@ -86,16 +89,17 @@ Welcome to this XMPP server."
- "flat"
- "hometree"
- "pep"
+ mod_mix: []
mod_roster:
versioning: true
store_current_id: true
- db_type: odbc
+ db_type: sql
mod_mam:
- db_type: odbc
+ db_type: sql
mod_vcard:
- db_type: odbc
+ db_type: sql
mod_vcard_xupdate:
- db_type: odbc
+ db_type: sql
mod_adhoc: []
mod_configure: []
mod_disco: []
@@ -110,33 +114,34 @@ Welcome to this XMPP server."
mod_time: []
mod_version: []
"mysql.localhost":
- odbc_username: "@@mysql_user@@"
- odbc_type: mysql
- odbc_server: "@@mysql_server@@"
- odbc_port: @@mysql_port@@
- odbc_pool_size: 1
- odbc_password: "@@mysql_pass@@"
- odbc_database: "@@mysql_db@@"
- auth_method: odbc
+ sql_username: "@@mysql_user@@"
+ sql_type: mysql
+ sql_server: "@@mysql_server@@"
+ sql_port: @@mysql_port@@
+ sql_pool_size: 1
+ sql_password: "@@mysql_pass@@"
+ sql_database: "@@mysql_db@@"
+ auth_method: sql
+ sm_db_type: sql
modules:
mod_announce:
- db_type: odbc
+ db_type: sql
access: local
mod_blocking: []
mod_caps:
- db_type: odbc
+ db_type: sql
mod_last:
- db_type: odbc
+ db_type: sql
mod_muc:
- db_type: odbc
+ db_type: sql
mod_offline:
- db_type: odbc
+ db_type: sql
mod_privacy:
- db_type: odbc
+ db_type: sql
mod_private:
- db_type: odbc
+ db_type: sql
mod_pubsub:
- db_type: odbc
+ db_type: sql
access_createnode: pubsub_createnode
ignore_pep_from_offline: true
last_item_cache: false
@@ -144,16 +149,17 @@ Welcome to this XMPP server."
- "flat"
- "hometree"
- "pep"
+ mod_mix: []
mod_roster:
versioning: true
store_current_id: true
- db_type: odbc
+ db_type: sql
mod_mam:
- db_type: odbc
+ db_type: sql
mod_vcard:
- db_type: odbc
+ db_type: sql
mod_vcard_xupdate:
- db_type: odbc
+ db_type: sql
mod_adhoc: []
mod_configure: []
mod_disco: []
@@ -194,6 +200,63 @@ Welcome to this XMPP server."
- "flat"
- "hometree"
- "pep"
+ mod_mix: []
+ mod_roster:
+ versioning: true
+ store_current_id: true
+ db_type: internal
+ mod_mam:
+ db_type: internal
+ mod_vcard:
+ db_type: internal
+ mod_vcard_xupdate:
+ db_type: internal
+ mod_carboncopy: []
+ mod_client_state:
+ drop_chat_states: true
+ queue_presence: true
+ mod_adhoc: []
+ mod_configure: []
+ mod_disco: []
+ mod_ping: []
+ mod_proxy65: []
+ mod_register:
+ welcome_message:
+ subject: "Welcome!"
+ body: "Hi.
+Welcome to this XMPP server."
+ mod_stats: []
+ mod_time: []
+ mod_version: []
+ "redis.localhost":
+ auth_method: internal
+ sm_db_type: redis
+ modules:
+ mod_announce:
+ db_type: internal
+ access: local
+ mod_blocking: []
+ mod_caps:
+ db_type: internal
+ mod_last:
+ db_type: internal
+ mod_muc:
+ db_type: internal
+ mod_offline:
+ db_type: internal
+ mod_privacy:
+ db_type: internal
+ mod_private:
+ db_type: internal
+ mod_pubsub:
+ access_createnode: pubsub_createnode
+ ignore_pep_from_offline: true
+ last_item_cache: false
+ plugins:
+ - "flat"
+ - "hometree"
+ - "pep"
+ mod_mix: []
mod_roster:
versioning: true
store_current_id: true
@@ -273,6 +336,15 @@ Welcome to this XMPP server."
auth_method: ldap
modules:
mod_vcard_ldap: []
+ mod_roster: [] # mod_roster is required by mod_shared_roster
+ mod_shared_roster_ldap:
+ ldap_auth_check: off
+ ldap_base: "dc=localhost"
+ ldap_rfilter: "(objectClass=posixGroup)"
+ ldap_gfilter: "(&(objectClass=posixGroup)(cn=%g))"
+ ldap_memberattr: "memberUid"
+ ldap_ufilter: "(uid=%u)"
+ ldap_userdesc: "cn"
mod_adhoc: []
mod_configure: []
mod_disco: []
@@ -292,6 +364,7 @@ Welcome to this XMPP server."
hosts:
- "localhost"
- "mnesia.localhost"
+ - "redis.localhost"
- "mysql.localhost"
- "pgsql.localhost"
- "extauth.localhost"
diff --git a/test/ejabberd_admin_test.exs b/test/ejabberd_admin_test.exs
new file mode 100644
index 00000000..1c999314
--- /dev/null
+++ b/test/ejabberd_admin_test.exs
@@ -0,0 +1,79 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2015 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
+ # For some myterious reason, :ejabberd_commands.init mays
+ # sometimes fails if module is not loaded before
+ {:module, :ejabberd_commands} = Code.ensure_loaded(:ejabberd_commands)
+ :ejabberd_commands.init
+ :ejabberd_admin.start
+ :ok
+ end
+
+ setup do
+ :ok
+ end
+
+ test "Logvel can be set and retrieved" do
+ :ejabberd_logger.start()
+
+ assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [1])
+ assert {1, :critical, 'Critical'} ==
+ :ejabberd_commands.execute_command(:get_loglevel, [])
+
+ assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [2])
+ assert {2, :error, 'Error'} ==
+ :ejabberd_commands.execute_command(:get_loglevel, [])
+
+ assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [3])
+ assert {3, :warning, 'Warning'} ==
+ :ejabberd_commands.execute_command(:get_loglevel, [])
+
+ assert {:wrong_loglevel, 6} ==
+ catch_throw :ejabberd_commands.execute_command(:set_loglevel, [6])
+ assert {3, :warning, 'Warning'} ==
+ :ejabberd_commands.execute_command(:get_loglevel, [])
+
+ assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [4])
+ assert {4, :info, 'Info'} ==
+ :ejabberd_commands.execute_command(:get_loglevel, [])
+
+ assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [5])
+ assert {5, :debug, 'Debug'} ==
+ :ejabberd_commands.execute_command(:get_loglevel, [])
+
+ assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [0])
+ assert {0, :no_log, 'No log'} ==
+ :ejabberd_commands.execute_command(:get_loglevel, [])
+
+ end
+
+ test "command status works with ejabberd stopped" do
+ assert :ejabberd_not_running ==
+ elem(:ejabberd_commands.execute_command(:status, []), 0)
+ end
+
+end
diff --git a/test/ejabberd_auth_mock.exs b/test/ejabberd_auth_mock.exs
new file mode 100644
index 00000000..83019c8e
--- /dev/null
+++ b/test/ejabberd_auth_mock.exs
@@ -0,0 +1,74 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2016 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, :is_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_commands_mock_test.exs b/test/ejabberd_commands_mock_test.exs
new file mode 100644
index 00000000..28558503
--- /dev/null
+++ b/test/ejabberd_commands_mock_test.exs
@@ -0,0 +1,423 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2016 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 EjabberdCommandsMockTest do
+ use ExUnit.Case, async: false
+
+ @author "jsautret@process-one.net"
+
+ # mocked callback module
+ @module :test_module
+ # Admin user
+ @admin "admin"
+ @adminpass "adminpass"
+ # Non admin user
+ @user "user"
+ @userpass "userpass"
+ # XMPP domain
+ @domain "domain"
+
+ require Record
+ Record.defrecord :ejabberd_commands, Record.extract(:ejabberd_commands, from_lib: "ejabberd/include/ejabberd_commands.hrl")
+
+ setup_all do
+ try do
+ :stringprep.start
+ rescue
+ _ -> :ok
+ end
+ :mnesia.start
+ EjabberdOauthMock.init
+ :ok
+ end
+
+ setup do
+ :meck.unload
+ :meck.new(@module, [:non_strict])
+ :ejabberd_commands.init
+ end
+
+ test "API command can be registered, listed and unregistered" do
+ command = ejabberd_commands name: :test, module: @module,
+ function: :test_command
+
+ assert :ok == :ejabberd_commands.register_commands [command]
+ commands = :ejabberd_commands.list_commands
+ assert Enum.member? commands, {:test, [], ''}
+
+ assert :ok == :ejabberd_commands.unregister_commands [command]
+ commands = :ejabberd_commands.list_commands
+ refute Enum.member? commands, {:test, [], ''}
+ end
+
+
+ test "API command with versions can be registered, listed and unregistered" do
+ command1 = ejabberd_commands name: :test, module: @module,
+ function: :test_command, version: 1, desc: 'version1'
+ command3 = ejabberd_commands name: :test, module: @module,
+ function: :test_command, version: 3, desc: 'version3'
+ assert :ejabberd_commands.register_commands [command1, command3]
+
+ version1 = {:test, [], 'version1'}
+ version3 = {:test, [], 'version3'}
+
+ # default version is latest one
+ commands = :ejabberd_commands.list_commands
+ refute Enum.member? commands, version1
+ assert Enum.member? commands, version3
+
+ # no such command in APIv0
+ commands = :ejabberd_commands.list_commands 0
+ refute Enum.member? commands, version1
+ refute Enum.member? commands, version3
+
+ commands = :ejabberd_commands.list_commands 1
+ assert Enum.member? commands, version1
+ refute Enum.member? commands, version3
+
+ commands = :ejabberd_commands.list_commands 2
+ assert Enum.member? commands, version1
+ refute Enum.member? commands, version3
+
+ commands = :ejabberd_commands.list_commands 3
+ refute Enum.member? commands, version1
+ assert Enum.member? commands, version3
+
+ commands = :ejabberd_commands.list_commands 4
+ refute Enum.member? commands, version1
+ assert Enum.member? commands, version3
+
+ assert :ok == :ejabberd_commands.unregister_commands [command1]
+
+ commands = :ejabberd_commands.list_commands 1
+ refute Enum.member? commands, version1
+ refute Enum.member? commands, version3
+
+ commands = :ejabberd_commands.list_commands 3
+ refute Enum.member? commands, version1
+ assert Enum.member? commands, version3
+
+ assert :ok == :ejabberd_commands.unregister_commands [command3]
+
+ commands = :ejabberd_commands.list_commands 1
+ refute Enum.member? commands, version1
+ refute Enum.member? commands, version3
+
+ commands = :ejabberd_commands.list_commands 3
+ refute Enum.member? commands, version1
+ refute Enum.member? commands, version3
+ end
+
+
+ test "API command can be registered and executed" do
+ # Create & register a mocked command test() -> :result
+ command_name = :test
+ function = :test_command
+ command = ejabberd_commands(name: command_name,
+ module: @module,
+ function: function)
+ :meck.expect @module, function, fn -> :result end
+ assert :ok == :ejabberd_commands.register_commands [command]
+
+ assert :result == :ejabberd_commands.execute_command(command_name, [])
+
+ assert :meck.validate @module
+ end
+
+ test "API command with versions can be registered and executed" do
+ command_name = :test
+
+ function1 = :test_command1
+ command1 = ejabberd_commands(name: command_name,
+ version: 1,
+ module: @module,
+ function: function1)
+ :meck.expect(@module, function1, fn -> :result1 end)
+
+ function3 = :test_command3
+ command3 = ejabberd_commands(name: command_name,
+ version: 3,
+ module: @module,
+ function: function3)
+ :meck.expect(@module, function3, fn -> :result3 end)
+
+ assert :ok == :ejabberd_commands.register_commands [command1, command3]
+
+ # default version is latest one
+ assert :result3 == :ejabberd_commands.execute_command(command_name, [])
+ # no such command in APIv0
+ assert :unknown_command ==
+ catch_throw :ejabberd_commands.execute_command(command_name, [], 0)
+ assert :result1 == :ejabberd_commands.execute_command(command_name, [], 1)
+ assert :result1 == :ejabberd_commands.execute_command(command_name, [], 2)
+ assert :result3 == :ejabberd_commands.execute_command(command_name, [], 3)
+ assert :result3 == :ejabberd_commands.execute_command(command_name, [], 4)
+
+ assert :meck.validate @module
+ end
+
+
+
+ test "API command with user policy" do
+ mock_commands_config
+
+ # Register a command test(user, domain) -> {:versionN, user, domain}
+ # with policy=user and versions 1 & 3
+ command_name = :test
+ command1 = ejabberd_commands(name: command_name,
+ module: @module,
+ function: :test_command1,
+ policy: :user, version: 1)
+ command3 = ejabberd_commands(name: command_name,
+ module: @module,
+ function: :test_command3,
+ policy: :user, version: 3)
+ :meck.expect(@module, :test_command1,
+ fn(user, domain) when is_binary(user) and is_binary(domain) ->
+ {:version1, user, domain}
+ end)
+ :meck.expect(@module, :test_command3,
+ fn(user, domain) when is_binary(user) and is_binary(domain) ->
+ {:version3, user, domain}
+ end)
+ assert :ok == :ejabberd_commands.register_commands [command1, command3]
+
+ # A normal user must not pass user info as parameter
+ assert {:version1, @user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ @userpass, false},
+ command_name,
+ [], 2)
+ assert {:version3, @user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ @userpass, false},
+ command_name,
+ [], 3)
+ token = EjabberdOauthMock.get_token @user, @domain, command_name
+ assert {:version3, @user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ {:oauth, token}, false},
+ command_name,
+ [], 4)
+ # Expired oauth token
+ token = EjabberdOauthMock.get_token @user, @domain, command_name, 1
+ :timer.sleep 1500
+ assert {:error, :invalid_account_data} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ {:oauth, token}, false},
+ command_name,
+ [], 4)
+ # Wrong oauth scope
+ token = EjabberdOauthMock.get_token @user, @domain, :bad_command
+ assert {:error, :invalid_account_data} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ {:oauth, token}, false},
+ command_name,
+ [], 4)
+
+
+ assert :function_clause ==
+ catch_error :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ @userpass, false},
+ command_name,
+ [@user, @domain], 2)
+ # @user is not admin
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ @userpass, true},
+ command_name,
+ [], 2)
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ @userpass, true},
+ command_name,
+ [@user, @domain], 2)
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ {:oauth, token}, true},
+ command_name,
+ [@user, @domain], 2)
+
+
+ # An admin must explicitely pass user info
+ assert {:version1, @user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined, :admin,
+ command_name, [@user, @domain], 2)
+ assert {:version3, @user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined, :admin,
+ command_name, [@user, @domain], 4)
+ assert {:version1, @user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain, @adminpass, true},
+ command_name, [@user, @domain], 1)
+ token = EjabberdOauthMock.get_token @admin, @domain, command_name
+ assert {:version3, @user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain, {:oauth, token}, true},
+ command_name, [@user, @domain], 3)
+ # Wrong @admin password
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ @adminpass<>"bad", true},
+ command_name,
+ [@user, @domain], 3)
+ # @admin calling as a normal user
+ assert {:version3, @admin, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ @adminpass, false},
+ command_name, [], 5)
+ assert {:version3, @admin, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ {:oauth, token}, false},
+ command_name, [], 6)
+ assert :function_clause ==
+ catch_error :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ @adminpass, false},
+ command_name,
+ [@user, @domain], 5)
+ assert :meck.validate @module
+ end
+
+
+
+ test "API command with admin policy" do
+ mock_commands_config
+
+ # Register a command test(user, domain) -> {user, domain}
+ # with policy=admin
+ command_name = :test
+ function = :test_command
+ command = ejabberd_commands(name: command_name,
+ args: [{:user, :binary}, {:host, :binary}],
+ module: @module,
+ function: function,
+ policy: :admin)
+ :meck.expect(@module, function,
+ fn(user, domain) when is_binary(user) and is_binary(domain) ->
+ {user, domain}
+ end)
+ assert :ok == :ejabberd_commands.register_commands [command]
+
+ # A normal user cannot call the command
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ @userpass, false},
+ command_name,
+ [@user, @domain])
+
+ # An admin can call the command
+ assert {@user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ @adminpass, true},
+ command_name,
+ [@user, @domain])
+
+ # An admin can call the command with oauth token
+ token = EjabberdOauthMock.get_token @admin, @domain, command_name
+ assert {@user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ {:oauth, token}, true},
+ command_name,
+ [@user, @domain])
+
+
+ # An admin with bad password cannot call the command
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ "bad"<>@adminpass, false},
+ command_name,
+ [@user, @domain])
+
+ # An admin cannot call the command with bad oauth token
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ {:oauth, "bad"<>token}, true},
+ command_name,
+ [@user, @domain])
+
+ # An admin as a normal user cannot call the command
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ @adminpass, false},
+ command_name,
+ [@user, @domain])
+
+ # An admin as a normal user cannot call the command with oauth token
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ {:oauth, token}, false},
+ command_name,
+ [@user, @domain])
+
+ assert :meck.validate @module
+ end
+
+
+ ##########################################################
+ # Utils
+
+ # Mock a config where only @admin user is allowed to call commands
+ # as admin
+ def mock_commands_config do
+ EjabberdAuthMock.init
+ EjabberdAuthMock.create_user @user, @domain, @userpass
+ EjabberdAuthMock.create_user @admin, @domain, @adminpass
+
+ :meck.new :ejabberd_config
+ :meck.expect(:ejabberd_config, :get_option,
+ fn(:commands_admin_access, _, _) -> :commands_admin_access
+ (:oauth_access, _, _) -> :all
+ (_, _, default) -> default
+ end)
+ :meck.expect(:ejabberd_config, :get_myhosts,
+ fn() -> [@domain] end)
+ :meck.new :acl
+ :meck.expect(:acl, :match_rule,
+ fn(@domain, :commands_admin_access, user) ->
+ case :jlib.make_jid(@admin, @domain, "") do
+ ^user -> :allow
+ _ -> :deny
+ end
+ (@domain, :all, _user) ->
+ :allow
+ end)
+ end
+
+end
diff --git a/test/ejabberd_commands_test.exs b/test/ejabberd_commands_test.exs
new file mode 100644
index 00000000..31d10821
--- /dev/null
+++ b/test/ejabberd_commands_test.exs
@@ -0,0 +1,84 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2016 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 EjabberdCommandsTest do
+ @author "mremond@process-one.net"
+
+ use ExUnit.Case, async: true
+
+ require Record
+ Record.defrecord :ejabberd_commands, Record.extract(:ejabberd_commands, from_lib: "ejabberd/include/ejabberd_commands.hrl")
+
+ setup_all do
+ :mnesia.start
+ :ejabberd_commands.init
+ end
+
+ test "Check that we can register a command" do
+ :ok = :ejabberd_commands.register_commands([user_test_command])
+ commands = :ejabberd_commands.list_commands
+ assert Enum.member?(commands, {:test_user, [], "Test user"})
+ end
+
+ test "Check that admin commands are rejected with noauth credentials" do
+ :ok = :ejabberd_commands.register_commands([admin_test_command])
+
+ assert catch_throw(:ejabberd_commands.execute_command(:undefined, :noauth, :test_admin, [])) == {:error, :account_unprivileged}
+
+ # Command executed from ejabberdctl passes anyway with access commands trick
+ # TODO: We should refactor to have explicit call when bypassing auth check for command-line
+ :ok = :ejabberd_commands.execute_command([], :noauth, :test_admin, [])
+ end
+
+ # TODO Test that we can add command to list of expose commands
+ # This can be done with:
+ # ejabberd_config:add_local_option(commands, [[{add_commands, [open_cmd]}]]).
+
+# test "Check that a user can use a user command" do
+# [Command] = ets:lookup(ejabberd_commands, test_user),
+# AccessCommands = ejabberd_commands:get_access_commands(undefined),
+# ejabberd_commands:check_access_commands(AccessCommands, {<<"test">>,<<"localhost">>, {oauth,<<"MyToken">>}, false}, test_user, Command, []).
+# end
+
+ defp user_test_command do
+ ejabberd_commands(name: :test_user, tags: [:roster],
+ desc: "Test user",
+ policy: :user,
+ module: __MODULE__,
+ function: :test_user,
+ args: [],
+ result: {:contacts, {:list, {:contact, {:tuple, [
+ {:jid, :string},
+ {:nick, :string}
+ ]}}}})
+ end
+
+ defp admin_test_command do
+ ejabberd_commands(name: :test_admin, tags: [:roster],
+ desc: "Test admin",
+ policy: :restricted,
+ module: __MODULE__,
+ function: :test_admin,
+ args: [],
+ result: {:res, :rescode})
+ end
+
+ def test_admin, do: :ok
+end
diff --git a/test/ejabberd_cyrsasl_test.exs b/test/ejabberd_cyrsasl_test.exs
new file mode 100644
index 00000000..0dc64ee4
--- /dev/null
+++ b/test/ejabberd_cyrsasl_test.exs
@@ -0,0 +1,153 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2016 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 EjabberdCyrsaslTest do
+ @author "pawel@process-one.net"
+
+ use ExUnit.Case, async: true
+
+ setup_all do
+ :p1_sha.load_nif()
+ :mnesia.start
+ :ok = start_module(:stringprep)
+ :ok = start_module(:jid)
+ :ok = :ejabberd_config.start(["domain1"], [])
+ :ok = :cyrsasl.start
+ cyrstate = :cyrsasl.server_new("domain1", "domain1", "domain1", :ok, &get_password/1,
+ &check_password/3, &check_password_digest/5)
+ {:ok, cyrstate: cyrstate}
+ end
+
+ test "Plain text (correct user and pass)", context do
+ step1 = :cyrsasl.server_start(context[:cyrstate], "PLAIN", <<0,"user1",0,"pass">>)
+ assert {:ok, _} = step1
+ {:ok, kv} = step1
+ assert kv[:authzid] == "user1", "got correct user"
+ end
+
+ test "Plain text (correct user wrong pass)", context do
+ step1 = :cyrsasl.server_start(context[:cyrstate], "PLAIN", <<0,"user1",0,"badpass">>)
+ assert step1 == {:error, "not-authorized", "user1"}, "got error response"
+ end
+
+ test "Plain text (wrong user wrong pass)", context do
+ step1 = :cyrsasl.server_start(context[:cyrstate], "PLAIN", <<0,"nouser1",0,"badpass">>)
+ assert step1 == {:error, "not-authorized", "nouser1"}, "got error response"
+ end
+
+ test "Anonymous", context do
+ setup_anonymous_mocks()
+ step1 = :cyrsasl.server_start(context[:cyrstate], "ANONYMOUS", "domain1")
+ assert {:ok, _} = step1
+ end
+
+ test "Digest-MD5 (correct user and pass)", context do
+ assert {:continue, init_str, state1} = :cyrsasl.server_start(context[:cyrstate], "DIGEST-MD5", "")
+ assert [_, nonce] = Regex.run(~r/nonce="(.*?)"/, init_str)
+ user = "user1"
+ domain = "domain1"
+ digest_uri = "xmpp/#{domain}"
+ pass = "pass"
+ cnonce = "abcd"
+ nc = "00000001"
+ response_hash = calc_digest_sha(user, domain, pass, nc, nonce, cnonce)
+ response = "username=\"#{user}\",realm=\"#{domain}\",nonce=\"#{nonce}\",cnonce=\"#{cnonce}\"," <>
+ "nc=\"#{nc}\",qop=auth,digest-uri=\"#{digest_uri}\",response=\"#{response_hash}\"," <>
+ "charset=utf-8,algorithm=md5-sess"
+ assert {:continue, calc_str, state3} = :cyrsasl.server_step(state1, response)
+ assert {:ok, list} = :cyrsasl.server_step(state3, "")
+ end
+
+ defp calc_digest_sha(user, domain, pass, nc, nonce, cnonce) do
+ digest_uri = "xmpp/#{domain}"
+ a0 = "#{user}:#{domain}:#{pass}"
+ a1 = "#{str_md5(a0)}:#{nonce}:#{cnonce}"
+ a2 = "AUTHENTICATE:#{digest_uri}"
+ hex_md5("#{hex_md5(a1)}:#{nonce}:#{nc}:#{cnonce}:auth:#{hex_md5(a2)}")
+ end
+
+ defp str_md5(str) do
+ :erlang.md5(str)
+ end
+
+ defp hex_md5(str) do
+ :p1_sha.to_hexlist(:erlang.md5(str))
+ end
+
+ defp setup_anonymous_mocks() do
+ :meck.unload
+ mock(:ejabberd_auth_anonymous, :is_sasl_anonymous_enabled,
+ fn (host) ->
+ true
+ end)
+ mock(:ejabberd_auth, :is_user_exists,
+ fn (user, domain) ->
+ domain == "domain1" and get_password(user) != false
+ end)
+ end
+
+ defp start_module(module) do
+ case apply(module, :start, []) do
+ :ok -> :ok
+ {:error, {:already_started, _}} -> :ok
+ other -> other
+ end
+ end
+
+ defp get_password(user) do
+ if user == "user1" or user == "user2" do
+ {"pass", :internal}
+ else
+ :false
+ end
+ end
+
+ defp check_password(user, authzid, pass) do
+ case get_password(authzid) do
+ {^pass, mod} ->
+ {true, mod}
+ _ ->
+ false
+ end
+ end
+
+ defp check_password_digest(user, authzid, pass, digest, digest_gen) do
+ case get_password(authzid) do
+ {spass, mod} ->
+ v = digest_gen.(spass)
+ if v == digest do
+ {true, mod}
+ else
+ false
+ end
+ _ ->
+ false
+ end
+ end
+
+ defp 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_hooks_test.exs b/test/ejabberd_hooks_test.exs
index ff753a7b..a69fbbd6 100644
--- a/test/ejabberd_hooks_test.exs
+++ b/test/ejabberd_hooks_test.exs
@@ -18,30 +18,41 @@
#
# ----------------------------------------------------------------------
+# 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: true
+ 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, _pid} = :ejabberd_hooks.start_link
:ok
end
-
+
setup do
:meck.unload
:true = :ejabberd_hooks.delete_all_hooks
:ok
- end
-
+ 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
@@ -51,7 +62,7 @@ defmodule EjabberdHooksTest do
test "An anonymous function can be removed from hook handlers" do
hookname = :test_fun_hook
- anon_fun = fn _ -> :ok end
+ 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)
@@ -77,20 +88,20 @@ defmodule EjabberdHooksTest do
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)
+ [{_pid, {^modulename, _callback, [:hook_params]}, :stop}] = :meck.history(modulename)
end
test "Run fold hooks accumulate state in correct order through handlers" do
@@ -99,10 +110,10 @@ defmodule EjabberdHooksTest do
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
@@ -115,12 +126,12 @@ defmodule EjabberdHooksTest do
: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
@@ -148,19 +159,19 @@ defmodule EjabberdHooksTest do
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
+ 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)
+ [{_pid, {^modulename, ^callback, ^params}, ^result}] = :meck.history(modulename)
end
def run_fold_crash(crash_fun) do
@@ -175,7 +186,7 @@ defmodule EjabberdHooksTest do
:final = :ejabberd_hooks.run_fold(hookname, @host, :started, [])
# Both handlers were called
- 2 = length(:meck.history(modulename))
+ 2 = length(:meck.history(modulename))
end
# TODO refactor: Move to ejabberd_test_mock
@@ -188,5 +199,5 @@ defmodule EjabberdHooksTest do
:meck.expect(module, function, fun)
end
-
+
end
diff --git a/test/ejabberd_oauth_mock.exs b/test/ejabberd_oauth_mock.exs
new file mode 100644
index 00000000..81cfdc03
--- /dev/null
+++ b/test/ejabberd_oauth_mock.exs
@@ -0,0 +1,47 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2016 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]])
+ 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}],
+ :undefined)
+ token
+ end
+
+end
diff --git a/test/ejabberd_sm_mock.exs b/test/ejabberd_sm_mock.exs
new file mode 100644
index 00000000..53c2c750
--- /dev/null
+++ b/test/ejabberd_sm_mock.exs
@@ -0,0 +1,121 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2016 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: "ejabberd/include/jlib.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 (_from, to, {:broadcast, {: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
index 6243456b..48cc94f8 100644
--- a/test/elixir_SUITE.erl
+++ b/test/elixir_SUITE.erl
@@ -8,7 +8,7 @@
%%% Example: Is run with:
%%% ./rebar skip_deps=true ct suites=elixir
%%% or from ejabber overall test suite:
-%%% make test
+%%% make quicktest
%%% @end
%%% Created : 19 Feb 2015 by Mickael Remond <mremond@process-one.net>
%%%-------------------------------------------------------------------
@@ -17,24 +17,39 @@
-compile(export_all).
+init_per_suite(Config) ->
+ check_meck(),
+ code:add_pathz(filename:join(test_dir(), "../include")),
+ Config.
+
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, ".*\.exs", false,
- fun(Filename, Acc) -> [list_to_atom(filename:basename(Filename)) | Acc] end,
- []);
- false ->
- []
+ 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
@@ -42,20 +57,37 @@ is_elixir_available() ->
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)
+ 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.ExUnit':start([]),
+ %% 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']}]),
+
+ 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
ResultMap = 'Elixir.ExUnit':run(),
- {ok, 0} = maps:find(failures, ResultMap).
+ 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(),
diff --git a/test/jid_test.exs b/test/jid_test.exs
new file mode 100644
index 00000000..b75a3603
--- /dev/null
+++ b/test/jid_test.exs
@@ -0,0 +1,44 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2016 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: "ejabberd/include/jlib.hrl")
+
+ setup_all do
+ :stringprep.start
+ :jid.start
+ 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/mod_admin_extra_test.exs b/test/mod_admin_extra_test.exs
new file mode 100644
index 00000000..2029cf68
--- /dev/null
+++ b/test/mod_admin_extra_test.exs
@@ -0,0 +1,356 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2015 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
+
+ @author "jsautret@process-one.net"
+
+ @user "user"
+ @domain "domain"
+ @password "password"
+ @resource "resource"
+
+ require Record
+ Record.defrecord :jid, Record.extract(:jid, from_lib: "ejabberd/include/jlib.hrl")
+
+ setup_all do
+ try do
+ :jid.start
+ :stringprep.start
+ :mnesia.start
+ :p1_sha.load_nif
+ rescue
+ _ -> :ok
+ end
+ :ejabberd_commands.init
+ :mod_admin_extra.start(@domain, [])
+ :sel_application.start_app(:moka)
+ {:ok, _pid} = :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 :ejabberd_commands.execute_command(:check_account, [@user, @domain])
+ refute :ejabberd_commands.execute_command(:check_account, [@user, "bad_domain"])
+ refute :ejabberd_commands.execute_command(:check_account, ["bad_user", @domain])
+
+ assert :meck.validate :ejabberd_auth
+ end
+
+ test "check_password works" do
+
+ EjabberdAuthMock.create_user @user, @domain, @password
+
+ assert :ejabberd_commands.execute_command(:check_password,
+ [@user, @domain, @password])
+ refute :ejabberd_commands.execute_command(:check_password,
+ [@user, @domain, "bad_password"])
+ refute :ejabberd_commands.execute_command(:check_password,
+ [@user, "bad_domain", @password])
+ refute :ejabberd_commands.execute_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 :ejabberd_commands.execute_command(:check_password_hash,
+ [@user, @domain, hash, "md5"])
+ refute :ejabberd_commands.execute_command(:check_password_hash,
+ [@user, @domain, "bad_hash", "md5"])
+ refute :ejabberd_commands.execute_command(:check_password_hash,
+ [@user, "bad_domain", hash, "md5"])
+ refute :ejabberd_commands.execute_command(:check_password_hash,
+ ["bad_user", @domain, hash, "md5"])
+
+ hash = "5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8" # echo -n password|shasum
+ assert :ejabberd_commands.execute_command(:check_password_hash,
+ [@user, @domain, hash, "sha"])
+
+ assert :unkown_hash_method ==
+ catch_throw :ejabberd_commands.execute_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 :ejabberd_commands.execute_command(:change_password,
+ [@user, @domain, "new_password"])
+ refute :ejabberd_commands.execute_command(:check_password,
+ [@user, @domain, @password])
+ assert :ejabberd_commands.execute_command(:check_password,
+ [@user, @domain, "new_password"])
+ assert {:not_found, 'unknown_user'} ==
+ catch_throw :ejabberd_commands.execute_command(:change_password,
+ ["bad_user", @domain,
+ @password])
+ assert :meck.validate :ejabberd_auth
+ end
+
+ ###################### Sessions
+
+ test "num_resources works" do
+ assert 0 == :ejabberd_commands.execute_command(:num_resources,
+ [@user, @domain])
+
+ EjabberdSmMock.connect_resource @user, @domain, @resource
+ assert 1 == :ejabberd_commands.execute_command(:num_resources,
+ [@user, @domain])
+
+ EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
+ assert 2 == :ejabberd_commands.execute_command(:num_resources,
+ [@user, @domain])
+
+ EjabberdSmMock.connect_resource @user<>"1", @domain, @resource
+ assert 2 == :ejabberd_commands.execute_command(:num_resources,
+ [@user, @domain])
+
+ EjabberdSmMock.disconnect_resource @user, @domain, @resource
+ assert 1 == :ejabberd_commands.execute_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(:ejabberd_commands.execute_command(:resource_num,
+ [@user, @domain, 0])), 0)
+ assert @resource<>"1" ==
+ :ejabberd_commands.execute_command(:resource_num, [@user, @domain, 1])
+ assert @resource<>"3" ==
+ :ejabberd_commands.execute_command(:resource_num, [@user, @domain, 3])
+ assert :bad_argument ==
+ elem(catch_throw(:ejabberd_commands.execute_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 ==
+ :ejabberd_commands.execute_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 'Never' ==
+ :ejabberd_commands.execute_command(:get_last, [@user, @domain])
+
+ EjabberdSmMock.connect_resource @user, @domain, @resource<>"1"
+ EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
+
+ assert 'Online' ==
+ :ejabberd_commands.execute_command(:get_last, [@user, @domain])
+
+ EjabberdSmMock.disconnect_resource @user, @domain, @resource<>"1"
+
+ assert 'Online' ==
+ :ejabberd_commands.execute_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_local_time now
+ result = List.flatten(:io_lib.format(
+ "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w ",
+ [year, month, day, hour, minute, second]))
+ assert result ==
+ :ejabberd_commands.execute_command(:get_last, [@user, @domain])
+
+ assert :meck.validate :mod_last
+ end
+
+ ###################### Roster
+
+ 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 ==
+ :ejabberd_commands.execute_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, jid,
+ {:broadcast, {:item, {@user<>"1", @domain, ""}, :both}}])
+
+ assert :ok ==
+ :ejabberd_commands.execute_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, jid,
+ {:broadcast, {:item, {@user<>"2", @domain, ""}, :both}}])
+
+
+ :ejabberd_commands.execute_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, jid,
+ {:broadcast, {:item, {@user<>"1", @domain, ""}, :none}}])
+
+ :ejabberd_commands.execute_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, jid,
+ {:broadcast, {: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, jid,
+ {:broadcast, {:item, :_, :_}}])
+
+ assert [] == ModRosterMock.get_roster(@user, @domain)
+ assert :meck.validate :ejabberd_sm
+
+ end
+
+ test "get_roster works" do
+ assert [] == ModRosterMock.get_roster(@user, @domain)
+ assert [] == :ejabberd_commands.execute_command(:get_roster, [@user, @domain],
+ :admin)
+
+ assert :ok ==
+ :ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
+ @user<>"1", @domain,
+ "nick1",
+ "group1",
+ "both"])
+ assert [{@user<>"1@"<>@domain, "", 'both', 'none', "group1"}] ==
+ :ejabberd_commands.execute_command(:get_roster, [@user, @domain], :admin)
+ assert :ok ==
+ :ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
+ @user<>"2", @domain,
+ "nick2",
+ "group2",
+ "none"])
+ result = :ejabberd_commands.execute_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
+
+# kick_user command is defined in ejabberd_sm, move to extra?
+# test "kick_user works" do
+# assert 0 == :ejabberd_commands.execute_command(:num_resources,
+# [@user, @domain])
+# EjabberdSmMock.connect_resource(@user, @domain, @resource<>"1")
+# EjabberdSmMock.connect_resource(@user, @domain, @resource<>"2")
+# assert 2 ==
+# :ejabberd_commands.execute_command(:kick_user, [@user, @domain])
+# assert 0 == :ejabberd_commands.execute_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
new file mode 100644
index 00000000..78832d7a
--- /dev/null
+++ b/test/mod_http_api_mock_test.exs
@@ -0,0 +1,197 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2015 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
+ :stringprep.start
+ :ejabberd_config.start([@domain], [])
+ :ejabberd_commands.init
+ rescue
+ _ -> :ok
+ end
+ :mod_http_api.start(@domain, [])
+ EjabberdOauthMock.init
+ :ok
+ end
+
+ setup do
+ :meck.unload
+ :meck.new :ejabberd_commands
+ 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, {@user, @domain, @userpass, false}, @version) ->
+ {[], {:res, :rescode}}
+ end)
+ :meck.expect(:ejabberd_commands, :get_command_policy,
+ fn (@acommand) -> {:ok, :user} end)
+ :meck.expect(:ejabberd_commands, :get_commands,
+ fn () -> [@acommand] end)
+ :meck.expect(:ejabberd_commands, :execute_command,
+ fn (:undefined, {@user, @domain, @userpass, false}, @acommand, [], @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_command, :_)
+
+ 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, {@user, @domain, {:oauth, _token}, false}, @version) ->
+ {[], {:res, :rescode}}
+ end)
+ :meck.expect(:ejabberd_commands, :get_command_policy,
+ fn (@acommand) -> {:ok, :user} end)
+ :meck.expect(:ejabberd_commands, :get_commands,
+ fn () -> [@acommand] end)
+ :meck.expect(:ejabberd_commands, :execute_command,
+ fn (:undefined, {@user, @domain, {:oauth, _token}, false},
+ @acommand, [], @version) ->
+ :ok
+ end)
+
+
+ # Correct OAuth call
+ 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
+
+ # 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 401 == elem(result, 0) # HTTP code
+
+ # Check that the command was executed only once
+ assert 1 ==
+ :meck.num_calls(:ejabberd_commands, :execute_command, :_)
+
+ assert :meck.validate :ejabberd_auth
+ assert :meck.validate :ejabberd_commands
+ #assert :ok = :meck.history(:ejabberd_commands)
+ end
+
+
+end
diff --git a/test/mod_http_api_test.exs b/test/mod_http_api_test.exs
new file mode 100644
index 00000000..9432ae47
--- /dev/null
+++ b/test/mod_http_api_test.exs
@@ -0,0 +1,101 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2016 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
+ :stringprep.start
+ :ok = :ejabberd_config.start(["localhost"], [])
+
+ :ok = :ejabberd_commands.init
+
+ :ok = :ejabberd_commands.register_commands(cmds)
+ on_exit fn -> unregister_commands(cmds) end
+ end
+
+ test "We can expose several commands to API at a time" do
+ :ejabberd_config.add_local_option(:commands, [[{:add_commands, [:open_cmd, :user_cmd]}]])
+ commands = :ejabberd_commands.get_commands()
+ assert Enum.member?(commands, :open_cmd)
+ assert Enum.member?(commands, :user_cmd)
+ end
+
+ test "We can call open commands without authentication" do
+ :ejabberd_config.add_local_option(:commands, [[{:add_commands, [:open_cmd]}]])
+ request = request(method: :POST, 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 401" do
+ :ejabberd_config.add_local_option(:commands, [])
+ request = request(method: :POST, data: "[]")
+ {401, _, _} = :mod_http_api.process(["open_cmd"], request)
+ end
+
+ test "Call to user, admin or restricted commands without authentication are rejected" do
+ :ejabberd_config.add_local_option(:commands, [[{:add_commands, [:user_cmd, :admin_cmd, :restricted]}]])
+ request = request(method: :POST, data: "[]")
+ {401, _, _} = :mod_http_api.process(["user_cmd"], request)
+ {401, _, _} = :mod_http_api.process(["admin_cmd"], request)
+ {401, _, _} = :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
+ 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 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
new file mode 100644
index 00000000..4f8da366
--- /dev/null
+++ b/test/mod_last_mock.exs
@@ -0,0 +1,79 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2016 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: "ejabberd/include/jlib.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
new file mode 100644
index 00000000..b1251ba8
--- /dev/null
+++ b/test/mod_roster_mock.exs
@@ -0,0 +1,208 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2016 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")
+
+ @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_moka module
+
+ #:mod_roster.stop(domain)
+ :mod_roster.start(domain, [])
+ end
+
+ def mock_with_moka(module) do
+ try do
+
+ module_mock = :moka.start(module)
+ :moka.replace(module_mock, :mod_roster, :invalidate_roster_cache,
+ fn (_user, _server) ->
+ :ok
+ end)
+
+ :moka.load(module_mock)
+
+ roster_mock = :moka.start(:mod_roster)
+
+ :moka.replace(roster_mock, :gen_mod, :db_type,
+ fn (_host, _opts) ->
+ {:none}
+ end)
+
+ :moka.replace(roster_mock, :gen_iq_handler, :add_iq_handler,
+ fn (_module, _host, _ns, _m, _f, _iqdisc) ->
+ :ok
+ end)
+
+ :moka.replace(roster_mock, :gen_iq_handler, :remove_iq_handler,
+ fn (_module, _host, _ns) ->
+ :ok
+ end)
+
+ :moka.replace(roster_mock, :transaction,
+ fn (_server, function) ->
+ {:atomic, function.()}
+ end)
+
+ :moka.replace(roster_mock, :get_roster,
+ fn (user, domain) ->
+ to_records(get_roster(user, domain))
+ end)
+
+ :moka.replace(roster_mock, :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)
+
+ :moka.replace(roster_mock, :del_roster_t,
+ fn (user, domain, jid) ->
+ remove_roster_item(user, domain, :jid.to_string(jid))
+ end)
+
+ :moka.load(roster_mock)
+
+ catch
+ {:already_started, _pid} -> :ok
+ end
+
+ end
+
+ def mock_with_meck do
+# mock(:gen_mod, :db_type,
+# fn (_server, :mod_roster) ->
+# :mnesia
+# end)
+#
+# mock(:mnesia, :transaction,
+# fn (_server, function) ->
+# {:atomic, function.()}
+# end)
+#
+# mock(:mnesia, :write,
+# fn (Item) ->
+# throw Item
+# {:atomic, :ok}
+# end)
+
+ mock(:mod_roster, :transaction,
+ fn (_server, function) ->
+ {:atomic, function.()}
+ end)
+
+ mock(:mod_roster, :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, :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/suite.erl b/test/suite.erl
index 74d9b062..8000e1d2 100644
--- a/test/suite.erl
+++ b/test/suite.erl
@@ -65,9 +65,21 @@ init_config(Config) ->
{resource, <<"resource">>},
{master_resource, <<"master_resource">>},
{slave_resource, <<"slave_resource">>},
- {password, <<"password">>}
+ {password, <<"password">>},
+ {backends, get_config_backends()}
|Config].
+%% Read environment variable CT_DB=riak,mysql to limit the backends to test.
+%% You can thus limit the backend you want to test with:
+%% CT_BACKENDS=riak,mysql rebar ct suites=ejabberd
+get_config_backends() ->
+ case os:getenv("CT_BACKENDS") of
+ false -> all;
+ String ->
+ Backends0 = string:tokens(String, ","),
+ lists:map(fun(Backend) -> string:strip(Backend, both, $ ) end, Backends0)
+ end.
+
process_config_tpl(Content, []) ->
Content;
process_config_tpl(Content, [{Name, DefaultValue} | Rest]) ->
@@ -94,8 +106,8 @@ init_stream(Config) ->
ok = send_text(Config, io_lib:format(?STREAM_HEADER,
[?config(server, Config)])),
{xmlstreamstart, <<"stream:stream">>, Attrs} = recv(),
- <<"jabber:client">> = xml:get_attr_s(<<"xmlns">>, Attrs),
- <<"1.0">> = xml:get_attr_s(<<"version">>, Attrs),
+ <<"jabber:client">> = fxml:get_attr_s(<<"xmlns">>, Attrs),
+ <<"1.0">> = fxml:get_attr_s(<<"version">>, Attrs),
#stream_features{sub_els = Fs} = recv(),
Mechs = lists:flatmap(
fun(#sasl_mechanisms{list = Ms}) ->
@@ -182,8 +194,8 @@ wait_auth_SASL_result(Config) ->
io_lib:format(?STREAM_HEADER,
[?config(server, Config)])),
{xmlstreamstart, <<"stream:stream">>, Attrs} = recv(),
- <<"jabber:client">> = xml:get_attr_s(<<"xmlns">>, Attrs),
- <<"1.0">> = xml:get_attr_s(<<"version">>, Attrs),
+ <<"jabber:client">> = fxml:get_attr_s(<<"xmlns">>, Attrs),
+ <<"1.0">> = fxml:get_attr_s(<<"version">>, Attrs),
#stream_features{sub_els = Fs} = recv(),
lists:foldl(
fun(#feature_sm{}, ConfigAcc) ->
@@ -255,7 +267,7 @@ send(State, Pkt) ->
end,
El = xmpp_codec:encode(NewPkt),
ct:pal("sent: ~p <-~n~s", [El, xmpp_codec:pp(NewPkt)]),
- ok = send_text(State, xml:element_to_binary(El)),
+ ok = send_text(State, fxml:element_to_binary(El)),
NewID.
send_recv(State, IQ) ->
@@ -271,7 +283,7 @@ sasl_new(<<"DIGEST-MD5">>, User, Server, Password) ->
case cyrsasl_digest:parse(ServerIn) of
bad -> {error, <<"Invalid SASL challenge">>};
KeyVals ->
- Nonce = xml:get_attr_s(<<"nonce">>, KeyVals),
+ Nonce = fxml:get_attr_s(<<"nonce">>, KeyVals),
CNonce = id(),
Realm = proplists:get_value(<<"realm">>, KeyVals, Server),
DigestURI = <<"xmpp/", Realm/binary>>,
@@ -331,28 +343,36 @@ response(User, Passwd, Nonce, AuthzId, Realm, CNonce,
hex((erlang:md5(T))).
my_jid(Config) ->
- jlib:make_jid(?config(user, Config),
- ?config(server, Config),
- ?config(resource, Config)).
+ jid:make(?config(user, Config),
+ ?config(server, Config),
+ ?config(resource, Config)).
server_jid(Config) ->
- jlib:make_jid(<<>>, ?config(server, Config), <<>>).
+ jid:make(<<>>, ?config(server, Config), <<>>).
pubsub_jid(Config) ->
Server = ?config(server, Config),
- jlib:make_jid(<<>>, <<"pubsub.", Server/binary>>, <<>>).
+ jid:make(<<>>, <<"pubsub.", Server/binary>>, <<>>).
proxy_jid(Config) ->
Server = ?config(server, Config),
- jlib:make_jid(<<>>, <<"proxy.", Server/binary>>, <<>>).
+ jid:make(<<>>, <<"proxy.", Server/binary>>, <<>>).
muc_jid(Config) ->
Server = ?config(server, Config),
- jlib:make_jid(<<>>, <<"conference.", Server/binary>>, <<>>).
+ jid:make(<<>>, <<"conference.", Server/binary>>, <<>>).
muc_room_jid(Config) ->
Server = ?config(server, Config),
- jlib:make_jid(<<"test">>, <<"conference.", Server/binary>>, <<>>).
+ jid:make(<<"test">>, <<"conference.", Server/binary>>, <<>>).
+
+mix_jid(Config) ->
+ Server = ?config(server, Config),
+ jid:make(<<>>, <<"mix.", Server/binary>>, <<>>).
+
+mix_room_jid(Config) ->
+ Server = ?config(server, Config),
+ jid:make(<<"test">>, <<"mix.", Server/binary>>, <<>>).
id() ->
id(undefined).
diff --git a/test/suite.hrl b/test/suite.hrl
index 5463575a..fb6b4f3a 100644
--- a/test/suite.hrl
+++ b/test/suite.hrl
@@ -1,5 +1,5 @@
-include_lib("common_test/include/ct.hrl").
--include_lib("p1_xml/include/xml.hrl").
+-include_lib("fast_xml/include/fxml.hrl").
-include("ns.hrl").
-include("ejabberd.hrl").
-include("mod_proxy65.hrl").
@@ -74,6 +74,7 @@
-define(COMMON_VHOST, <<"localhost">>).
-define(MNESIA_VHOST, <<"mnesia.localhost">>).
+-define(REDIS_VHOST, <<"redis.localhost">>).
-define(MYSQL_VHOST, <<"mysql.localhost">>).
-define(PGSQL_VHOST, <<"pgsql.localhost">>).
-define(SQLITE_VHOST, <<"sqlite.localhost">>).
diff --git a/tools/ejabberdctl.bc b/tools/ejabberdctl.bc
index 72a5356f..3a63e29f 100644
--- a/tools/ejabberdctl.bc
+++ b/tools/ejabberdctl.bc
@@ -42,7 +42,7 @@ _ejabberdctl()
ejabberdctl)
# This clause matches even when calling `/sbin/ejabberdctl` thanks to the ##*/ in the case
get_help
- COMPREPLY=($(compgen -W "--node --auth ${startpars} ${startcoms} ${runningcommands}" -- $cur))
+ COMPREPLY=($(compgen -W "--node ${startpars} ${startcoms} ${runningcommands}" -- $cur))
return 0
;;
start|live)
@@ -75,7 +75,7 @@ _ejabberdctl()
prev2="${COMP_WORDS[COMP_CWORD-2]}"
get_help
if [[ "$prev2" == --* ]]; then
- COMPREPLY=($(compgen -W "--node --auth ${startpars} ${startcoms} ${runningcommands}" -- $cur))
+ COMPREPLY=($(compgen -W "--node ${startpars} ${startcoms} ${runningcommands}" -- $cur))
else
if [[ $ISRUNNING == 1 ]]; then
echo ""
diff --git a/tools/xmpp_codec.erl b/tools/xmpp_codec.erl
index 3adceabc..01e0676a 100644
--- a/tools/xmpp_codec.erl
+++ b/tools/xmpp_codec.erl
@@ -1,4 +1,4 @@
-%% Created automatically by XML generator (xml_gen.erl)
+%% Created automatically by XML generator (fxml_gen.erl)
%% Source: xmpp_codec.spec
-module(xmpp_codec).
@@ -15,6 +15,32 @@ decode(_el) -> decode(_el, []).
decode({xmlel, _name, _attrs, _} = _el, Opts) ->
IgnoreEls = proplists:get_bool(ignore_els, Opts),
case {_name, get_attr(<<"xmlns">>, _attrs)} of
+ {<<"participant">>, <<"urn:xmpp:mix:0">>} ->
+ decode_mix_participant(<<"urn:xmpp:mix:0">>, IgnoreEls,
+ _el);
+ {<<"leave">>, <<"urn:xmpp:mix:0">>} ->
+ decode_mix_leave(<<"urn:xmpp:mix:0">>, IgnoreEls, _el);
+ {<<"join">>, <<"urn:xmpp:mix:0">>} ->
+ decode_mix_join(<<"urn:xmpp:mix:0">>, IgnoreEls, _el);
+ {<<"subscribe">>, <<"urn:xmpp:mix:0">>} ->
+ decode_mix_subscribe(<<"urn:xmpp:mix:0">>, IgnoreEls,
+ _el);
+ {<<"offline">>,
+ <<"http://jabber.org/protocol/offline">>} ->
+ decode_offline(<<"http://jabber.org/protocol/offline">>,
+ IgnoreEls, _el);
+ {<<"item">>,
+ <<"http://jabber.org/protocol/offline">>} ->
+ decode_offline_item(<<"http://jabber.org/protocol/offline">>,
+ IgnoreEls, _el);
+ {<<"fetch">>,
+ <<"http://jabber.org/protocol/offline">>} ->
+ decode_offline_fetch(<<"http://jabber.org/protocol/offline">>,
+ IgnoreEls, _el);
+ {<<"purge">>,
+ <<"http://jabber.org/protocol/offline">>} ->
+ decode_offline_purge(<<"http://jabber.org/protocol/offline">>,
+ IgnoreEls, _el);
{<<"failed">>, <<"urn:xmpp:sm:2">>} ->
decode_sm_failed(<<"urn:xmpp:sm:2">>, IgnoreEls, _el);
{<<"failed">>, <<"urn:xmpp:sm:3">>} ->
@@ -1072,6 +1098,22 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) ->
is_known_tag({xmlel, _name, _attrs, _} = _el) ->
case {_name, get_attr(<<"xmlns">>, _attrs)} of
+ {<<"participant">>, <<"urn:xmpp:mix:0">>} -> true;
+ {<<"leave">>, <<"urn:xmpp:mix:0">>} -> true;
+ {<<"join">>, <<"urn:xmpp:mix:0">>} -> true;
+ {<<"subscribe">>, <<"urn:xmpp:mix:0">>} -> true;
+ {<<"offline">>,
+ <<"http://jabber.org/protocol/offline">>} ->
+ true;
+ {<<"item">>,
+ <<"http://jabber.org/protocol/offline">>} ->
+ true;
+ {<<"fetch">>,
+ <<"http://jabber.org/protocol/offline">>} ->
+ true;
+ {<<"purge">>,
+ <<"http://jabber.org/protocol/offline">>} ->
+ true;
{<<"failed">>, <<"urn:xmpp:sm:2">>} -> true;
{<<"failed">>, <<"urn:xmpp:sm:3">>} -> true;
{<<"a">>, <<"urn:xmpp:sm:2">>} -> true;
@@ -1959,7 +2001,7 @@ encode({pubsub_items, _, _, _, _} = Items) ->
encode_pubsub_items(Items,
[{<<"xmlns">>,
<<"http://jabber.org/protocol/pubsub">>}]);
-encode({pubsub_event_item, _, _, _} = Item) ->
+encode({pubsub_event_item, _, _, _, _} = Item) ->
encode_pubsub_event_item(Item,
[{<<"xmlns">>,
<<"http://jabber.org/protocol/pubsub#event">>}]);
@@ -2124,7 +2166,24 @@ encode({sm_resumed, _, _, _} = Resumed) ->
encode({sm_r, _} = R) -> encode_sm_r(R, []);
encode({sm_a, _, _} = A) -> encode_sm_a(A, []);
encode({sm_failed, _, _} = Failed) ->
- encode_sm_failed(Failed, []).
+ encode_sm_failed(Failed, []);
+encode({offline_item, _, _} = Item) ->
+ encode_offline_item(Item,
+ [{<<"xmlns">>,
+ <<"http://jabber.org/protocol/offline">>}]);
+encode({offline, _, _, _} = Offline) ->
+ encode_offline(Offline,
+ [{<<"xmlns">>,
+ <<"http://jabber.org/protocol/offline">>}]);
+encode({mix_join, _, _} = Join) ->
+ encode_mix_join(Join,
+ [{<<"xmlns">>, <<"urn:xmpp:mix:0">>}]);
+encode({mix_leave} = Leave) ->
+ encode_mix_leave(Leave,
+ [{<<"xmlns">>, <<"urn:xmpp:mix:0">>}]);
+encode({mix_participant, _, _} = Participant) ->
+ encode_mix_participant(Participant,
+ [{<<"xmlns">>, <<"urn:xmpp:mix:0">>}]).
get_ns({last, _, _}) -> <<"jabber:iq:last">>;
get_ns({version, _, _, _}) -> <<"jabber:iq:version">>;
@@ -2250,7 +2309,7 @@ get_ns({pubsub_item, _, _}) ->
<<"http://jabber.org/protocol/pubsub">>;
get_ns({pubsub_items, _, _, _, _}) ->
<<"http://jabber.org/protocol/pubsub">>;
-get_ns({pubsub_event_item, _, _, _}) ->
+get_ns({pubsub_event_item, _, _, _, _}) ->
<<"http://jabber.org/protocol/pubsub#event">>;
get_ns({pubsub_event_items, _, _, _}) ->
<<"http://jabber.org/protocol/pubsub#event">>;
@@ -2319,6 +2378,13 @@ get_ns({carbons_sent, _}) -> <<"urn:xmpp:carbons:2">>;
get_ns({feature_csi, _}) -> <<"urn:xmpp:csi:0">>;
get_ns({csi, active}) -> <<"urn:xmpp:csi:0">>;
get_ns({csi, inactive}) -> <<"urn:xmpp:csi:0">>;
+get_ns({offline_item, _, _}) ->
+ <<"http://jabber.org/protocol/offline">>;
+get_ns({offline, _, _, _}) ->
+ <<"http://jabber.org/protocol/offline">>;
+get_ns({mix_join, _, _}) -> <<"urn:xmpp:mix:0">>;
+get_ns({mix_leave}) -> <<"urn:xmpp:mix:0">>;
+get_ns({mix_participant, _, _}) -> <<"urn:xmpp:mix:0">>;
get_ns(_) -> <<>>.
dec_int(Val) -> dec_int(Val, infinity, infinity).
@@ -2465,7 +2531,8 @@ pp(pubsub_subscription, 4) -> [jid, node, subid, type];
pp(pubsub_affiliation, 2) -> [node, type];
pp(pubsub_item, 2) -> [id, xml_els];
pp(pubsub_items, 4) -> [node, max_items, subid, items];
-pp(pubsub_event_item, 3) -> [id, node, publisher];
+pp(pubsub_event_item, 4) ->
+ [id, node, publisher, xml_els];
pp(pubsub_event_items, 3) -> [node, retract, items];
pp(pubsub_event, 1) -> [items];
pp(pubsub_subscribe, 2) -> [node, jid];
@@ -2522,6 +2589,11 @@ pp(sm_resumed, 3) -> [h, previd, xmlns];
pp(sm_r, 1) -> [xmlns];
pp(sm_a, 2) -> [h, xmlns];
pp(sm_failed, 2) -> [reason, xmlns];
+pp(offline_item, 2) -> [node, action];
+pp(offline, 3) -> [items, purge, fetch];
+pp(mix_join, 2) -> [jid, subscribe];
+pp(mix_leave, 0) -> [];
+pp(mix_participant, 2) -> [jid, nick];
pp(_, _) -> no.
enc_bool(false) -> <<"false">>;
@@ -2564,6 +2636,320 @@ dec_tzo(Val) ->
M = jlib:binary_to_integer(M1),
if H >= -12, H =< 12, M >= 0, M < 60 -> {H, M} end.
+decode_mix_participant(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"participant">>, _attrs, _els}) ->
+ {Jid, Nick} = decode_mix_participant_attrs(__TopXMLNS,
+ _attrs, undefined, undefined),
+ {mix_participant, Jid, Nick}.
+
+decode_mix_participant_attrs(__TopXMLNS,
+ [{<<"jid">>, _val} | _attrs], _Jid, Nick) ->
+ decode_mix_participant_attrs(__TopXMLNS, _attrs, _val,
+ Nick);
+decode_mix_participant_attrs(__TopXMLNS,
+ [{<<"nick">>, _val} | _attrs], Jid, _Nick) ->
+ decode_mix_participant_attrs(__TopXMLNS, _attrs, Jid,
+ _val);
+decode_mix_participant_attrs(__TopXMLNS, [_ | _attrs],
+ Jid, Nick) ->
+ decode_mix_participant_attrs(__TopXMLNS, _attrs, Jid,
+ Nick);
+decode_mix_participant_attrs(__TopXMLNS, [], Jid,
+ Nick) ->
+ {decode_mix_participant_attr_jid(__TopXMLNS, Jid),
+ decode_mix_participant_attr_nick(__TopXMLNS, Nick)}.
+
+encode_mix_participant({mix_participant, Jid, Nick},
+ _xmlns_attrs) ->
+ _els = [],
+ _attrs = encode_mix_participant_attr_nick(Nick,
+ encode_mix_participant_attr_jid(Jid,
+ _xmlns_attrs)),
+ {xmlel, <<"participant">>, _attrs, _els}.
+
+decode_mix_participant_attr_jid(__TopXMLNS,
+ undefined) ->
+ erlang:error({xmpp_codec,
+ {missing_attr, <<"jid">>, <<"participant">>,
+ __TopXMLNS}});
+decode_mix_participant_attr_jid(__TopXMLNS, _val) ->
+ case catch dec_jid(_val) of
+ {'EXIT', _} ->
+ erlang:error({xmpp_codec,
+ {bad_attr_value, <<"jid">>, <<"participant">>,
+ __TopXMLNS}});
+ _res -> _res
+ end.
+
+encode_mix_participant_attr_jid(_val, _acc) ->
+ [{<<"jid">>, enc_jid(_val)} | _acc].
+
+decode_mix_participant_attr_nick(__TopXMLNS,
+ undefined) ->
+ undefined;
+decode_mix_participant_attr_nick(__TopXMLNS, _val) ->
+ _val.
+
+encode_mix_participant_attr_nick(undefined, _acc) ->
+ _acc;
+encode_mix_participant_attr_nick(_val, _acc) ->
+ [{<<"nick">>, _val} | _acc].
+
+decode_mix_leave(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"leave">>, _attrs, _els}) ->
+ {mix_leave}.
+
+encode_mix_leave({mix_leave}, _xmlns_attrs) ->
+ _els = [],
+ _attrs = _xmlns_attrs,
+ {xmlel, <<"leave">>, _attrs, _els}.
+
+decode_mix_join(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"join">>, _attrs, _els}) ->
+ Subscribe = decode_mix_join_els(__TopXMLNS, __IgnoreEls,
+ _els, []),
+ Jid = decode_mix_join_attrs(__TopXMLNS, _attrs,
+ undefined),
+ {mix_join, Jid, Subscribe}.
+
+decode_mix_join_els(__TopXMLNS, __IgnoreEls, [],
+ Subscribe) ->
+ lists:reverse(Subscribe);
+decode_mix_join_els(__TopXMLNS, __IgnoreEls,
+ [{xmlel, <<"subscribe">>, _attrs, _} = _el | _els],
+ Subscribe) ->
+ _xmlns = get_attr(<<"xmlns">>, _attrs),
+ if _xmlns == <<>>; _xmlns == __TopXMLNS ->
+ decode_mix_join_els(__TopXMLNS, __IgnoreEls, _els,
+ [decode_mix_subscribe(__TopXMLNS, __IgnoreEls,
+ _el)
+ | Subscribe]);
+ true ->
+ decode_mix_join_els(__TopXMLNS, __IgnoreEls, _els,
+ Subscribe)
+ end;
+decode_mix_join_els(__TopXMLNS, __IgnoreEls, [_ | _els],
+ Subscribe) ->
+ decode_mix_join_els(__TopXMLNS, __IgnoreEls, _els,
+ Subscribe).
+
+decode_mix_join_attrs(__TopXMLNS,
+ [{<<"jid">>, _val} | _attrs], _Jid) ->
+ decode_mix_join_attrs(__TopXMLNS, _attrs, _val);
+decode_mix_join_attrs(__TopXMLNS, [_ | _attrs], Jid) ->
+ decode_mix_join_attrs(__TopXMLNS, _attrs, Jid);
+decode_mix_join_attrs(__TopXMLNS, [], Jid) ->
+ decode_mix_join_attr_jid(__TopXMLNS, Jid).
+
+encode_mix_join({mix_join, Jid, Subscribe},
+ _xmlns_attrs) ->
+ _els =
+ lists:reverse('encode_mix_join_$subscribe'(Subscribe,
+ [])),
+ _attrs = encode_mix_join_attr_jid(Jid, _xmlns_attrs),
+ {xmlel, <<"join">>, _attrs, _els}.
+
+'encode_mix_join_$subscribe'([], _acc) -> _acc;
+'encode_mix_join_$subscribe'([Subscribe | _els],
+ _acc) ->
+ 'encode_mix_join_$subscribe'(_els,
+ [encode_mix_subscribe(Subscribe, []) | _acc]).
+
+decode_mix_join_attr_jid(__TopXMLNS, undefined) ->
+ undefined;
+decode_mix_join_attr_jid(__TopXMLNS, _val) ->
+ case catch dec_jid(_val) of
+ {'EXIT', _} ->
+ erlang:error({xmpp_codec,
+ {bad_attr_value, <<"jid">>, <<"join">>, __TopXMLNS}});
+ _res -> _res
+ end.
+
+encode_mix_join_attr_jid(undefined, _acc) -> _acc;
+encode_mix_join_attr_jid(_val, _acc) ->
+ [{<<"jid">>, enc_jid(_val)} | _acc].
+
+decode_mix_subscribe(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"subscribe">>, _attrs, _els}) ->
+ Node = decode_mix_subscribe_attrs(__TopXMLNS, _attrs,
+ undefined),
+ Node.
+
+decode_mix_subscribe_attrs(__TopXMLNS,
+ [{<<"node">>, _val} | _attrs], _Node) ->
+ decode_mix_subscribe_attrs(__TopXMLNS, _attrs, _val);
+decode_mix_subscribe_attrs(__TopXMLNS, [_ | _attrs],
+ Node) ->
+ decode_mix_subscribe_attrs(__TopXMLNS, _attrs, Node);
+decode_mix_subscribe_attrs(__TopXMLNS, [], Node) ->
+ decode_mix_subscribe_attr_node(__TopXMLNS, Node).
+
+encode_mix_subscribe(Node, _xmlns_attrs) ->
+ _els = [],
+ _attrs = encode_mix_subscribe_attr_node(Node,
+ _xmlns_attrs),
+ {xmlel, <<"subscribe">>, _attrs, _els}.
+
+decode_mix_subscribe_attr_node(__TopXMLNS, undefined) ->
+ erlang:error({xmpp_codec,
+ {missing_attr, <<"node">>, <<"subscribe">>,
+ __TopXMLNS}});
+decode_mix_subscribe_attr_node(__TopXMLNS, _val) ->
+ _val.
+
+encode_mix_subscribe_attr_node(_val, _acc) ->
+ [{<<"node">>, _val} | _acc].
+
+decode_offline(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"offline">>, _attrs, _els}) ->
+ {Items, Purge, Fetch} = decode_offline_els(__TopXMLNS,
+ __IgnoreEls, _els, [], false,
+ false),
+ {offline, Items, Purge, Fetch}.
+
+decode_offline_els(__TopXMLNS, __IgnoreEls, [], Items,
+ Purge, Fetch) ->
+ {lists:reverse(Items), Purge, Fetch};
+decode_offline_els(__TopXMLNS, __IgnoreEls,
+ [{xmlel, <<"purge">>, _attrs, _} = _el | _els], Items,
+ Purge, Fetch) ->
+ _xmlns = get_attr(<<"xmlns">>, _attrs),
+ if _xmlns == <<>>; _xmlns == __TopXMLNS ->
+ decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items,
+ decode_offline_purge(__TopXMLNS, __IgnoreEls,
+ _el),
+ Fetch);
+ true ->
+ decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items,
+ Purge, Fetch)
+ end;
+decode_offline_els(__TopXMLNS, __IgnoreEls,
+ [{xmlel, <<"fetch">>, _attrs, _} = _el | _els], Items,
+ Purge, Fetch) ->
+ _xmlns = get_attr(<<"xmlns">>, _attrs),
+ if _xmlns == <<>>; _xmlns == __TopXMLNS ->
+ decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items,
+ Purge,
+ decode_offline_fetch(__TopXMLNS, __IgnoreEls,
+ _el));
+ true ->
+ decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items,
+ Purge, Fetch)
+ end;
+decode_offline_els(__TopXMLNS, __IgnoreEls,
+ [{xmlel, <<"item">>, _attrs, _} = _el | _els], Items,
+ Purge, Fetch) ->
+ _xmlns = get_attr(<<"xmlns">>, _attrs),
+ if _xmlns == <<>>; _xmlns == __TopXMLNS ->
+ decode_offline_els(__TopXMLNS, __IgnoreEls, _els,
+ [decode_offline_item(__TopXMLNS, __IgnoreEls, _el)
+ | Items],
+ Purge, Fetch);
+ true ->
+ decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items,
+ Purge, Fetch)
+ end;
+decode_offline_els(__TopXMLNS, __IgnoreEls, [_ | _els],
+ Items, Purge, Fetch) ->
+ decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items,
+ Purge, Fetch).
+
+encode_offline({offline, Items, Purge, Fetch},
+ _xmlns_attrs) ->
+ _els = lists:reverse('encode_offline_$items'(Items,
+ 'encode_offline_$purge'(Purge,
+ 'encode_offline_$fetch'(Fetch,
+ [])))),
+ _attrs = _xmlns_attrs,
+ {xmlel, <<"offline">>, _attrs, _els}.
+
+'encode_offline_$items'([], _acc) -> _acc;
+'encode_offline_$items'([Items | _els], _acc) ->
+ 'encode_offline_$items'(_els,
+ [encode_offline_item(Items, []) | _acc]).
+
+'encode_offline_$purge'(false, _acc) -> _acc;
+'encode_offline_$purge'(Purge, _acc) ->
+ [encode_offline_purge(Purge, []) | _acc].
+
+'encode_offline_$fetch'(false, _acc) -> _acc;
+'encode_offline_$fetch'(Fetch, _acc) ->
+ [encode_offline_fetch(Fetch, []) | _acc].
+
+decode_offline_item(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"item">>, _attrs, _els}) ->
+ {Node, Action} = decode_offline_item_attrs(__TopXMLNS,
+ _attrs, undefined, undefined),
+ {offline_item, Node, Action}.
+
+decode_offline_item_attrs(__TopXMLNS,
+ [{<<"node">>, _val} | _attrs], _Node, Action) ->
+ decode_offline_item_attrs(__TopXMLNS, _attrs, _val,
+ Action);
+decode_offline_item_attrs(__TopXMLNS,
+ [{<<"action">>, _val} | _attrs], Node, _Action) ->
+ decode_offline_item_attrs(__TopXMLNS, _attrs, Node,
+ _val);
+decode_offline_item_attrs(__TopXMLNS, [_ | _attrs],
+ Node, Action) ->
+ decode_offline_item_attrs(__TopXMLNS, _attrs, Node,
+ Action);
+decode_offline_item_attrs(__TopXMLNS, [], Node,
+ Action) ->
+ {decode_offline_item_attr_node(__TopXMLNS, Node),
+ decode_offline_item_attr_action(__TopXMLNS, Action)}.
+
+encode_offline_item({offline_item, Node, Action},
+ _xmlns_attrs) ->
+ _els = [],
+ _attrs = encode_offline_item_attr_action(Action,
+ encode_offline_item_attr_node(Node,
+ _xmlns_attrs)),
+ {xmlel, <<"item">>, _attrs, _els}.
+
+decode_offline_item_attr_node(__TopXMLNS, undefined) ->
+ undefined;
+decode_offline_item_attr_node(__TopXMLNS, _val) -> _val.
+
+encode_offline_item_attr_node(undefined, _acc) -> _acc;
+encode_offline_item_attr_node(_val, _acc) ->
+ [{<<"node">>, _val} | _acc].
+
+decode_offline_item_attr_action(__TopXMLNS,
+ undefined) ->
+ undefined;
+decode_offline_item_attr_action(__TopXMLNS, _val) ->
+ case catch dec_enum(_val, [view, remove]) of
+ {'EXIT', _} ->
+ erlang:error({xmpp_codec,
+ {bad_attr_value, <<"action">>, <<"item">>,
+ __TopXMLNS}});
+ _res -> _res
+ end.
+
+encode_offline_item_attr_action(undefined, _acc) ->
+ _acc;
+encode_offline_item_attr_action(_val, _acc) ->
+ [{<<"action">>, enc_enum(_val)} | _acc].
+
+decode_offline_fetch(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"fetch">>, _attrs, _els}) ->
+ true.
+
+encode_offline_fetch(true, _xmlns_attrs) ->
+ _els = [],
+ _attrs = _xmlns_attrs,
+ {xmlel, <<"fetch">>, _attrs, _els}.
+
+decode_offline_purge(__TopXMLNS, __IgnoreEls,
+ {xmlel, <<"purge">>, _attrs, _els}) ->
+ true.
+
+encode_offline_purge(true, _xmlns_attrs) ->
+ _els = [],
+ _attrs = _xmlns_attrs,
+ {xmlel, <<"purge">>, _attrs, _els}.
+
decode_sm_failed(__TopXMLNS, __IgnoreEls,
{xmlel, <<"failed">>, _attrs, _els}) ->
Reason = decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
@@ -7691,10 +8077,24 @@ encode_pubsub_event_items_attr_node(_val, _acc) ->
decode_pubsub_event_item(__TopXMLNS, __IgnoreEls,
{xmlel, <<"item">>, _attrs, _els}) ->
+ __Xmls = decode_pubsub_event_item_els(__TopXMLNS,
+ __IgnoreEls, _els, []),
{Id, Node, Publisher} =
decode_pubsub_event_item_attrs(__TopXMLNS, _attrs,
undefined, undefined, undefined),
- {pubsub_event_item, Id, Node, Publisher}.
+ {pubsub_event_item, Id, Node, Publisher, __Xmls}.
+
+decode_pubsub_event_item_els(__TopXMLNS, __IgnoreEls,
+ [], __Xmls) ->
+ lists:reverse(__Xmls);
+decode_pubsub_event_item_els(__TopXMLNS, __IgnoreEls,
+ [{xmlel, _, _, _} = _el | _els], __Xmls) ->
+ decode_pubsub_event_item_els(__TopXMLNS, __IgnoreEls,
+ _els, [_el | __Xmls]);
+decode_pubsub_event_item_els(__TopXMLNS, __IgnoreEls,
+ [_ | _els], __Xmls) ->
+ decode_pubsub_event_item_els(__TopXMLNS, __IgnoreEls,
+ _els, __Xmls).
decode_pubsub_event_item_attrs(__TopXMLNS,
[{<<"id">>, _val} | _attrs], _Id, Node,
@@ -7723,9 +8123,9 @@ decode_pubsub_event_item_attrs(__TopXMLNS, [], Id, Node,
Publisher)}.
encode_pubsub_event_item({pubsub_event_item, Id, Node,
- Publisher},
+ Publisher, __Xmls},
_xmlns_attrs) ->
- _els = [],
+ _els = __Xmls,
_attrs =
encode_pubsub_event_item_attr_publisher(Publisher,
encode_pubsub_event_item_attr_node(Node,
diff --git a/tools/xmpp_codec.hrl b/tools/xmpp_codec.hrl
index 7996f6a1..b2773a6c 100644
--- a/tools/xmpp_codec.hrl
+++ b/tools/xmpp_codec.hrl
@@ -1,4 +1,4 @@
-%% Created automatically by XML generator (xml_gen.erl)
+%% Created automatically by XML generator (fxml_gen.erl)
%% Source: xmpp_codec.spec
-record(chatstate, {type :: active | composing | gone | inactive | paused}).
@@ -36,6 +36,8 @@
jid :: any(),
subid :: binary()}).
+-record(mix_leave, {}).
+
-record(ping, {}).
-record(delay, {stamp :: any(),
@@ -98,7 +100,8 @@
-record(pubsub_event_item, {id :: binary(),
node :: binary(),
- publisher :: binary()}).
+ publisher :: binary(),
+ xml_els = [] :: [any()]}).
-record(sm_r, {xmlns :: binary()}).
@@ -229,6 +232,9 @@
notify = false :: any(),
items = [] :: [#pubsub_item{}]}).
+-record(mix_participant, {jid :: any(),
+ nick :: binary()}).
+
-record(vcard_geo, {lat :: binary(),
lon :: binary()}).
@@ -424,6 +430,13 @@
features = [] :: [binary()],
xdata = [] :: [#xdata{}]}).
+-record(offline_item, {node :: binary(),
+ action :: 'remove' | 'view'}).
+
+-record(offline, {items = [] :: [#offline_item{}],
+ purge = false :: boolean(),
+ fetch = false :: boolean()}).
+
-record(sasl_mechanisms, {list = [] :: [binary()]}).
-record(sm_failed, {reason :: atom() | #gone{} | #redirect{},
@@ -464,6 +477,9 @@
error :: #error{},
sub_els = [] :: [any()]}).
+-record(mix_join, {jid :: any(),
+ subscribe = [] :: [binary()]}).
+
-record(privacy_item, {order :: non_neg_integer(),
action :: 'allow' | 'deny',
type :: 'group' | 'jid' | 'subscription',
diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec
index 8d087c8b..e61b951c 100644
--- a/tools/xmpp_codec.spec
+++ b/tools/xmpp_codec.spec
@@ -1645,7 +1645,7 @@
-xml(pubsub_event_item,
#elem{name = <<"item">>,
xmlns = <<"http://jabber.org/protocol/pubsub#event">>,
- result = {pubsub_event_item, '$id', '$node', '$publisher'},
+ result = {pubsub_event_item, '$id', '$node', '$publisher', '$_xmls'},
attrs = [#attr{name = <<"id">>},
#attr{name = <<"node">>},
#attr{name = <<"publisher">>}]}).
@@ -2398,6 +2398,70 @@
#ref{name = error_unexpected_request,
min = 0, max = 1, label = '$reason'}]}).
+-xml(offline_purge,
+ #elem{name = <<"purge">>,
+ xmlns = <<"http://jabber.org/protocol/offline">>,
+ result = true}).
+
+-xml(offline_fetch,
+ #elem{name = <<"fetch">>,
+ xmlns = <<"http://jabber.org/protocol/offline">>,
+ result = true}).
+
+-xml(offline_item,
+ #elem{name = <<"item">>,
+ xmlns = <<"http://jabber.org/protocol/offline">>,
+ result = {offline_item, '$node', '$action'},
+ attrs = [#attr{name = <<"node">>},
+ #attr{name = <<"action">>,
+ dec = {dec_enum, [[view, remove]]},
+ enc = {enc_enum, []}}]}).
+
+-xml(offline,
+ #elem{name = <<"offline">>,
+ xmlns = <<"http://jabber.org/protocol/offline">>,
+ result = {offline, '$items', '$purge', '$fetch'},
+ refs = [#ref{name = offline_purge, min = 0, max = 1,
+ label = '$purge', default = false},
+ #ref{name = offline_fetch, min = 0, max = 1,
+ label = '$fetch', default = false},
+ #ref{name = offline_item, min = 0, label = '$items'}]}).
+
+-xml(mix_subscribe,
+ #elem{name = <<"subscribe">>,
+ xmlns = <<"urn:xmpp:mix:0">>,
+ result = '$node',
+ attrs = [#attr{name = <<"node">>,
+ required = true,
+ label = '$node'}]}).
+
+-xml(mix_join,
+ #elem{name = <<"join">>,
+ xmlns = <<"urn:xmpp:mix:0">>,
+ result = {mix_join, '$jid', '$subscribe'},
+ attrs = [#attr{name = <<"jid">>,
+ label = '$jid',
+ dec = {dec_jid, []},
+ enc = {enc_jid, []}}],
+ refs = [#ref{name = mix_subscribe, min = 0, label = '$subscribe'}]}).
+
+-xml(mix_leave,
+ #elem{name = <<"leave">>,
+ xmlns = <<"urn:xmpp:mix:0">>,
+ result = {mix_leave}}).
+
+-xml(mix_participant,
+ #elem{name = <<"participant">>,
+ xmlns = <<"urn:xmpp:mix:0">>,
+ result = {mix_participant, '$jid', '$nick'},
+ attrs = [#attr{name = <<"jid">>,
+ required = true,
+ label = '$jid',
+ dec = {dec_jid, []},
+ enc = {enc_jid, []}},
+ #attr{name = <<"nick">>,
+ label = '$nick'}]}).
+
dec_tzo(Val) ->
[H1, M1] = str:tokens(Val, <<":">>),
H = jlib:binary_to_integer(H1),