Compare commits
252 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 639c2fb640 | |||
| c585f74730 | |||
| d18fe138aa | |||
| d6f02a51e4 | |||
| 9c369b7a8c | |||
| d8bb5d9c01 | |||
| 26d3a978cc | |||
| 0f06ed8a9b | |||
| d1db9f92c4 | |||
| 52fde758b3 | |||
| b79aef3bbc | |||
| bae24464d3 | |||
| ef90a389c1 | |||
| 65ad70d7dc | |||
| a37cf33358 | |||
| 58478e52bf | |||
| b79f09d0eb | |||
| beeb1c82d9 | |||
| 2660dcabba | |||
| f0e6def3ed | |||
| 8487b51ecd | |||
| f7d4aae64d | |||
| 97e3a33077 | |||
| 1aae8a9fda | |||
| fafeeb80c2 | |||
| 655c22021b | |||
| 382c6ce1fb | |||
| 0bdcafdb02 | |||
| e5e4f39c01 | |||
| 222572bd56 | |||
| 64fdbe7866 | |||
| fb0ecf3361 | |||
| 52571e8624 | |||
| 901d2e0aed | |||
| 860db2ddca | |||
| c8c4a41b66 | |||
| 79d64e0d71 | |||
| f8e3560ad2 | |||
| 398f1de5ae | |||
| 0b439a7d5b | |||
| ae69f09257 | |||
| ef70ce65ab | |||
| b5d1ce795f | |||
| cd094bc903 | |||
| 2d7e03f5e1 | |||
| b2abc1edb7 | |||
| 7fd4808cde | |||
| 5eef8a8bcf | |||
| cd2e2b1a88 | |||
| 5958a91428 | |||
| 15d184a909 | |||
| 8c8a6869be | |||
| 695592a38c | |||
| bd3b7cd2df | |||
| b67dc00db2 | |||
| 127342449e | |||
| b4739396ec | |||
| 1dbdd58b1b | |||
| c5dbdfc71a | |||
| 86dfbe6ece | |||
| b83ec483e9 | |||
| afd3accf75 | |||
| 0935f493ee | |||
| 373f9fb0eb | |||
| 60c0c8e968 | |||
| 18557fa3d6 | |||
| a938af4180 | |||
| d5c29360fb | |||
| 48a1d818d6 | |||
| eb36440c2e | |||
| 47039aed15 | |||
| d45ad3e3a5 | |||
| 5ad8c790c7 | |||
| 5efcf0a175 | |||
| 0916694e0e | |||
| 2900aa208f | |||
| 27b4217a9d | |||
| a9e50468b6 | |||
| abf768274a | |||
| f78b170c24 | |||
| 991529a657 | |||
| b2279d481d | |||
| 267fdb2e95 | |||
| d9f1061b8a | |||
| dd654fa794 | |||
| 1b9b5f8e6a | |||
| 67b9b82261 | |||
| 2eee2de6e2 | |||
| acc11195f8 | |||
| be7f65da05 | |||
| 232915184c | |||
| 9ac6e4edf7 | |||
| 490aa2c6a6 | |||
| ca9ac019eb | |||
| e24da5789e | |||
| 47266de6d7 | |||
| f243c30847 | |||
| 2a2a47b5c6 | |||
| b5f1479763 | |||
| a8f92ae767 | |||
| 97d345d287 | |||
| ef2e2e45b3 | |||
| 3290a5ff15 | |||
| 7988e2e350 | |||
| dc0ca51ef1 | |||
| 78c4a0d65f | |||
| 91233eafaa | |||
| da0751239c | |||
| 3dc55c6d47 | |||
| d35c5ebde5 | |||
| 3cfcdbb245 | |||
| 7c2998a55d | |||
| fced8dc3d9 | |||
| c00cfca8e7 | |||
| 3c480a5b0b | |||
| b160bd7ac1 | |||
| 809057678b | |||
| 36ac1cd6c7 | |||
| ead83b008c | |||
| 6f25122f8c | |||
| bf79f223df | |||
| e58b62f737 | |||
| 82cf7f7ca8 | |||
| 6d7891ed16 | |||
| c7c70ffa0a | |||
| 78a44d8099 | |||
| b49a615e21 | |||
| 3b2d0fd24a | |||
| aa15148898 | |||
| 3809b898aa | |||
| e386bf6b58 | |||
| 221d8e0e5d | |||
| 53d12caa56 | |||
| b0ef3e66a8 | |||
| 7a9e93839a | |||
| 54ddc990c2 | |||
| 4afe0b195c | |||
| 494e638f03 | |||
| 915ccbbdfb | |||
| 381065397f | |||
| a08ecc0f41 | |||
| eace5fc463 | |||
| 0afcf561d6 | |||
| 46568fb959 | |||
| c7cf95ba99 | |||
| 914578a85e | |||
| df4c551f06 | |||
| a3a33bd5fc | |||
| 7066338948 | |||
| cb27a3540e | |||
| 61e914a83f | |||
| b90c3764c0 | |||
| bdce5556bd | |||
| 57f7b34b90 | |||
| f8cf1aef91 | |||
| e7ef65a22d | |||
| 107569a17d | |||
| 0112135096 | |||
| 4be9cc1b6d | |||
| 95475966fd | |||
| ef04dd75aa | |||
| 3441157a38 | |||
| c98df3c0da | |||
| c924cd42ff | |||
| 31c194a682 | |||
| 5b7dc0c215 | |||
| 0e3026539e | |||
| dc7b2c45c2 | |||
| cf9ef456b7 | |||
| 7b5825a205 | |||
| 2d103b4ae1 | |||
| 939bb244e1 | |||
| f19a54e9a1 | |||
| ef02053a9d | |||
| d9bb3730b7 | |||
| 7b72247b2c | |||
| 34bc698526 | |||
| efbaba5d04 | |||
| c985a2bd3d | |||
| 74053b114e | |||
| b40154f8f4 | |||
| 3c2cd91fb1 | |||
| 367adc2113 | |||
| 5b5548b8ca | |||
| 058b3d96bf | |||
| 68675effac | |||
| 3fef1a4435 | |||
| 7f25c3b3e8 | |||
| dcefb6bbe3 | |||
| 5351e8236d | |||
| 9440049208 | |||
| b871fbba1b | |||
| 91573a8e82 | |||
| 55c567ff00 | |||
| 5a4b7817df | |||
| 50e5cdc2fa | |||
| 5045fb584d | |||
| 357e48fb6b | |||
| 9ceeaf213b | |||
| 9297782868 | |||
| 0d1edc4771 | |||
| 1336c6c9fa | |||
| 8b03c0a385 | |||
| 15ee72138a | |||
| 92a0181932 | |||
| 035c63fd2a | |||
| 9e6efaf9bc | |||
| f4ee8a2505 | |||
| 842d52352a | |||
| e31799a3b1 | |||
| 1860801e36 | |||
| ae4fa22180 | |||
| b5121a346d | |||
| b6289d646f | |||
| c065a2c5b9 | |||
| 6e40573c13 | |||
| eb0890284a | |||
| 16c1b9a5c2 | |||
| 83accedded | |||
| 8e6a301026 | |||
| 4013629e5d | |||
| 6e14a47316 | |||
| 304afd75ac | |||
| 9c3d57e63e | |||
| 44978ce978 | |||
| 10d6c330a5 | |||
| e95cf420a2 | |||
| 3ecd7e850c | |||
| fee5873310 | |||
| 96b09c587d | |||
| 79853ad44f | |||
| 9a049442ff | |||
| e21f25f5b9 | |||
| 1f9fd25ff8 | |||
| 968576d4f2 | |||
| d8fbe8a289 | |||
| 6d7ce0237a | |||
| 2d042f078e | |||
| 3d8219d8f9 | |||
| 7f3bffe821 | |||
| 99255631dd | |||
| ba35c1ed9d | |||
| 437e768e4a | |||
| c58a4be6ee | |||
| 6374ef4866 | |||
| eeac7f9b02 | |||
| 8c49d1e1af | |||
| ae77b1300a | |||
| d89bbba181 | |||
| 3e49bf0e83 | |||
| 917d48f30b | |||
| d9814709e2 |
@@ -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?
|
||||
|
||||
@@ -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?
|
||||
+19
-5
@@ -1,9 +1,8 @@
|
||||
language: erlang
|
||||
|
||||
otp_release:
|
||||
- 17.1
|
||||
- 17.5
|
||||
- 18.0
|
||||
- 18.3
|
||||
|
||||
services:
|
||||
- riak
|
||||
@@ -18,19 +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';"
|
||||
@@ -40,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
|
||||
- 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
|
||||
|
||||
@@ -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-----
|
||||
@@ -0,0 +1,5 @@
|
||||
{level, details}.
|
||||
{incl_dirs, ["src", "ebin"]}.
|
||||
{excl_mods, [eldap, 'ELDAPv3']}.
|
||||
{export, "logs/all.coverdata"}.
|
||||
|
||||
@@ -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
|
||||
|
||||
+33
-39
@@ -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: {}
|
||||
|
||||
+65
-112
@@ -83,18 +83,9 @@ if [ "$EJABBERD_DOC_PATH" = "" ] ; then
|
||||
fi
|
||||
if [ "$ERLANG_NODE_ARG" != "" ] ; then
|
||||
ERLANG_NODE=$ERLANG_NODE_ARG
|
||||
NODE=${ERLANG_NODE%@*}
|
||||
fi
|
||||
if [ "{{release}}" != "true" ] ; then
|
||||
if [ "$EJABBERDDIR" = "" ] ; then
|
||||
EJABBERDDIR={{libdir}}/ejabberd
|
||||
fi
|
||||
if [ "$EJABBERD_PRIV_PATH" = "" ] ; then
|
||||
EJABBERD_PRIV_PATH=$EJABBERDDIR/priv
|
||||
fi
|
||||
if [ "$EJABBERD_BIN_PATH" = "" ] ; then
|
||||
EJABBERD_BIN_PATH=$EJABBERD_PRIV_PATH/bin
|
||||
fi
|
||||
if [ "{{release}}" != "true" -a "$EJABBERD_BIN_PATH" = "" ] ; then
|
||||
EJABBERD_BIN_PATH={{libdir}}/ejabberd/priv/bin
|
||||
fi
|
||||
EJABBERD_LOG_PATH=$LOGS_DIR/ejabberd.log
|
||||
DATETIME=`date "+%Y%m%d-%H%M%S"`
|
||||
@@ -141,8 +132,8 @@ fi
|
||||
[ -z "$date" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_date '$date'"
|
||||
[ -z "$EJABBERD_OPTS" ] || EJABBERD_OPTS="-ejabberd ${EJABBERD_OPTS}"
|
||||
|
||||
[ -d $SPOOL_DIR ] || $EXEC_CMD "mkdir -p $SPOOL_DIR"
|
||||
cd $SPOOL_DIR
|
||||
[ -d "$SPOOL_DIR" ] || $EXEC_CMD "mkdir -p $SPOOL_DIR"
|
||||
cd "$SPOOL_DIR"
|
||||
|
||||
# export global variables
|
||||
export EJABBERD_CONFIG_PATH
|
||||
@@ -159,6 +150,15 @@ 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=()
|
||||
@@ -190,8 +190,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 \
|
||||
@@ -204,15 +204,15 @@ 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 \"$@\"\""
|
||||
$EXEC_CMD "$CMD"
|
||||
--erl `shell_escape \"$KERNEL_OPTS\"` \
|
||||
--erl `shell_escape \"$ERLANG_OPTS\"` \
|
||||
--erl `shell_escape \"${ARGS[@]}\"` \
|
||||
--erl `shell_escape_str \"$@\"`"
|
||||
$EXEC_CMD "ERL_PATH=$\"$ERL\" $CMD"
|
||||
}
|
||||
|
||||
# start interactive server
|
||||
@@ -233,15 +233,16 @@ 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 \"$@\"`\""
|
||||
$EXEC_CMD "$CMD"
|
||||
--erl `shell_escape \"$ERLANG_OPTS\"` \
|
||||
--erl `shell_escape \"${ARGS[@]}\"` \
|
||||
--erl `shell_escape_str \"$@\"`"
|
||||
$EXEC_CMD "ERL_PATH=\"$ERL\" $CMD"
|
||||
}
|
||||
|
||||
# start server in the foreground
|
||||
@@ -309,20 +310,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"
|
||||
}
|
||||
|
||||
@@ -350,97 +358,42 @@ 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
|
||||
|
||||
case $result in
|
||||
0) :;;
|
||||
1) :;;
|
||||
2) help;;
|
||||
3) help;;
|
||||
esac
|
||||
return $result
|
||||
}
|
||||
|
||||
ctlexec()
|
||||
{
|
||||
CONN_NAME=$1; shift
|
||||
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$CONN_NAME\"` \
|
||||
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
|
||||
2) help;;
|
||||
3) help;;
|
||||
*) :;;
|
||||
esac
|
||||
return $result
|
||||
}
|
||||
|
||||
uid()
|
||||
{
|
||||
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
|
||||
stop_epmd()
|
||||
{
|
||||
$EPMD -names 2>/dev/null | grep -q name || $EPMD -kill >/dev/null
|
||||
"$EPMD" -names 2>/dev/null | grep -q name || "$EPMD" -kill >/dev/null
|
||||
}
|
||||
|
||||
# make sure node not already running and node name unregistered
|
||||
check_start()
|
||||
{
|
||||
$EPMD -names 2>/dev/null | grep -q " ${ERLANG_NODE%@*} " && {
|
||||
"$EPMD" -names 2>/dev/null | grep -q " ${ERLANG_NODE%@*} " && {
|
||||
ps ux | grep -v grep | grep -q " $ERLANG_NODE " && {
|
||||
echo "ERROR: The ejabberd node '$ERLANG_NODE' is already running."
|
||||
exit 4
|
||||
@@ -451,7 +404,7 @@ check_start()
|
||||
echo "Shutdown all other erlang nodes, and call 'epmd -kill'."
|
||||
exit 5
|
||||
} || {
|
||||
$EPMD -kill >/dev/null
|
||||
"$EPMD" -kill >/dev/null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -485,7 +438,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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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()},
|
||||
|
||||
@@ -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}).
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-record(motd, {server = <<"">> :: binary(),
|
||||
packet = #xmlel{} :: xmlel()}).
|
||||
|
||||
-record(motd_users, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
|
||||
dummy = [] :: [] | '_'}).
|
||||
@@ -0,0 +1,4 @@
|
||||
-record(caps_features,
|
||||
{node_pair = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
features = [] :: [binary()] | pos_integer()
|
||||
}).
|
||||
@@ -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()}).
|
||||
@@ -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()}).
|
||||
@@ -0,0 +1,3 @@
|
||||
-record(last_activity, {us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
timestamp = 0 :: non_neg_integer(),
|
||||
status = <<"">> :: binary()}).
|
||||
@@ -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()]}).
|
||||
@@ -0,0 +1,4 @@
|
||||
-record(private_storage,
|
||||
{usns = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() |
|
||||
'$1' | '_'},
|
||||
xml = #xmlel{} :: xmlel() | '_' | '$1'}).
|
||||
@@ -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()}}).
|
||||
@@ -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()}).
|
||||
@@ -0,0 +1,2 @@
|
||||
-record(vcard_xupdate, {us = {<<>>, <<>>} :: {binary(), binary()},
|
||||
hash = <<>> :: binary()}).
|
||||
@@ -157,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">>).
|
||||
|
||||
+1
-1
@@ -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().
|
||||
|
||||
@@ -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
|
||||
@@ -1,2 +0,0 @@
|
||||
defmodule Ejabberd do
|
||||
end
|
||||
@@ -3,9 +3,9 @@ defmodule Ejabberd.Mixfile do
|
||||
|
||||
def project do
|
||||
[app: :ejabberd,
|
||||
version: "16.01.0-beta1",
|
||||
version: "16.04.0",
|
||||
description: description,
|
||||
elixir: "~> 1.1",
|
||||
elixir: "~> 1.2",
|
||||
elixirc_paths: ["lib"],
|
||||
compile_path: ".",
|
||||
compilers: [:asn1] ++ Mix.compilers,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
%{"bbmustache": {:hex, :bbmustache, "1.0.3"},
|
||||
%{"bbmustache": {:hex, :bbmustache, "1.0.4"},
|
||||
"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.15.0"},
|
||||
"esip": {:hex, :esip, "1.0.2"},
|
||||
"exrm": {:hex, :exrm, "1.0.0-rc7"},
|
||||
"erlware_commons": {:hex, :erlware_commons, "0.19.0"},
|
||||
"esip": {:hex, :esip, "1.0.4"},
|
||||
"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.3"},
|
||||
"fast_yaml": {:hex, :fast_yaml, "1.0.2"},
|
||||
"fast_tls": {:hex, :fast_tls, "1.0.3"},
|
||||
"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"},
|
||||
@@ -15,11 +16,11 @@
|
||||
"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.0.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.4.1"},
|
||||
"relx": {:hex, :relx, "3.5.0"},
|
||||
"providers": {:hex, :providers, "1.6.0"},
|
||||
"relx": {:hex, :relx, "3.19.0"},
|
||||
"sqlite3": {:hex, :sqlite3, "1.1.5"},
|
||||
"stringprep": {:hex, :stringprep, "1.0.2"},
|
||||
"stun": {:hex, :stun, "1.0.1"}}
|
||||
"stringprep": {:hex, :stringprep, "1.0.3"},
|
||||
"stun": {:hex, :stun, "1.0.3"}}
|
||||
|
||||
+113
-135
@@ -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."
|
||||
|
||||
|
||||
+36
-25
@@ -8,44 +8,49 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
{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"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.1"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.2"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.3"}}},
|
||||
{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", "1.0.2"}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.2"}}},
|
||||
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.3"}}},
|
||||
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.4"}}},
|
||||
{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.git", {tag, "0.6.1"}}},
|
||||
{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",
|
||||
"9524d0309a88b7c62ae93da0b632b185de3ba9db"}},
|
||||
{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"}}}},
|
||||
{tag, "1.0.1"}}}},
|
||||
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
|
||||
{tag, "1.0.1"}}}},
|
||||
{tag, "1.1.0"}}}},
|
||||
{if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
|
||||
{tag, "1.1.5"}}}},
|
||||
{tag, "1.1.5"}}}},
|
||||
{if_var_true, pam, {p1_pam, ".*", {git, "https://github.com/processone/epam",
|
||||
{tag, "1.0.0"}}}},
|
||||
{tag, "1.0.0"}}}},
|
||||
{if_var_true, zlib, {ezlib, ".*", {git, "https://github.com/processone/ezlib",
|
||||
{tag, "1.0.1"}}}},
|
||||
{if_var_true, riak, {hamcrest, ".*", {git, "https://github.com/hyperthunk/hamcrest-erlang",
|
||||
"908a24fda4a46776a5135db60ca071e3d783f9f6"}}}, % for riak_pb-2.1.0.7
|
||||
{tag, "1.0.1"}}}},
|
||||
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/basho/riak-erlang-client",
|
||||
"527722d12d0433b837cdb92a60900c2cb5df8942"}}},
|
||||
"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.0"}}}},
|
||||
{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"}}},
|
||||
{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.2", {git, "https://github.com/eproxus/meck",
|
||||
{tag, "0.8.2"}}}},
|
||||
{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"}}}}]}.
|
||||
{tag, "v1.0.8"}}}}]}.
|
||||
|
||||
{if_var_true, latest_deps,
|
||||
{floating_deps, [cache_tab,
|
||||
@@ -53,7 +58,7 @@
|
||||
stringprep,
|
||||
fast_xml,
|
||||
esip,
|
||||
luerl,
|
||||
luerl,
|
||||
stun,
|
||||
fast_yaml,
|
||||
p1_utils,
|
||||
@@ -63,8 +68,11 @@
|
||||
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'}},
|
||||
@@ -90,7 +98,7 @@
|
||||
|
||||
{xref_warnings, false}.
|
||||
|
||||
{xref_checks, [deprecated_function_calls, undefined_function_calls]}.
|
||||
{xref_checks, [deprecated_function_calls]}.
|
||||
|
||||
{xref_exclusions, [
|
||||
"(\"gen_transport\":_/_)",
|
||||
@@ -109,6 +117,9 @@
|
||||
|
||||
{eunit_compile_opts, [{i, "tools"}]}.
|
||||
|
||||
{cover_enabled, true}.
|
||||
{cover_export_enabled, true}.
|
||||
|
||||
{post_hook_configure, [{"fast_tls", []},
|
||||
{"stringprep", []},
|
||||
{"fast_yaml", []},
|
||||
|
||||
+26
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
Binary file not shown.
+6
-3
@@ -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 (
|
||||
|
||||
+4
-1
@@ -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;
|
||||
|
||||
+34
-2
@@ -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;
|
||||
|
||||
+3
-1
@@ -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}) ->
|
||||
|
||||
+1
-1
@@ -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">>};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 = fxml:get_attr_s(<<"digest-uri">>, KeyVals),
|
||||
UserName = proplists:get_value(<<"username">>, KeyVals, <<>>),
|
||||
%UserName = fxml: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 = fxml: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, <<>>),
|
||||
%fxml: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]),
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+11
-7
@@ -192,16 +192,20 @@ 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, odbc, riak],
|
||||
#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}},
|
||||
@@ -221,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],
|
||||
|
||||
+3
-40
@@ -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,
|
||||
@@ -256,7 +220,6 @@ start_apps() ->
|
||||
ejabberd:start_app(fast_tls),
|
||||
ejabberd:start_app(fast_xml),
|
||||
ejabberd:start_app(stringprep),
|
||||
ejabberd:start_app(ezlib),
|
||||
ejabberd:start_app(cache_tab).
|
||||
|
||||
opt_type(net_ticktime) ->
|
||||
|
||||
+28
-37
@@ -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_mnesia | ejabberd_auth_ldap
|
||||
%% | 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) ->
|
||||
@@ -428,30 +428,21 @@ auth_modules() ->
|
||||
%% Return the list of authenticated modules for a given host
|
||||
auth_modules(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
Default = case gen_mod:default_db(LServer) of
|
||||
mnesia -> internal;
|
||||
DBType -> DBType
|
||||
end,
|
||||
Default = ejabberd_config:default_db(LServer, ?MODULE),
|
||||
Methods = ejabberd_config:get_option(
|
||||
{auth_method, LServer},
|
||||
fun(V) when is_list(V) ->
|
||||
true = lists:all(fun is_atom/1, V),
|
||||
V;
|
||||
(V) when is_atom(V) ->
|
||||
[V]
|
||||
end, [Default]),
|
||||
{auth_method, LServer}, opt_type(auth_method), [Default]),
|
||||
[jlib:binary_to_atom(<<"ejabberd_auth_",
|
||||
(jlib:atom_to_binary(M))/binary>>)
|
||||
|| M <- Methods].
|
||||
|
||||
export(Server) ->
|
||||
ejabberd_auth_internal:export(Server).
|
||||
ejabberd_auth_mnesia:export(Server).
|
||||
|
||||
import(Server) ->
|
||||
ejabberd_auth_internal:import(Server).
|
||||
ejabberd_auth_mnesia:import(Server).
|
||||
|
||||
import(Server, mnesia, Passwd) ->
|
||||
ejabberd_auth_internal:import(Server, mnesia, Passwd);
|
||||
ejabberd_auth_mnesia:import(Server, mnesia, Passwd);
|
||||
import(Server, riak, Passwd) ->
|
||||
ejabberd_auth_riak:import(Server, riak, Passwd);
|
||||
import(_, _, _) ->
|
||||
@@ -459,7 +450,7 @@ import(_, _, _) ->
|
||||
|
||||
opt_type(auth_method) ->
|
||||
fun (V) when is_list(V) ->
|
||||
true = lists:all(fun is_atom/1, V), V;
|
||||
(V) when is_atom(V) -> [V]
|
||||
lists:map(fun(M) -> ejabberd_config:v_db(?MODULE, M) end, V);
|
||||
(V) -> [ejabberd_config:v_db(?MODULE, V)]
|
||||
end;
|
||||
opt_type(_) -> [auth_method].
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
@@ -56,7 +56,7 @@ start(Host) ->
|
||||
"extauth"),
|
||||
extauth:start(Host, Cmd),
|
||||
check_cache_last_options(Host),
|
||||
ejabberd_auth_internal:start(Host).
|
||||
ejabberd_auth_mnesia:start(Host).
|
||||
|
||||
check_cache_last_options(Server) ->
|
||||
case get_cache_option(Server) of
|
||||
@@ -76,21 +76,25 @@ 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
|
||||
true ->
|
||||
set_password_internal(User, Server, Password), ok;
|
||||
set_password_mnesia(User, Server, Password), ok;
|
||||
_ -> {error, unknown_problem}
|
||||
end.
|
||||
|
||||
@@ -102,20 +106,20 @@ try_register(User, Server, Password) ->
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
ejabberd_auth_internal:dirty_get_registered_users().
|
||||
ejabberd_auth_mnesia:dirty_get_registered_users().
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users(Server).
|
||||
ejabberd_auth_mnesia:get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users(Server, Data) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users(Server,
|
||||
ejabberd_auth_mnesia:get_vh_registered_users(Server,
|
||||
Data).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users_number(Server).
|
||||
ejabberd_auth_mnesia:get_vh_registered_users_number(Server).
|
||||
|
||||
get_vh_registered_users_number(Server, Data) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users_number(Server,
|
||||
ejabberd_auth_mnesia:get_vh_registered_users_number(Server,
|
||||
Data).
|
||||
|
||||
%% The password can only be returned if cache is enabled, cached info exists and is fresh enough.
|
||||
@@ -147,7 +151,7 @@ remove_user(User, Server) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_internal:remove_user(User, Server)
|
||||
ejabberd_auth_mnesia:remove_user(User, Server)
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -158,7 +162,7 @@ remove_user(User, Server, Password) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_internal:remove_user(User, Server,
|
||||
ejabberd_auth_mnesia:remove_user(User, Server,
|
||||
Password)
|
||||
end
|
||||
end.
|
||||
@@ -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,45 +191,45 @@ 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_mnesia(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_mnesia(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).
|
||||
get_password_mnesia(User, Server) ->
|
||||
ejabberd_auth_mnesia: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);
|
||||
online -> get_password_mnesia(User, Server);
|
||||
never -> false;
|
||||
mod_last_required ->
|
||||
?ERROR_MSG("extauth is used, extauth_cache is enabled "
|
||||
@@ -235,16 +239,16 @@ get_password_cache(User, Server, CacheTime) ->
|
||||
false;
|
||||
TimeStamp ->
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
true -> get_password_internal(User, Server);
|
||||
true -> get_password_mnesia(User, Server);
|
||||
false -> false
|
||||
end
|
||||
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;
|
||||
set_password_mnesia(User, Server, Password), true;
|
||||
false -> false
|
||||
end.
|
||||
|
||||
@@ -252,31 +256,31 @@ check_password_external_cache(User, Server, Password) ->
|
||||
try_register_external_cache(User, Server, Password) ->
|
||||
case try_register_extauth(User, Server, Password) of
|
||||
{atomic, ok} = R ->
|
||||
set_password_internal(User, Server, Password), R;
|
||||
set_password_mnesia(User, Server, Password), R;
|
||||
_ -> {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_mnesia(User, AuthzId, Server, Password) ->
|
||||
ejabberd_auth_mnesia:check_password(User, AuthzId, Server,
|
||||
Password).
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
|
||||
set_password_internal(User, Server, Password) ->
|
||||
set_password_mnesia(User, Server, Password) ->
|
||||
%% @spec (TimeLast, CacheTime) -> true | false
|
||||
%% TimeLast = online | never | integer()
|
||||
%% CacheTime = integer() | false
|
||||
ejabberd_auth_internal:set_password(User, Server,
|
||||
ejabberd_auth_mnesia:set_password(User, Server,
|
||||
Password).
|
||||
|
||||
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
|
||||
[] ->
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_auth_internal.erl
|
||||
%%% File : ejabberd_auth_mnesia.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Authentification via mnesia
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
@@ -23,7 +23,7 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_auth_internal).
|
||||
-module(ejabberd_auth_mnesia).
|
||||
|
||||
-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,
|
||||
@@ -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, <<"');">>]];
|
||||
@@ -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) ->
|
||||
|
||||
+18
-10
@@ -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, <<"');">>]];
|
||||
|
||||
@@ -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};
|
||||
+82
-82
@@ -139,8 +139,8 @@
|
||||
-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>">>).
|
||||
|
||||
@@ -327,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,
|
||||
@@ -382,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
|
||||
@@ -522,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,
|
||||
@@ -537,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};
|
||||
@@ -619,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}} ->
|
||||
@@ -634,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
|
||||
@@ -685,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;
|
||||
@@ -706,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
|
||||
@@ -718,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};
|
||||
@@ -752,9 +750,7 @@ wait_for_feature_request({xmlstreamelement, El},
|
||||
of
|
||||
{ok, Props} ->
|
||||
(StateData#state.sockmod):reset_stream(StateData#state.socket),
|
||||
%U = fxml:get_attr_s(username, Props),
|
||||
U = proplists:get_value(username, Props, <<>>),
|
||||
%AuthModule = fxml: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",
|
||||
@@ -839,7 +835,6 @@ wait_for_feature_request({xmlstreamelement, El},
|
||||
send_element(StateData,
|
||||
?POLICY_VIOLATION_ERR(Lang,
|
||||
<<"Use of STARTTLS required">>)),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData};
|
||||
true ->
|
||||
process_unauthenticated_stanza(StateData, El),
|
||||
@@ -850,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};
|
||||
@@ -876,9 +870,7 @@ wait_for_sasl_response({xmlstreamelement, El},
|
||||
{ok, Props} ->
|
||||
catch
|
||||
(StateData#state.sockmod):reset_stream(StateData#state.socket),
|
||||
% U = fxml:get_attr_s(username, Props),
|
||||
U = proplists:get_value(username, Props, <<>>),
|
||||
% AuthModule = fxml: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",
|
||||
@@ -899,9 +891,7 @@ wait_for_sasl_response({xmlstreamelement, El},
|
||||
user = U});
|
||||
{ok, Props, ServerOut} ->
|
||||
(StateData#state.sockmod):reset_stream(StateData#state.socket),
|
||||
% U = fxml:get_attr_s(username, Props),
|
||||
U = proplists:get_value(username, Props, <<>>),
|
||||
% AuthModule = fxml: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",
|
||||
@@ -963,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};
|
||||
@@ -999,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.
|
||||
|
||||
@@ -1019,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 = 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);
|
||||
_ ->
|
||||
@@ -1092,10 +1081,9 @@ wait_for_bind({xmlstreamelement, El}, StateData) ->
|
||||
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};
|
||||
@@ -1106,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 ->
|
||||
@@ -1143,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)
|
||||
@@ -1166,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)
|
||||
@@ -1179,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),
|
||||
@@ -1344,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}},
|
||||
@@ -1357,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,
|
||||
@@ -1628,7 +1613,6 @@ handle_info({route, From, To,
|
||||
<<"error">> -> ok;
|
||||
<<"groupchat">> -> ok;
|
||||
<<"headline">> -> ok;
|
||||
<<"result">> -> ok;
|
||||
_ ->
|
||||
Err =
|
||||
jlib:make_error_reply(Packet,
|
||||
@@ -1671,11 +1655,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};
|
||||
@@ -1807,6 +1789,7 @@ terminate(_Reason, StateName, StateData) ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
catch send_trailer(StateData),
|
||||
(StateData#state.sockmod):close(StateData#state.socket),
|
||||
ok.
|
||||
|
||||
@@ -1913,6 +1896,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} ->
|
||||
@@ -2278,30 +2265,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};
|
||||
@@ -2368,15 +2357,16 @@ process_unauthenticated_stanza(StateData, El) ->
|
||||
_ -> 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,
|
||||
<<"">>),
|
||||
@@ -2422,7 +2412,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);
|
||||
@@ -2882,6 +2871,7 @@ handle_unacked_stanzas(StateData)
|
||||
false
|
||||
end
|
||||
end,
|
||||
Lang = StateData#state.lang,
|
||||
ReRoute = case ResendOnTimeout of
|
||||
true ->
|
||||
fun(From, To, El, Time) ->
|
||||
@@ -2890,9 +2880,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,
|
||||
@@ -2900,7 +2892,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
|
||||
@@ -3126,6 +3120,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;
|
||||
|
||||
+221
-101
@@ -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].
|
||||
list_commands(?DEFAULT_VERSION).
|
||||
|
||||
-spec list_commands_policy() -> [{atom(), [aterm()], string(), atom()}].
|
||||
-spec list_commands(integer()) -> [{atom(), [aterm()], string()}].
|
||||
|
||||
%% @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].
|
||||
%% @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 get_command_format(atom()) -> {[aterm()], rterm()} | {error, command_unknown}.
|
||||
|
||||
-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, 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).
|
||||
|
||||
get_command_format(Name, Auth) ->
|
||||
-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}
|
||||
%% @doc Execute a command.
|
||||
execute_command(Name, Arguments) ->
|
||||
execute_command([], noauth, Name, Arguments).
|
||||
-spec get_commands_definition(integer()) -> [ejabberd_commands()].
|
||||
|
||||
-spec execute_command([{atom(), [atom()], [any()]}],
|
||||
{binary(), binary(), binary(), boolean()} |
|
||||
noauth | admin,
|
||||
atom(),
|
||||
[any()]
|
||||
% @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(Name, Arguments, ?DEFAULT_VERSION).
|
||||
|
||||
-spec execute_command(atom(),
|
||||
[any()],
|
||||
integer() |
|
||||
{binary(), binary(), binary(), boolean()} |
|
||||
noauth | admin
|
||||
) -> any().
|
||||
|
||||
%% @spec (AccessCommands, Auth, Name::atom(), Arguments) -> ResultTerm | {error, Error}
|
||||
%% @spec (Name::atom(), Arguments, integer() | Auth) -> ResultTerm
|
||||
%% where
|
||||
%% AccessCommands = [{Access, CommandNames, Arguments}]
|
||||
%% 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
|
||||
%% 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
|
||||
%% 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()],
|
||||
integer()
|
||||
) -> any().
|
||||
|
||||
%% @spec (AccessCommands, Auth, Name::atom(), Arguments, integer()) -> ResultTerm
|
||||
%% where
|
||||
%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
|
||||
%% 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(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;
|
||||
|
||||
+172
-53
@@ -34,10 +34,12 @@
|
||||
get_vh_by_auth_method/1, is_file_readable/1,
|
||||
get_version/0, get_myhosts/0, get_mylang/0,
|
||||
prepare_opt_val/4, convert_table_to_binary/5,
|
||||
transform_options/1, collect_options/1,
|
||||
convert_to_yaml/1, convert_to_yaml/2,
|
||||
transform_options/1, collect_options/1, default_db/2,
|
||||
convert_to_yaml/1, convert_to_yaml/2, v_db/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,8 +54,48 @@
|
||||
|
||||
%% @type macro_value() = term().
|
||||
|
||||
|
||||
start() ->
|
||||
mnesia_init(),
|
||||
Config = get_ejabberd_config_path(),
|
||||
State0 = read_file(Config),
|
||||
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
|
||||
nocookie ->
|
||||
p1_sha:sha(randoms:get_string());
|
||||
Cookie ->
|
||||
p1_sha:sha(jlib:atom_to_binary(Cookie))
|
||||
end,
|
||||
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);
|
||||
@@ -64,21 +106,7 @@ start() ->
|
||||
[{ram_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, local_config)}]),
|
||||
mnesia:add_table_copy(local_config, node(), ram_copies),
|
||||
Config = get_ejabberd_config_path(),
|
||||
State0 = read_file(Config),
|
||||
State = validate_opts(State0),
|
||||
%% This start time is used by mod_last:
|
||||
UnixTime = p1_time_compat:system_time(seconds),
|
||||
SharedKey = case erlang:get_cookie() of
|
||||
nocookie ->
|
||||
p1_sha:sha(randoms:get_string());
|
||||
Cookie ->
|
||||
p1_sha:sha(jlib:atom_to_binary(Cookie))
|
||||
end,
|
||||
State1 = set_option({node_start, global}, UnixTime, State),
|
||||
State2 = set_option({shared_key, global}, SharedKey, State1),
|
||||
set_opts(State2).
|
||||
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},
|
||||
@@ -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,42 @@ 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}, change_val(Opt, Val), State);
|
||||
{Opt, Val} when Action == append ->
|
||||
append_option({Opt, Host}, Val, State);
|
||||
append_option({rename_option(Opt), Host}, change_val(Opt, 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.
|
||||
|
||||
change_val(auth_method, Val) ->
|
||||
prepare_opt_val(auth_method, Val,
|
||||
fun(V) ->
|
||||
L = if is_list(V) -> V;
|
||||
true -> [V]
|
||||
end,
|
||||
lists:map(
|
||||
fun(odbc) -> sql;
|
||||
(internal) -> mnesia;
|
||||
(A) when is_atom(A) -> A
|
||||
end, L)
|
||||
end, [mnesia]);
|
||||
change_val(_Opt, Val) ->
|
||||
Val.
|
||||
|
||||
set_option(Opt, Val, State) ->
|
||||
State#state{opts = [#local_config{key = Opt, value = Val} |
|
||||
State#state.opts]}.
|
||||
@@ -694,13 +753,11 @@ prepare_opt_val(Opt, Val, F, Default) ->
|
||||
end,
|
||||
case Res of
|
||||
{'EXIT', _} ->
|
||||
?INFO_MSG("Configuration problem:~n"
|
||||
"** Option: ~s~n"
|
||||
"** Invalid value: ~s~n"
|
||||
"** Using as fallback: ~s",
|
||||
[format_term(Opt),
|
||||
format_term(Val),
|
||||
format_term(Default)]),
|
||||
?WARNING_MSG("incorrect value '~s' of option '~s', "
|
||||
"using '~s' as fallback",
|
||||
[format_term(Val),
|
||||
format_term(Opt),
|
||||
format_term(Default)]),
|
||||
Default;
|
||||
_ ->
|
||||
Res
|
||||
@@ -756,9 +813,57 @@ get_option(Opt, F, Default) ->
|
||||
end
|
||||
end.
|
||||
|
||||
init_module_db_table(Modules) ->
|
||||
catch ets:new(module_db, [named_table, public, bag]),
|
||||
%% Dirty hack for mod_pubsub
|
||||
ets:insert(module_db, {mod_pubsub, mnesia}),
|
||||
ets:insert(module_db, {mod_pubsub, sql}),
|
||||
lists:foreach(
|
||||
fun(M) ->
|
||||
case re:split(atom_to_list(M), "_", [{return, list}]) of
|
||||
[_] ->
|
||||
ok;
|
||||
Parts ->
|
||||
[Suffix|T] = lists:reverse(Parts),
|
||||
BareMod = string:join(lists:reverse(T), "_"),
|
||||
ets:insert(module_db, {list_to_atom(BareMod),
|
||||
list_to_atom(Suffix)})
|
||||
end
|
||||
end, Modules).
|
||||
|
||||
-spec v_db(module(), atom()) -> atom().
|
||||
|
||||
v_db(Mod, internal) -> v_db(Mod, mnesia);
|
||||
v_db(Mod, odbc) -> v_db(Mod, sql);
|
||||
v_db(Mod, Type) ->
|
||||
case ets:match_object(module_db, {Mod, Type}) of
|
||||
[_|_] -> Type;
|
||||
[] -> erlang:error(badarg)
|
||||
end.
|
||||
|
||||
-spec default_db(binary(), module()) -> atom().
|
||||
|
||||
default_db(Host, Module) ->
|
||||
case ejabberd_config:get_option(
|
||||
{default_db, Host}, fun(T) when is_atom(T) -> T end) of
|
||||
undefined ->
|
||||
mnesia;
|
||||
DBType ->
|
||||
try
|
||||
v_db(Module, DBType)
|
||||
catch error:badarg ->
|
||||
?WARNING_MSG("Module '~s' doesn't support database '~s' "
|
||||
"defined in option 'default_db', using "
|
||||
"'mnesia' as fallback", [Module, DBType]),
|
||||
mnesia
|
||||
end
|
||||
end.
|
||||
|
||||
get_modules_with_options() ->
|
||||
{ok, Mods} = application:get_key(ejabberd, modules),
|
||||
ExtMods = [Name || {Name, _Details} <- ext_mod:installed()],
|
||||
AllMods = [?MODULE|ExtMods++Mods],
|
||||
init_module_db_table(AllMods),
|
||||
lists:foldl(
|
||||
fun(Mod, D) ->
|
||||
case catch Mod:opt_type('') of
|
||||
@@ -770,7 +875,7 @@ get_modules_with_options() ->
|
||||
{'EXIT', {undef, _}} ->
|
||||
D
|
||||
end
|
||||
end, dict:new(), [?MODULE|ExtMods++Mods]).
|
||||
end, dict:new(), AllMods).
|
||||
|
||||
validate_opts(#state{opts = Opts} = State) ->
|
||||
ModOpts = get_modules_with_options(),
|
||||
@@ -798,11 +903,25 @@ validate_opts(#state{opts = Opts} = State) ->
|
||||
|
||||
-spec get_vh_by_auth_method(atom()) -> [binary()].
|
||||
|
||||
%% Return the list of hosts handled by a given module
|
||||
%% Return the list of hosts with a given auth method
|
||||
get_vh_by_auth_method(AuthMethod) ->
|
||||
mnesia:dirty_select(local_config,
|
||||
[{#local_config{key = {auth_method, '$1'},
|
||||
value=AuthMethod},[],['$1']}]).
|
||||
Cfgs = mnesia:dirty_match_object(local_config,
|
||||
#local_config{key = {auth_method, '_'},
|
||||
_ = '_'}),
|
||||
lists:flatmap(
|
||||
fun(#local_config{key = {auth_method, Host}, value = M}) ->
|
||||
Methods = if not is_list(M) -> [M];
|
||||
true -> M
|
||||
end,
|
||||
case lists:member(AuthMethod, Methods) of
|
||||
true when Host == global ->
|
||||
get_myhosts();
|
||||
true ->
|
||||
[Host];
|
||||
false ->
|
||||
[]
|
||||
end
|
||||
end, Cfgs).
|
||||
|
||||
%% @spec (Path::string()) -> true | false
|
||||
is_file_readable(Path) ->
|
||||
@@ -836,20 +955,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 +1073,7 @@ transform_terms(Terms) ->
|
||||
mod_last,
|
||||
ejabberd_s2s,
|
||||
ejabberd_listener,
|
||||
ejabberd_odbc_sup,
|
||||
ejabberd_sql_sup,
|
||||
shaper,
|
||||
ejabberd_s2s_out,
|
||||
acl,
|
||||
|
||||
+86
-56
@@ -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).
|
||||
|
||||
process2(Args, Auth, AccessCommands) ->
|
||||
case try_run_ctp(Args, Auth, AccessCommands) of
|
||||
%% @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, 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} =
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
+10
-5
@@ -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,
|
||||
|
||||
+54
-2
@@ -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.
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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.
|
||||
@@ -80,7 +80,6 @@ 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} ->
|
||||
@@ -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),
|
||||
@@ -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
|
||||
@@ -576,8 +502,6 @@ 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(
|
||||
@@ -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,7 +547,6 @@ 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 = fxml:get_attr_s(<<"from">>, El#xmlel.attrs),
|
||||
case jid:from_string(FromS) of
|
||||
@@ -643,7 +563,6 @@ 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 = fxml:get_attr_s(<<"from">>, El#xmlel.attrs),
|
||||
case jid:from_string(FromS) of
|
||||
|
||||
+17
-17
@@ -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].
|
||||
|
||||
@@ -141,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);
|
||||
|
||||
+17
-8
@@ -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;
|
||||
|
||||
@@ -70,8 +70,8 @@ is_riak_configured(Host) ->
|
||||
{modules, Host},
|
||||
fun(L) when is_list(L) -> L end, []),
|
||||
ModuleWithRiakDBConfigured = lists:any(
|
||||
fun({_Module, Opts}) ->
|
||||
gen_mod:db_type(Host, Opts) == riak
|
||||
fun({Module, Opts}) ->
|
||||
gen_mod:db_type(Host, Opts, Module) == riak
|
||||
end, Modules),
|
||||
ServerConfigured or PortConfigured
|
||||
or AuthConfigured or ModuleWithRiakDBConfigured.
|
||||
@@ -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].
|
||||
|
||||
+67
-42
@@ -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, {}).
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -312,8 +312,10 @@ do_route(From, To, Packet) ->
|
||||
<<"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
|
||||
|
||||
@@ -325,7 +325,7 @@ 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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)),
|
||||
@@ -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,
|
||||
@@ -360,7 +362,9 @@ handle_info({route, From, To, Packet}, StateName,
|
||||
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};
|
||||
|
||||
+33
-24
@@ -35,6 +35,7 @@
|
||||
-export([start/0,
|
||||
start_link/0,
|
||||
route/3,
|
||||
process_iq/3,
|
||||
open_session/5,
|
||||
open_session/6,
|
||||
close_session/4,
|
||||
@@ -65,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,
|
||||
@@ -158,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.
|
||||
|
||||
@@ -422,6 +426,7 @@ 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
|
||||
@@ -495,8 +500,9 @@ do_route(From, To, #xmlel{} = Packet) ->
|
||||
<<"headline">> -> route_message(From, To, Packet, headline);
|
||||
<<"error">> -> ok;
|
||||
<<"groupchat">> ->
|
||||
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);
|
||||
_ ->
|
||||
route_message(From, To, Packet, normal)
|
||||
@@ -514,10 +520,13 @@ do_route(From, To, #xmlel{} = Packet) ->
|
||||
<<"chat">> -> route_message(From, To, Packet, chat);
|
||||
<<"normal">> -> route_message(From, To, Packet, normal);
|
||||
<<"">> -> route_message(From, To, Packet, normal);
|
||||
<<"headline">> -> ok;
|
||||
<<"error">> -> 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;
|
||||
<<"iq">> ->
|
||||
@@ -525,8 +534,10 @@ do_route(From, To, #xmlel{} = Packet) ->
|
||||
<<"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", [])
|
||||
@@ -683,7 +694,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}] ->
|
||||
@@ -696,8 +707,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;
|
||||
@@ -720,12 +733,10 @@ force_update_presence({LUser, LServer}) ->
|
||||
-spec get_sm_backend(binary()) -> module().
|
||||
|
||||
get_sm_backend(Host) ->
|
||||
DBType = ejabberd_config:get_option({sm_db_type, Host},
|
||||
fun(mnesia) -> mnesia;
|
||||
(internal) -> mnesia;
|
||||
(odbc) -> odbc;
|
||||
(redis) -> redis
|
||||
end, mnesia),
|
||||
DBType = ejabberd_config:get_option(
|
||||
{sm_db_type, Host},
|
||||
fun(T) -> ejabberd_config:v_db(?MODULE, T) end,
|
||||
mnesia),
|
||||
list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)).
|
||||
|
||||
-spec get_sm_backends() -> [module()].
|
||||
@@ -795,10 +806,8 @@ kick_user(User, Server) ->
|
||||
end, Resources),
|
||||
length(Resources).
|
||||
|
||||
opt_type(sm_db_type) ->
|
||||
fun (mnesia) -> mnesia;
|
||||
(internal) -> mnesia;
|
||||
(odbc) -> odbc;
|
||||
(redis) -> redis
|
||||
end;
|
||||
make_sid() ->
|
||||
{p1_time_compat:unique_timestamp(), self()}.
|
||||
|
||||
opt_type(sm_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
opt_type(_) -> [sm_db_type].
|
||||
|
||||
@@ -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;
|
||||
@@ -47,14 +47,14 @@ init() ->
|
||||
|
||||
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)};
|
||||
@@ -93,7 +93,7 @@ get_sessions() ->
|
||||
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,
|
||||
@@ -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].
|
||||
@@ -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).
|
||||
@@ -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].
|
||||
@@ -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(
|
||||
|
||||
@@ -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, <<"">>))
|
||||
@@ -1520,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) ->
|
||||
@@ -2415,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,
|
||||
|
||||
@@ -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)}]};
|
||||
|
||||
@@ -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) ->
|
||||
@@ -90,7 +104,7 @@ import_file(Server, FileName) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
Mods = [{Mod, gen_mod:db_type(LServer, Mod)}
|
||||
|| Mod <- modules(), gen_mod:is_loaded(LServer, Mod)],
|
||||
AuthMods = case lists:member(ejabberd_auth_internal,
|
||||
AuthMods = case lists:member(ejabberd_auth_mnesia,
|
||||
ejabberd_auth:auth_modules(LServer)) of
|
||||
true ->
|
||||
[{ejabberd_auth, mnesia}];
|
||||
@@ -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);
|
||||
@@ -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.
|
||||
+2
-3
@@ -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;
|
||||
|
||||
+82
-32
@@ -31,11 +31,12 @@
|
||||
|
||||
-export([start/0, start_module/2, start_module/3,
|
||||
stop_module/2, stop_module_keep_config/2, get_opt/3,
|
||||
get_opt/4, get_opt_host/3, db_type/1, db_type/2,
|
||||
get_opt/4, get_opt_host/3, db_type/2, db_type/3,
|
||||
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,
|
||||
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) ->
|
||||
@@ -262,29 +295,46 @@ validate_opts(Module, Opts) ->
|
||||
false
|
||||
end, Opts).
|
||||
|
||||
-spec v_db(db_type() | internal) -> db_type().
|
||||
|
||||
v_db(odbc) -> odbc;
|
||||
v_db(internal) -> mnesia;
|
||||
v_db(mnesia) -> mnesia;
|
||||
v_db(riak) -> riak.
|
||||
|
||||
-spec db_type(opts()) -> db_type().
|
||||
|
||||
db_type(Opts) ->
|
||||
db_type(global, Opts).
|
||||
|
||||
-spec db_type(binary() | global, atom() | opts()) -> db_type().
|
||||
-spec db_type(binary() | global, module()) -> db_type();
|
||||
(opts(), module()) -> db_type().
|
||||
|
||||
db_type(Opts, Module) when is_list(Opts) ->
|
||||
db_type(global, Opts, Module);
|
||||
db_type(Host, Module) when is_atom(Module) ->
|
||||
get_module_opt(Host, Module, db_type, fun v_db/1, default_db(Host));
|
||||
db_type(Host, Opts) when is_list(Opts) ->
|
||||
get_opt(db_type, Opts, fun v_db/1, default_db(Host)).
|
||||
case Module:mod_opt_type(db_type) of
|
||||
F when is_function(F) ->
|
||||
case get_module_opt(Host, Module, db_type, F) of
|
||||
undefined -> ejabberd_config:default_db(Host, Module);
|
||||
Type -> Type
|
||||
end;
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
-spec default_db(binary() | global) -> db_type().
|
||||
-spec db_type(binary(), opts(), module()) -> db_type().
|
||||
|
||||
default_db(Host) ->
|
||||
ejabberd_config:get_option({default_db, Host}, fun v_db/1, mnesia).
|
||||
db_type(Host, Opts, Module) ->
|
||||
case Module:mod_opt_type(db_type) of
|
||||
F when is_function(F) ->
|
||||
case get_opt(db_type, Opts, F) of
|
||||
undefined -> ejabberd_config:default_db(Host, Module);
|
||||
Type -> Type
|
||||
end;
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
-spec db_mod(binary() | global | db_type(), module()) -> module().
|
||||
|
||||
db_mod(Type, Module) when is_atom(Type) ->
|
||||
list_to_atom(atom_to_list(Module) ++ "_" ++ atom_to_list(Type));
|
||||
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), Module).
|
||||
|
||||
-spec loaded_modules(binary()) -> [atom()].
|
||||
|
||||
@@ -332,6 +382,6 @@ get_module_proc(Host, Base) ->
|
||||
is_loaded(Host, Module) ->
|
||||
ets:member(ejabberd_modules, {Module, Host}).
|
||||
|
||||
opt_type(default_db) -> fun v_db/1;
|
||||
opt_type(default_db) -> fun(T) when is_atom(T) -> T end;
|
||||
opt_type(modules) -> fun (L) when is_list(L) -> L end;
|
||||
opt_type(_) -> [default_db, modules].
|
||||
|
||||
+7
-3
@@ -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,
|
||||
|
||||
+5
-16
@@ -530,22 +530,11 @@ rsm_encode_count(Count, Arr) ->
|
||||
|
||||
-spec is_standalone_chat_state(xmlel()) -> boolean().
|
||||
|
||||
is_standalone_chat_state(#xmlel{name = <<"message">>} = El) ->
|
||||
ChatStates = [<<"active">>, <<"inactive">>, <<"gone">>, <<"composing">>,
|
||||
<<"paused">>],
|
||||
Stripped =
|
||||
lists:foldl(fun(ChatState, AccEl) ->
|
||||
fxml:remove_subtags(AccEl, ChatState,
|
||||
{<<"xmlns">>, ?NS_CHATSTATES})
|
||||
end, El, ChatStates),
|
||||
case Stripped of
|
||||
#xmlel{children = [#xmlel{name = <<"thread">>}]} ->
|
||||
true;
|
||||
#xmlel{children = []} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
is_standalone_chat_state(#xmlel{name = <<"message">>, children = Els}) ->
|
||||
Stripped = [El || #xmlel{name = Name, attrs = Attrs} = El <- Els,
|
||||
fxml:get_attr_s(<<"xmlns">>, Attrs) /= ?NS_CHATSTATES,
|
||||
Name /= <<"thread">>],
|
||||
Stripped == [];
|
||||
is_standalone_chat_state(_El) -> false.
|
||||
|
||||
-spec add_delay_info(xmlel(), jid() | ljid() | binary(), erlang:timestamp())
|
||||
|
||||
+6
-3
@@ -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.
|
||||
|
||||
|
||||
+42
-39
@@ -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,
|
||||
@@ -162,7 +162,7 @@ get_commands_spec() ->
|
||||
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}],
|
||||
args_example = [<<"peter">>, <<"myserver.com">>, <<"secret">>],
|
||||
args_desc = ["User name to check", "Server to check", "Password to check"],
|
||||
@@ -531,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],
|
||||
@@ -590,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).
|
||||
@@ -748,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),
|
||||
@@ -796,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) ->
|
||||
@@ -861,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).
|
||||
|
||||
@@ -1193,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">>,
|
||||
@@ -1322,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)];
|
||||
@@ -1582,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
|
||||
|
||||
+103
-328
@@ -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(fxml: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,112 +843,30 @@ 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.
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:delete_motd(LServer).
|
||||
|
||||
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;
|
||||
_ ->
|
||||
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 fxml_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} ->
|
||||
{fxml:get_subtag_cdata(Packet, <<"subject">>),
|
||||
fxml:get_subtag_cdata(Packet, <<"body">>)};
|
||||
@@ -992,34 +874,6 @@ get_stored_motd(LServer) ->
|
||||
{<<>>, <<>>}
|
||||
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 fxml_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,98 +907,19 @@ 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 = 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.
|
||||
|
||||
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(fxml: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 = 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}].
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:import(LServer).
|
||||
|
||||
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.
|
||||
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;
|
||||
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(_) -> [access, db_type].
|
||||
|
||||
@@ -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.
|
||||
@@ -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{}}.
|
||||
@@ -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
|
||||
%%%===================================================================
|
||||
+36
-237
@@ -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, fxml:remove_cdata(SubEls)} of
|
||||
{<<"block">>, []} -> {error, ?ERR_BAD_REQUEST};
|
||||
{<<"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.
|
||||
@@ -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].
|
||||
|
||||
@@ -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
|
||||
%%%===================================================================
|
||||
@@ -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
|
||||
%%%===================================================================
|
||||
@@ -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
|
||||
%%%===================================================================
|
||||
+20
-126
@@ -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,
|
||||
@@ -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),
|
||||
@@ -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, <<"">>),
|
||||
@@ -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)).
|
||||
@@ -757,6 +651,6 @@ 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(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(_) ->
|
||||
[cache_life_time, cache_size, db_type].
|
||||
|
||||
@@ -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.
|
||||
@@ -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{}}.
|
||||
@@ -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]].
|
||||
|
||||
+23
-33
@@ -43,12 +43,11 @@
|
||||
-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
|
||||
@@ -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).
|
||||
@@ -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
|
||||
@@ -286,8 +275,9 @@ has_non_empty_body(Packet) ->
|
||||
|
||||
%% 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(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(_) -> [db_type, iqdisc].
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user