Compare commits
432 Commits
obsolete-m
...
master
Author | SHA1 | Date |
---|---|---|
jacob.eva | 2d2ea29a20 | |
Badlop | 7ec5336a4a | |
Badlop | f4ea11b0b6 | |
Badlop | 32451219ed | |
Badlop | b0b62f509c | |
Badlop | 0bccf45c0e | |
Badlop | dcc59aef75 | |
Badlop | 323b53d125 | |
Badlop | b607d97331 | |
Paweł Chmielowski | 8a91598b04 | |
Badlop | 4106ae484c | |
Badlop | 0db49856b8 | |
Badlop | 232fc5e31f | |
Badlop | 26e7ce3003 | |
Badlop | bc43e7e750 | |
Badlop | 1469e7c76f | |
Badlop | a37f44ed20 | |
Badlop | f2497c3146 | |
badlop | 7e11adc32a | |
Roman Hargrave | 1a54cf7e52 | |
Roman Hargrave | 961060a284 | |
Roman Hargrave | 5c7ec06224 | |
Roman Hargrave | 7c58c7aa0a | |
Badlop | d536707e4f | |
Roman Hargrave | 83690375cb | |
Roman Hargrave | a91e9fa276 | |
badlop | bad48b0d6a | |
Mujtaba Roohani | eea2a5d257 | |
Mujtaba Roohani | 10ffbc08df | |
Mujtaba Roohani | af928e35cb | |
Badlop | 037c3749f3 | |
Badlop | 35d05f2925 | |
Badlop | 6adfc2cec5 | |
Badlop | 90d9ef3a5d | |
Badlop | f48adc2ea0 | |
Badlop | 3293ba1cfe | |
Badlop | 5b1402f033 | |
Badlop | 12e31364f6 | |
Badlop | f3e03b9729 | |
Badlop | 2bf5a362ce | |
Badlop | b71cbeb48a | |
Badlop | 241df9ac97 | |
Badlop | 8eb58060f8 | |
Badlop | b3c8d25104 | |
Badlop | 04f4489427 | |
Badlop | 7d4e1ad933 | |
Badlop | d2557111af | |
Badlop | 813a93faff | |
Badlop | fff94c83e7 | |
Badlop | 6be46dacc9 | |
Badlop | b96efd7df2 | |
Badlop | 6062f6a614 | |
Badlop | c7e8208879 | |
Badlop | 2ee0a514a5 | |
Badlop | e76b0877d6 | |
Badlop | 8499366bcc | |
Badlop | cc4702882f | |
Badlop | 10b1a83bbf | |
Badlop | dd4d5e698a | |
Badlop | 8fd6fabdbb | |
Badlop | 6a4618d3d3 | |
Badlop | f672262950 | |
Badlop | 9ac123ff56 | |
Badlop | 2612f1ead1 | |
Badlop | 7e2fe1b789 | |
Badlop | 3d523cec45 | |
Badlop | 03cd4de6b0 | |
Badlop | 3b116414db | |
Mickaël Rémond | c2efdc1950 | |
Badlop | a41df9c914 | |
Badlop | 0da3afbeeb | |
Badlop | c9084fae66 | |
Badlop | 4aa09c552c | |
Badlop | f72aadd88d | |
Badlop | 622233eaf6 | |
Badlop | 97d796cb00 | |
Badlop | a95e657a21 | |
Badlop | 664531f1ef | |
Badlop | 80bae05721 | |
Badlop | 5b44231a37 | |
Badlop | 2e60ec7079 | |
Badlop | 1096159be1 | |
Badlop | 37e44168f6 | |
Badlop | eac6bb36de | |
Badlop | dcf8189cb0 | |
Badlop | 65a37864ff | |
Badlop | 4be0998c6e | |
Badlop | f2b3151f0b | |
Badlop | f51ac06ecd | |
Badlop | c031269003 | |
Badlop | f8df7b0a60 | |
badlop | bf08312336 | |
badlop | 3374984b28 | |
Badlop | 00e3fa97fc | |
Badlop | 2aea4659cc | |
Badlop | 4cde5d84f2 | |
Badlop | 5e6eb2738a | |
Badlop | a6b3ceccbf | |
Badlop | f46182b238 | |
badlop | dd4c264b33 | |
Sonny Piers | d7d054cf05 | |
Badlop | cfea844cec | |
Badlop | 21d78a74c1 | |
Badlop | 05a3c87239 | |
Badlop | 68c8116102 | |
Badlop | 5fdcb0a6e3 | |
Badlop | 6220d58e43 | |
Badlop | 2fcf924747 | |
Badlop | 9d51b80051 | |
Badlop | f32bb792bc | |
Badlop | 0ab2007a3a | |
Evgeny Khramtsov | 75129ec0bc | |
Holger Weiss | 20fb74c363 | |
Badlop | 20ba631f3e | |
Badlop | b7222c8828 | |
Badlop | b8c42a4766 | |
Badlop | 857d350a71 | |
Badlop | 4753268b5f | |
Badlop | a4465fbeaa | |
Holger Weiss | f2faca6dc9 | |
Holger Weiss | b410f64f33 | |
Holger Weiss | 7c1b83941c | |
Holger Weiss | 2a7ae832a8 | |
Holger Weiss | 4af4244975 | |
Evgeny Khramtsov | 9404980f0d | |
Badlop | 0db6868aa6 | |
Mickaël Rémond | fc2dfb0f1d | |
Holger Weiss | 48f5cc3b88 | |
Holger Weiss | 6bf9a45c93 | |
Holger Weiss | 4cb537c693 | |
Holger Weiss | 5f612b7dab | |
Holger Weiss | 694b45474c | |
Badlop | b07766ab93 | |
Holger Weiss | 689f526cea | |
Holger Weiss | 34bf1a7d6c | |
Holger Weiss | 0bf94ba446 | |
Holger Weiss | 92fadcdf4e | |
Holger Weiss | fe121057f2 | |
Holger Weiss | cb0a14c89a | |
Holger Weiss | 4476c95f14 | |
Holger Weiss | a0a9aaa1e0 | |
Holger Weiss | 4e15dcbd7d | |
Holger Weiss | 6fc4fccfbb | |
Holger Weiss | 362c866862 | |
Holger Weiss | e26ea15d7f | |
Licaon_Kter | 86f90153c4 | |
Holger Weiss | c1591030b0 | |
Holger Weiss | 4787e890af | |
Holger Weiss | 05a5f002a3 | |
Holger Weiss | e6249c41e2 | |
Holger Weiss | 6a877d7018 | |
Holger Weiss | b5daceaac2 | |
Holger Weiss | f459a7e57c | |
Holger Weiss | 8f63b2cbae | |
Holger Weiss | 83117240d2 | |
Holger Weiss | d60fa5e9b3 | |
Badlop | 009d25f0bd | |
Badlop | bae1cdc52e | |
Badlop | d1066f6644 | |
Badlop | ebfaf70df4 | |
Badlop | eb39a4f3af | |
Badlop | 2be66bb69f | |
Badlop | 5c94639bf2 | |
Holger Weiss | cfad261cf4 | |
Badlop | 774cc46481 | |
Badlop | 18be432fdf | |
Badlop | 1fcf7b5cb1 | |
Badlop | a43bc6f794 | |
Badlop | 1a6284efa9 | |
Badlop | 95ef06b768 | |
Holger Weiss | 954fb94017 | |
Badlop | 4ebf99a1d1 | |
Badlop | 97de583107 | |
Badlop | 8978f38c22 | |
Badlop | 2627182cdb | |
Badlop | b130567e16 | |
Badlop | f20d6239db | |
Holger Weiss | b7b6af8315 | |
Badlop | 7191ddd6f3 | |
Badlop | e138359c7f | |
Evgeny Khramtsov | 562fb1640d | |
ytkang | 8045f4c60e | |
Paweł Chmielowski | 017ce87198 | |
Badlop | 06dbe80c65 | |
Evgeniy Khramtsov | acc32abf17 | |
Evgeniy Khramtsov | de4e42c20d | |
Evgeniy Khramtsov | 65575478a3 | |
Badlop | 721ca125ea | |
Holger Weiss | cd54f70943 | |
Holger Weiss | fa5c601325 | |
Holger Weiss | d37bb1cd2d | |
Holger Weiss | 435d0bcc3d | |
Holger Weiss | 9c3943b703 | |
Holger Weiss | 450f2eb5f4 | |
Holger Weiss | ef7c92233d | |
Holger Weiss | 79a804ac3b | |
Holger Weiss | 7d2a7aa418 | |
Holger Weiss | 89c5bc0546 | |
Holger Weiss | d783cc7cca | |
Holger Weiss | d84204bdb8 | |
Badlop | 494ba6f941 | |
Badlop | 9fe5f383dc | |
Badlop | fc40a38780 | |
Badlop | 9a14b7f7f7 | |
Badlop | 74e068d91c | |
Badlop | aa6519209e | |
Badlop | 4d3acc1637 | |
Badlop | e99866d835 | |
Badlop | babc6213f9 | |
Badlop | 156331c123 | |
Evgeniy Khramtsov | 6a4b9a08d5 | |
Badlop | 6780874893 | |
badlop | 91b9bd9973 | |
Tom Quackenbush | 8e1263c328 | |
Badlop | ec3c13cade | |
Badlop | 18d1a5d60d | |
Badlop | 2a95a53285 | |
Badlop | ce89412feb | |
Badlop | 00e76aec2c | |
Badlop | bd57bdb3ae | |
Badlop | 5359c2a0dd | |
Evgeny Khramtsov | 008a8bc70a | |
Abdulrhman A Alkhodiry | 3d6901efe2 | |
Badlop | 511f30a751 | |
Badlop | 36644dc78c | |
Badlop | 1f992d161a | |
Badlop | e93af70b57 | |
Badlop | fc901d9dea | |
Evgeniy Khramtsov | 3dc4494ee1 | |
badlop | da878bd75c | |
Badlop | df79a848f6 | |
Tom Quackenbush | 3faa47df45 | |
Tom Quackenbush | 78bb06c6aa | |
Tom Quackenbush | 707321e0e0 | |
Tom Quackenbush | a190156d0a | |
Tom Quackenbush | 83b32e7e4f | |
Tom Quackenbush | 5662e1c530 | |
Badlop | 4013a68554 | |
Evgeny Khramtsov | b63aa9c326 | |
Jonathan Gueron | ebd2671963 | |
Badlop | 65f4267fc1 | |
Tom Quackenbush | 29b4b5ae30 | |
Tom Quackenbush | 06c96ad778 | |
Tom Quackenbush | 9359ce3b75 | |
Tom Quackenbush | 610c0e72eb | |
Tom Quackenbush | 265ff3dc70 | |
Badlop | 41371cf7fe | |
Badlop | 065ab08592 | |
badlop | d3f0218dc4 | |
xmppjingle | 0aa5516e04 | |
badlop | d7513f5a00 | |
Tom Quackenbush | 612b2b78e9 | |
xmppjingle | ed3853ccf9 | |
xmppjingle | 098a810843 | |
xmppjingle | e2ebd1a405 | |
badlop | b8ed8af10e | |
badlop | f52b98c068 | |
Badlop | f806442b9c | |
Tom Quackenbush | 779024b9c5 | |
Tom Quackenbush | 3a03dde3c0 | |
Mark Tran | 9eefebadc2 | |
Tom Quackenbush | 3a3a3f1db1 | |
Tom Quackenbush | 1b86f7ccaa | |
badlop | bb8802a70a | |
Tom Quackenbush | 6c22e0fa0c | |
Badlop | d7ce81cd22 | |
Badlop | 0b5ad7a994 | |
Holger Weiss | 04d2234e0a | |
Badlop | 7429577b7a | |
Badlop | 9374ed0ffc | |
Badlop | b45fd21340 | |
Badlop | 5fc2b410a2 | |
Paweł Chmielowski | 7f75aa0372 | |
Holger Weiss | 32fd4c4d9c | |
Holger Weiss | 02759c6aae | |
Holger Weiss | 39cf5a0fd7 | |
Holger Weiss | 512255d98b | |
Holger Weiss | c5daf56385 | |
Holger Weiss | 04cc600772 | |
Holger Weiss | 4165428780 | |
Holger Weiss | b7414f8837 | |
Holger Weiss | c9039a5ad2 | |
badlop | 8c1df80794 | |
Holger Weiss | 44ffde7eda | |
Andrew B | e2939a0353 | |
Mickaël Rémond | e9a014a6cb | |
Badlop | 8c86849e86 | |
Holger Weiss | d73b648cf3 | |
Matthias Rieber | 4deb2fd2c3 | |
Holger Weiss | 1696107f43 | |
Holger Weiss | d4d8b15413 | |
Holger Weiss | 99d89ce59d | |
Holger Weiss | ce64f79b63 | |
Holger Weiss | ddf70d60df | |
Holger Weiss | f2f8fe25f7 | |
Holger Weiss | b6f5273228 | |
Holger Weiss | ea4964dccd | |
Holger Weiss | 0a24936e92 | |
Holger Weiss | 0cfd521b7a | |
Holger Weiss | 8c470c6a01 | |
Holger Weiss | 73e945fd1d | |
Holger Weiss | 1ee63657ac | |
Holger Weiss | 9cd4e405c8 | |
Holger Weiss | 5359525d3c | |
Holger Weiss | d46ee127e7 | |
Holger Weiss | 0b4e0e720e | |
Holger Weiss | 08f7291a24 | |
Holger Weiss | d29795fb24 | |
Holger Weiss | f6b66cd130 | |
Badlop | 0d2fa84c8a | |
Paweł Chmielowski | ecf8db140d | |
Kévin Dunglas | 5ccefb0e9e | |
Leon-SFS | 55f08f9b43 | |
Holger Weiss | 195daf9fa1 | |
Holger Weiss | 567e19728c | |
Holger Weiss | 1ee52e2e42 | |
Holger Weiss | c173ae9f36 | |
Holger Weiss | 4b0b66cfbf | |
Holger Weiss | 9337b3ea39 | |
Holger Weiss | dd34fc63c8 | |
Holger Weiss | 1ff5cbc467 | |
Holger Weiss | 4936e82bd1 | |
Holger Weiss | eeba72cd4c | |
Holger Weiss | 655f195e9d | |
Holger Weiss | 3652dd1796 | |
Holger Weiss | b88e766571 | |
Holger Weiss | d4d5200c5b | |
Holger Weiss | e5eef02b25 | |
Holger Weiss | c37387984a | |
Holger Weiss | 511f0fa03d | |
Holger Weiss | 157fd94c32 | |
Holger Weiss | 7c61eef522 | |
Holger Weiss | 8a849069ec | |
Holger Weiss | 14c3b13a11 | |
Badlop | 5964ad412e | |
Holger Weiss | 6dc35e8fcb | |
badlop | 9aa4cd41fe | |
Badlop | 0451576fcb | |
Badlop | 0cade69713 | |
Holger Weiss | a1fe316565 | |
Holger Weiss | f58db83523 | |
Holger Weiss | a36515d74d | |
Holger Weiss | de035cefff | |
Michael Ansel | e2a85ef9ad | |
Badlop | ce4c11f5fe | |
Badlop | 2cfe253b9b | |
Badlop | e5d66210f0 | |
Holger Weiss | 6ca6f021cc | |
Holger Weiss | 645d29a264 | |
Holger Weiss | 5142174b98 | |
Holger Weiss | a6c7dc9b8a | |
Holger Weiss | f3bf402e7a | |
Badlop | 31f19239d7 | |
Holger Weiss | c12e225e88 | |
Badlop | 3e4f4baeca | |
Holger Weiss | 96adf84c6f | |
Holger Weiss | 9eb68b4ef3 | |
Holger Weiss | 10092e3bd5 | |
Holger Weiss | d45ec798ee | |
Holger Weiss | 12274a2adc | |
Holger Weiss | 19044ccb85 | |
Holger Weiss | 06def469a4 | |
Holger Weiss | 81e8404207 | |
Holger Weiss | 11fc485a9c | |
Holger Weiss | f7beaa3ef1 | |
Holger Weiss | 958b428ff0 | |
Holger Weiss | a00090086e | |
Holger Weiss | 374c1b6dfc | |
Holger Weiss | 13e8c857ce | |
Holger Weiss | 6db50940a1 | |
Holger Weiss | f4950cdc83 | |
Holger Weiss | b9ece5c953 | |
Holger Weiss | a17a554a8c | |
Holger Weiss | 216e244b6a | |
Holger Weiss | 034a156066 | |
Holger Weiss | 69ec20b3b9 | |
Holger Weiss | 6316b0f097 | |
Holger Weiss | 26e45fa0b0 | |
Badlop | 21ffae911c | |
Holger Weiss | 941ed7c21a | |
Badlop | 5d6f6b820f | |
Badlop | 7d0f6caa33 | |
Badlop | 25a63c6ed1 | |
Badlop | 4e5fac304a | |
Holger Weiss | 7cba0f88e8 | |
Holger Weiss | 1c79c86168 | |
Holger Weiss | 638019ec58 | |
Holger Weiss | fb98a62bce | |
Badlop | 680d541efa | |
Holger Weiss | 16edbd5e7a | |
badlop | 2a953da2fb | |
Badlop | 6d655af0c4 | |
Badlop | db91c97614 | |
Badlop | ff61b1a2e1 | |
Holger Weiss | 33ec5968d7 | |
Holger Weiss | 32043a80eb | |
Holger Weiss | 94a439726b | |
Holger Weiss | 00267eeb71 | |
Holger Weiss | 746d198cc3 | |
Holger Weiss | 5363043402 | |
Holger Weiss | 564691c7a6 | |
Holger Weiss | 932987447d | |
Holger Weiss | 76c6d17d4b | |
Badlop | 89e0a70e44 | |
Badlop | 98ab39f962 | |
badlop | be350628c7 | |
Santiago26 | 2852dee33f | |
Christophe Romain | 9eca69d092 | |
Christophe Romain | e5336f02db | |
Badlop | 97834d2483 | |
Badlop | c8dca7f0a5 | |
Holger Weiss | eb52291ca1 | |
Holger Weiss | 73b984f193 | |
Christophe Romain | 362a896e6a | |
Christophe Romain | 6f7b5ccba0 | |
Badlop | fa47490da4 | |
Badlop | c098a763b0 | |
Holger Weiss | 176d2e68ca | |
Badlop | 5ab060ef72 | |
Badlop | 65007779f7 | |
Badlop | 6e150caa5e | |
Paweł Chmielowski | 5a32309eaa | |
Badlop | c30e7d7f7b | |
Badlop | 9379f73feb | |
Mickaël Rémond | cbea31ab68 | |
Christophe Romain | e2f860fd9f | |
Christophe Romain | a20187ed70 | |
Christophe Romain | 9330892787 | |
Badlop | f4fb0d7c7a | |
Christophe Romain | 416e203e66 | |
Christophe Romain | f5677e7033 | |
Christophe Romain | 4c68f4421b |
|
@ -0,0 +1,162 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.spec'
|
||||
- '**.txt'
|
||||
- '*/conf/*.yml'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.spec'
|
||||
- '**.txt'
|
||||
- '*/conf/*.yml'
|
||||
|
||||
jobs:
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
otp: ['21.3', '25.3']
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: erlang:${{ matrix.otp }}
|
||||
|
||||
steps:
|
||||
|
||||
- name: Checkout ejabberd
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: processone/ejabberd
|
||||
|
||||
- name: Checkout ejabberd-contrib
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: .ejabberd-modules/sources/ejabberd-contrib
|
||||
|
||||
- name: Get a compatible Rebar3
|
||||
if: matrix.otp <= '21.3'
|
||||
run: |
|
||||
rm rebar3
|
||||
wget https://github.com/processone/ejabberd/raw/21.12/rebar3
|
||||
chmod +x rebar3
|
||||
|
||||
- name: Prepare libraries
|
||||
run: |
|
||||
apt-get -qq update
|
||||
apt-get -y purge libgd3 nginx
|
||||
apt-get -qq install libexpat1-dev libgd-dev libpam0g-dev \
|
||||
libsqlite3-dev libwebp-dev libyaml-dev
|
||||
|
||||
- name: Prepare rebar
|
||||
id: rebar
|
||||
run: |
|
||||
echo '{xref_ignores, [{eldap_filter_yecc, return_error, 2},
|
||||
{fusco_lib, split_credentials, 1},
|
||||
{http_uri, encode, 1},
|
||||
{http_uri, decode, 1}
|
||||
]}.' >>rebar.config
|
||||
echo '{xref_checks, [deprecated_function_calls, deprecated_functions,
|
||||
locals_not_used, undefined_function_calls, undefined_functions]}.
|
||||
% Disabled: exports_not_used,' >>rebar.config
|
||||
echo '{dialyzer, [{get_warnings, true}, {plt_extra_apps, [cache_tab,
|
||||
eimp, epam, esip, ezlib, fast_tls, fast_xml, fast_yaml,
|
||||
mqtree, p1_acme, p1_mysql, p1_oauth2, p1_pgsql, p1_utils, pkix,
|
||||
sqlite3, stringprep, stun, xmpp, yconf]} ]}.' >>rebar.config
|
||||
echo '{ct_extra_params, "-verbosity 20"}.' >>rebar.config
|
||||
|
||||
- name: Remove syntax_tools from release
|
||||
run: sed -i 's|, syntax_tools||g' src/ejabberd.app.src.script
|
||||
|
||||
- name: Cache rebar
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/rebar3/
|
||||
key: ${{matrix.otp}}-${{hashFiles('rebar.config')}}
|
||||
|
||||
- name: Compile
|
||||
run: |
|
||||
./autogen.sh
|
||||
./configure --with-rebar=./rebar3 \
|
||||
--prefix=/tmp/ejabberd \
|
||||
--enable-all \
|
||||
--disable-elixir \
|
||||
--disable-mssql \
|
||||
--disable-odbc
|
||||
make update
|
||||
make
|
||||
|
||||
- name: Start ejabberd
|
||||
run: |
|
||||
echo "CONTRIB_MODULES_PATH=`pwd`/.ejabberd-modules" >> ejabberdctl.cfg.example
|
||||
CTL=_build/dev/rel/ejabberd/bin/ejabberdctl
|
||||
make dev
|
||||
$CTL start
|
||||
$CTL started
|
||||
|
||||
- name: Enable mod_muc_log
|
||||
run: |
|
||||
echo ' mod_muc_log: {}' >>.ejabberd-modules/sources/ejabberd-contrib/mod_muc_log_http/conf/mod_muc_log_http.yml
|
||||
|
||||
- name: Get list of available modules
|
||||
run: |
|
||||
CTL=_build/dev/rel/ejabberd/bin/ejabberdctl
|
||||
$CTL modules_available | awk '{print $1}' | grep -v mod_captcha_rust >modules_available.txt
|
||||
|
||||
- name: Install modules
|
||||
run: |
|
||||
CTL=_build/dev/rel/ejabberd/bin/ejabberdctl
|
||||
for i in `cat modules_available.txt` ; do
|
||||
echo "Installing $i"
|
||||
$CTL module_install $i
|
||||
done
|
||||
|
||||
- name: Copy modules
|
||||
run: |
|
||||
CTL=_build/dev/rel/ejabberd/bin/ejabberdctl
|
||||
for i in `cat modules_available.txt` ; do
|
||||
echo "Copying from $i"
|
||||
find .ejabberd-modules/sources/ejabberd-contrib/ -wholename "*/ejabberd-contrib/$i/include/*.hrl" -exec 'cp' '{}' 'include/' ';'
|
||||
find .ejabberd-modules/sources/ejabberd-contrib/ -wholename "*/ejabberd-contrib/$i/src/*.erl" -exec 'cp' '{}' 'src/' ';'
|
||||
find .ejabberd-modules/sources/ejabberd-contrib/ -wholename "*/ejabberd-contrib/$i/deps/*/ebin/*.beam" -exec 'cp' '{}' '_build/default/lib/ejabberd/ebin/' ';'
|
||||
find .ejabberd-modules/sources/ejabberd-contrib/ -wholename "*/ejabberd-contrib/$i/deps/*/include/*.hrl" -exec 'cp' '{}' 'include/' ';'
|
||||
done
|
||||
|
||||
- name: Uninstall modules
|
||||
run: |
|
||||
CTL=_build/dev/rel/ejabberd/bin/ejabberdctl
|
||||
for i in `cat modules_available.txt` ; do
|
||||
echo "Uninstalling $i"
|
||||
$CTL module_uninstall $i
|
||||
done
|
||||
|
||||
# This doesn't work right now, because epmd is in another path
|
||||
# - run: ./ejabberdctl stop && ./ejabberdctl stopped
|
||||
|
||||
- run: make
|
||||
- run: make hooks
|
||||
- run: make options
|
||||
- run: make xref
|
||||
|
||||
- name: Run Dialyzer
|
||||
if: always()
|
||||
run: |
|
||||
rm -rf _build/default/lib/ejabberd/ebin/fusco*
|
||||
rm -rf _build/default/lib/ejabberd/ebin/observer_cli*
|
||||
make dialyzer # Too many errors... first fix them, then enable this
|
||||
|
||||
- name: View logs dir
|
||||
if: always()
|
||||
run: ls -la _build/dev/rel/ejabberd/logs
|
||||
- name: View ejabberd.log
|
||||
if: always()
|
||||
run: cat _build/dev/rel/ejabberd/logs/ejabberd.log
|
||||
- name: View error.log
|
||||
if: always()
|
||||
run: cat _build/dev/rel/ejabberd/logs/error.log
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
This is the list of modules that are known to be broken with latest ejabberd master branch.
|
||||
|
||||
If you feel they are worth it, your help to fix them is welcome:
|
||||
|
||||
- atom_pubsub: "The atom_pubsub module provides access to all PEP nodes via an
|
||||
AtomPub interface."
|
||||
- ircd: "This is an IRC server frontend to ejabberd."
|
||||
- mod_archive: "Message Archiving (XEP-0136)"
|
||||
- mod_openid: "Transform the Jabber Server in an openid provider."
|
||||
- mod_profile: "User Profile (XEP-0154) in Mnesia table"
|
90
README.md
90
README.md
|
@ -4,34 +4,29 @@ ejabberd-contrib
|
|||
This is a collaborative development area for ejabberd module developers
|
||||
and users.
|
||||
|
||||
Those modules are not officially supported by ProcessOne.
|
||||
|
||||
For users
|
||||
---------
|
||||
|
||||
To use an ejabberd module coming from this repository:
|
||||
|
||||
- You need to have Erlang installed.
|
||||
- You need to have ejabberd installed.
|
||||
|
||||
- If you have not already done it, run `ejabberdctl modules_update_specs`
|
||||
to retrieve the list of available modules.
|
||||
|
||||
- Read the module-specific `README.txt` file to see if special steps are
|
||||
required to deploy it.
|
||||
- Run `ejabberdctl module_install <module>` to get the source code and to
|
||||
compile and install the `beam` file into ejabberd's module search path.
|
||||
This path is either `~/.ejabberd-modules` or defined by the
|
||||
`CONTRIB_MODULES_PATH` setting in `ejabberdctl.cfg`.
|
||||
|
||||
- Run `./build.sh` or `build.bat` in the root directory of the desired
|
||||
module.
|
||||
- Edit the configuration file provided in the `conf` directory of the
|
||||
installed module and update it to your needs. Or, if you prefer so,
|
||||
configure it in your main ejabberd configuration file.
|
||||
|
||||
- Copy generated `.beam` files from the `ebin` directory to the directory
|
||||
where your ejabberd `.beam` files are.
|
||||
|
||||
- Use the configuration file examples provided in the `conf` dir to update
|
||||
your `ejabberd.cfg` or `ejabberd.yml` configuration file.
|
||||
|
||||
If during compilation of a module you get an error like:
|
||||
|
||||
{"init terminating in do_boot",{undef,[{make,all,[]},...
|
||||
|
||||
it means Erlang couldn't find its `make.beam` file. In Debian and other
|
||||
distributions you can try to install packages like:
|
||||
|
||||
erlang-dev erlang-nox erlang-tools
|
||||
- Run `ejabberdctl module_uninstall <module>` to remove a module from
|
||||
ejabberd.
|
||||
|
||||
|
||||
For developers
|
||||
|
@ -39,23 +34,52 @@ For developers
|
|||
|
||||
The following organization has been set up for the development:
|
||||
|
||||
- Development and compilation of modules should be possible without the
|
||||
ejabberd source code, as the `ejabberd-dev` helper module contains the
|
||||
include files necessary to make compilation possible.
|
||||
- Development and compilation of modules is done by ejabberd. You need
|
||||
ejabberd installed. Use `ejabberdctl module_check <module>` to ensure it
|
||||
compiles correctly before committing your work. The sources of your
|
||||
module must be located in `$CONTRIB_MODULES_PATH/sources/<module>`.
|
||||
|
||||
- Compilation can by done manually (if you know what you are doing) so you
|
||||
don't need ejabberd running:
|
||||
```
|
||||
cd /path/of/module
|
||||
mkdir ebin
|
||||
/path/of/ejabberd's/erlc \
|
||||
-o ebin \
|
||||
-I include -I /path/of/ejabberd/lib/ejabberd-XX.YY/include \
|
||||
-DLAGER -DNO_EXT_LIB \
|
||||
src/*erl
|
||||
```
|
||||
|
||||
- The module directory structure is usually the following:
|
||||
* `README.txt`: Module description.
|
||||
* `LICENSE.txt`: License for the module.
|
||||
* `Emakefile`: Erlang Makefile to build the module (preferred way, if
|
||||
no dependencies on C code, as build will thus work on Windows).
|
||||
* `COPYING`: License for the module.
|
||||
* `doc/`: Documentation directory.
|
||||
* `src/`: Source directory.
|
||||
* `src/msgs/`: Directory with translation files (pot, po and msg).
|
||||
* `ebin/`: Empty (target directory for the build).
|
||||
* `conf/`: Directory containing example configuration for your module.
|
||||
* `build.sh`: Unix/Linux build script.
|
||||
* `build.bat`: Windows build script.
|
||||
* `src/`: Erlang source directory.
|
||||
* `lib/`: Elixir source directory.
|
||||
* `priv/msgs/`: Directory with translation files (pot, po and msg).
|
||||
* `conf/<module>.yml`: Configuration for your module.
|
||||
* `<module>.spec`: Yaml description file for your module.
|
||||
|
||||
- Module developers should note in the `README.txt` file whether the
|
||||
module has requirements or known incompatibilities with other modules
|
||||
(for example, by modifying the same main ejabberd modules).
|
||||
module has requirements or known incompatibilities with other modules.
|
||||
|
||||
- If your module project contains several erlang modules, you should export a
|
||||
function pre_uninstall/0 in the main one listing the other ones.
|
||||
See mod_statsdx as an example.
|
||||
|
||||
|
||||
Broken modules
|
||||
--------------
|
||||
|
||||
This is the list of modules that are known to be broken with latest ejabberd master branch.
|
||||
|
||||
If you feel they are worth it, your help to fix them is welcome:
|
||||
|
||||
- atom_pubsub: "Provides access to all PEP nodes via an AtomPub interface."
|
||||
- ircd: "This is an IRC server frontend to ejabberd."
|
||||
- mod_archive: "Message Archiving (XEP-0136)."
|
||||
- mod_irc: "IRC transport."
|
||||
- mod_mam_mnesia: This feature got included in ejabberd 15.06
|
||||
- mod_openid: "Transform the Jabber Server in an openid provider."
|
||||
- mod_profile: "User Profile (XEP-0154) in Mnesia table."
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{'../ejabberd-dev/src/gen_mod', [{outdir, "../ejabberd-dev/ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||
{'src/atom_microblog', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||
{'src/atom_pubsub', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||
{'src/mod_couch', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
|
@ -1,10 +1,20 @@
|
|||
|
||||
|
||||
***************
|
||||
PLEASE NOTE
|
||||
***************
|
||||
|
||||
This module does NOT work
|
||||
with ejabberd 13 or newer.
|
||||
|
||||
***************
|
||||
|
||||
|
||||
atom_pubsub - the Atom PubSub tunnel
|
||||
|
||||
Author: Eric Cestari <eric@ohmforce.com> http://www.cestari.info/
|
||||
Licensed under the same terms as ejabberd (GPL 2)
|
||||
Requires: ejabberd 2.0.3 or newer.
|
||||
Does NOT work with ejabberd 13 or newer.
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
|
@ -23,28 +33,6 @@ http://www.cestari.info/2008/6/19/atom-pubsub-module-for-ejabberd
|
|||
http://www.cestari.info/2008/9/12/atom_pubsub-dead-long-live-atom_microblog
|
||||
|
||||
|
||||
INSTALL
|
||||
-------
|
||||
|
||||
1. Compile the files executing: ./build.sh
|
||||
|
||||
2. Copy beam files from ebin/ into your ejabberd installation.
|
||||
|
||||
3. Edit ejabberd.cfg and add a request handler in ejabberd_http listener:
|
||||
{listen, [
|
||||
...
|
||||
{8080, ejabberd_http, [
|
||||
http_poll,
|
||||
web_admin,
|
||||
{request_handlers, [{["pep"], atom_microblog}, % Make sure it's "pep", or change it in atom_microblog.erl
|
||||
{["pubsub"], atom_pubsub}]} % Make sure it's "pubsub", or change it in atom_pubsub.erl
|
||||
|
||||
]}
|
||||
]}.
|
||||
|
||||
4. Restart ejabberd.
|
||||
|
||||
|
||||
USAGE
|
||||
-----
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
author: "Eric Cestari <eric at ohmforce.com>"
|
||||
category: "pubsub"
|
||||
summary: "Provides access to all PEP nodes via an AtomPub interface"
|
||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/atom_pubsub"
|
||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
@ -1 +0,0 @@
|
|||
erl -pa ../ejabberd-dev/ebin -pa ebin -make
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/sh
|
||||
erl -pa ../ejabberd-dev/ebin -pz ebin -make
|
|
@ -0,0 +1,7 @@
|
|||
listen:
|
||||
-
|
||||
port: 8080
|
||||
module: ejabberd_http
|
||||
request_handlers:
|
||||
"pep": atom_microblog
|
||||
"pubsub": atom_pubsub
|
|
@ -1,5 +0,0 @@
|
|||
ejabberd-dev is an helper module containing include files from the ejabberd
|
||||
project.
|
||||
|
||||
This should allow to develop or build ejabberd modules without a complete
|
||||
development version of ejabberd (for example with a binary version of ejabberd).
|
|
@ -1,44 +0,0 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(adhoc_request,
|
||||
{
|
||||
lang = <<"">> :: binary(),
|
||||
node = <<"">> :: binary(),
|
||||
sessionid = <<"">> :: binary(),
|
||||
action = <<"">> :: binary(),
|
||||
xdata = false :: false | xmlel(),
|
||||
others = [] :: [xmlel()]
|
||||
}).
|
||||
|
||||
-record(adhoc_response,
|
||||
{
|
||||
lang = <<"">> :: binary(),
|
||||
node = <<"">> :: binary(),
|
||||
sessionid = <<"">> :: binary(),
|
||||
status :: atom(),
|
||||
defaultaction = <<"">> :: binary(),
|
||||
actions = [] :: [binary()],
|
||||
notes = [] :: [{binary(), binary()}],
|
||||
elements = [] :: [xmlel()]
|
||||
}).
|
||||
|
||||
-type adhoc_request() :: #adhoc_request{}.
|
||||
-type adhoc_response() :: #adhoc_response{}.
|
|
@ -1,51 +0,0 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%% This macro returns a string of the ejabberd version running, e.g. "2.3.4"
|
||||
%% If the ejabberd application description isn't loaded, returns atom: undefined
|
||||
-define(VERSION, ejabberd_config:get_version()).
|
||||
|
||||
-define(MYHOSTS, ejabberd_config:get_myhosts()).
|
||||
|
||||
-define(MYNAME, hd(ejabberd_config:get_myhosts())).
|
||||
|
||||
-define(MYLANG, ejabberd_config:get_mylang()).
|
||||
|
||||
-define(MSGS_DIR, filename:join(["priv", "msgs"])).
|
||||
|
||||
-define(CONFIG_PATH, <<"ejabberd.cfg">>).
|
||||
|
||||
-define(LOG_PATH, <<"ejabberd.log">>).
|
||||
|
||||
-define(EJABBERD_URI, <<"http://www.process-one.net/en/ejabberd/">>).
|
||||
|
||||
-define(S2STIMEOUT, 600000).
|
||||
|
||||
%%-define(DBGFSM, true).
|
||||
|
||||
-record(scram,
|
||||
{storedkey = <<"">>,
|
||||
serverkey = <<"">>,
|
||||
salt = <<"">>,
|
||||
iterationcount = 0 :: integer()}).
|
||||
|
||||
-type scram() :: #scram{}.
|
||||
|
||||
-define(SCRAM_DEFAULT_ITERATION_COUNT, 4096).
|
|
@ -1,74 +0,0 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-type aterm() :: {atom(), atype()}.
|
||||
-type atype() :: integer | string | binary |
|
||||
{tuple, [aterm()]} | {list, aterm()}.
|
||||
-type rterm() :: {atom(), rtype()}.
|
||||
-type rtype() :: integer | string | atom |
|
||||
{tuple, [rterm()]} | {list, rterm()} |
|
||||
rescode | restuple.
|
||||
|
||||
-record(ejabberd_commands,
|
||||
{name :: atom(),
|
||||
tags = [] :: [atom()] | '_' | '$2',
|
||||
desc = "" :: string() | '_' | '$3',
|
||||
longdesc = "" :: string() | '_',
|
||||
module :: atom(),
|
||||
function :: atom(),
|
||||
args = [] :: [aterm()] | '_' | '$1' | '$2',
|
||||
result = {res, rescode} :: rterm() | '_' | '$2'}).
|
||||
|
||||
-type ejabberd_commands() :: #ejabberd_commands{name :: atom(),
|
||||
tags :: [atom()],
|
||||
desc :: string(),
|
||||
longdesc :: string(),
|
||||
module :: atom(),
|
||||
function :: atom(),
|
||||
args :: [aterm()],
|
||||
result :: rterm()}.
|
||||
|
||||
%% @type ejabberd_commands() = #ejabberd_commands{
|
||||
%% name = atom(),
|
||||
%% tags = [atom()],
|
||||
%% desc = string(),
|
||||
%% longdesc = string(),
|
||||
%% module = atom(),
|
||||
%% function = atom(),
|
||||
%% args = [aterm()],
|
||||
%% result = rterm()
|
||||
%% }.
|
||||
%% desc: Description of the command
|
||||
%% args: Describe the accepted arguments.
|
||||
%% This way the function that calls the command can format the
|
||||
%% arguments before calling.
|
||||
|
||||
%% @type atype() = integer | string | {tuple, [aterm()]} | {list, aterm()}.
|
||||
%% Allowed types for arguments are integer, string, tuple and list.
|
||||
|
||||
%% @type rtype() = integer | string | atom | {tuple, [rterm()]} | {list, rterm()} | rescode | restuple.
|
||||
%% A rtype is either an atom or a tuple with two elements.
|
||||
|
||||
%% @type aterm() = {Name::atom(), Type::atype()}.
|
||||
%% An argument term is a tuple with the term name and the term type.
|
||||
|
||||
%% @type rterm() = {Name::atom(), Type::rtype()}.
|
||||
%% A result term is a tuple with the term name and the term type.
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(request,
|
||||
{method, % :: method(),
|
||||
path = [] :: [binary()],
|
||||
q = [] :: [{binary() | nokey, binary()}],
|
||||
us = {<<>>, <<>>} :: {binary(), binary()},
|
||||
auth :: {binary(), binary()} |
|
||||
{auth_jid, {binary(), binary()}, jlib:jid()},
|
||||
lang = <<"">> :: binary(),
|
||||
data = <<"">> :: binary(),
|
||||
ip :: {inet:ip_address(), inet:port_number()},
|
||||
host = <<"">> :: binary(),
|
||||
port = 5280 :: inet:port_number(),
|
||||
tp = http, % :: protocol(),
|
||||
opts = [] :: list(),
|
||||
headers = [] :: [{atom() | binary(), binary()}]}).
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-define(X(Name),
|
||||
#xmlel{name = Name, attrs = [], children = []}).
|
||||
|
||||
-define(XA(Name, Attrs),
|
||||
#xmlel{name = Name, attrs = Attrs, children = []}).
|
||||
|
||||
-define(XE(Name, Els),
|
||||
#xmlel{name = Name, attrs = [], children = Els}).
|
||||
|
||||
-define(XAE(Name, Attrs, Els),
|
||||
#xmlel{name = Name, attrs = Attrs, children = Els}).
|
||||
|
||||
-define(C(Text), {xmlcdata, Text}).
|
||||
|
||||
-define(XC(Name, Text), ?XE(Name, [?C(Text)])).
|
||||
|
||||
-define(XAC(Name, Attrs, Text),
|
||||
?XAE(Name, Attrs, [?C(Text)])).
|
||||
|
||||
-define(T(Text), translate:translate(Lang, Text)).
|
||||
|
||||
-define(CT(Text), ?C((?T(Text)))).
|
||||
|
||||
-define(XCT(Name, Text), ?XC(Name, (?T(Text)))).
|
||||
|
||||
-define(XACT(Name, Attrs, Text),
|
||||
?XAC(Name, Attrs, (?T(Text)))).
|
||||
|
||||
-define(LI(Els), ?XE(<<"li">>, Els)).
|
||||
|
||||
-define(A(URL, Els),
|
||||
?XAE(<<"a">>, [{<<"href">>, URL}], Els)).
|
||||
|
||||
-define(AC(URL, Text), ?A(URL, [?C(Text)])).
|
||||
|
||||
-define(ACT(URL, Text), ?AC(URL, (?T(Text)))).
|
||||
|
||||
-define(P, ?X(<<"p">>)).
|
||||
|
||||
-define(BR, ?X(<<"br">>)).
|
||||
|
||||
-define(INPUT(Type, Name, Value),
|
||||
?XA(<<"input">>,
|
||||
[{<<"type">>, Type}, {<<"name">>, Name},
|
||||
{<<"value">>, Value}])).
|
||||
|
||||
-define(INPUTT(Type, Name, Value),
|
||||
?INPUT(Type, Name, (?T(Value)))).
|
||||
|
||||
-define(INPUTS(Type, Name, Value, Size),
|
||||
?XA(<<"input">>,
|
||||
[{<<"type">>, Type}, {<<"name">>, Name},
|
||||
{<<"value">>, Value}, {<<"size">>, Size}])).
|
||||
|
||||
-define(INPUTST(Type, Name, Value, Size),
|
||||
?INPUT(Type, Name, (?T(Value)), Size)).
|
||||
|
||||
-define(ACLINPUT(Text),
|
||||
?XE(<<"td">>,
|
||||
[?INPUT(<<"text">>, <<"value", ID/binary>>, Text)])).
|
||||
|
||||
-define(TEXTAREA(Name, Rows, Cols, Value),
|
||||
?XAC(<<"textarea">>,
|
||||
[{<<"name">>, Name}, {<<"rows">>, Rows},
|
||||
{<<"cols">>, Cols}],
|
||||
Value)).
|
||||
|
||||
%% Build an xmlelement for result
|
||||
-define(XRES(Text),
|
||||
?XAC(<<"p">>, [{<<"class">>, <<"result">>}], Text)).
|
||||
|
||||
%% Guide Link
|
||||
-define(XREST(Text), ?XRES((?T(Text)))).
|
||||
|
||||
-define(GL(Ref, Title),
|
||||
?XAE(<<"div">>, [{<<"class">>, <<"guidelink">>}],
|
||||
[?XAE(<<"a">>,
|
||||
[{<<"href">>, <<"/admin/doc/guide.html#", Ref/binary>>},
|
||||
{<<"target">>, <<"_blank">>}],
|
||||
[?C(<<"[Guide: ", Title/binary, "]">>)])])).
|
||||
|
||||
%% h1 with a Guide Link
|
||||
-define(H1GL(Name, Ref, Title),
|
||||
[?XC(<<"h1">>, Name), ?GL(Ref, Title)]).
|
|
@ -1,501 +0,0 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-include("ns.hrl").
|
||||
-include("xml.hrl").
|
||||
|
||||
-define(STANZA_ERROR(Code, Type, Condition),
|
||||
#xmlel{name = <<"error">>,
|
||||
attrs = [{<<"code">>, Code}, {<<"type">>, Type}],
|
||||
children =
|
||||
[#xmlel{name = Condition,
|
||||
attrs = [{<<"xmlns">>, ?NS_STANZAS}],
|
||||
children = []}]}).
|
||||
|
||||
-define(ERR_BAD_FORMAT,
|
||||
?STANZA_ERROR(<<"406">>, <<"modify">>,
|
||||
<<"bad-format">>)).
|
||||
|
||||
-define(ERR_BAD_REQUEST,
|
||||
?STANZA_ERROR(<<"400">>, <<"modify">>,
|
||||
<<"bad-request">>)).
|
||||
|
||||
-define(ERR_CONFLICT,
|
||||
?STANZA_ERROR(<<"409">>, <<"cancel">>, <<"conflict">>)).
|
||||
|
||||
-define(ERR_FEATURE_NOT_IMPLEMENTED,
|
||||
?STANZA_ERROR(<<"501">>, <<"cancel">>,
|
||||
<<"feature-not-implemented">>)).
|
||||
|
||||
-define(ERR_FORBIDDEN,
|
||||
?STANZA_ERROR(<<"403">>, <<"auth">>, <<"forbidden">>)).
|
||||
|
||||
-define(ERR_GONE,
|
||||
?STANZA_ERROR(<<"302">>, <<"modify">>, <<"gone">>)).
|
||||
|
||||
-define(ERR_INTERNAL_SERVER_ERROR,
|
||||
?STANZA_ERROR(<<"500">>, <<"wait">>,
|
||||
<<"internal-server-error">>)).
|
||||
|
||||
-define(ERR_ITEM_NOT_FOUND,
|
||||
?STANZA_ERROR(<<"404">>, <<"cancel">>,
|
||||
<<"item-not-found">>)).
|
||||
|
||||
-define(ERR_JID_MALFORMED,
|
||||
?STANZA_ERROR(<<"400">>, <<"modify">>,
|
||||
<<"jid-malformed">>)).
|
||||
|
||||
-define(ERR_NOT_ACCEPTABLE,
|
||||
?STANZA_ERROR(<<"406">>, <<"modify">>,
|
||||
<<"not-acceptable">>)).
|
||||
|
||||
-define(ERR_NOT_ALLOWED,
|
||||
?STANZA_ERROR(<<"405">>, <<"cancel">>,
|
||||
<<"not-allowed">>)).
|
||||
|
||||
-define(ERR_NOT_AUTHORIZED,
|
||||
?STANZA_ERROR(<<"401">>, <<"auth">>,
|
||||
<<"not-authorized">>)).
|
||||
|
||||
-define(ERR_PAYMENT_REQUIRED,
|
||||
?STANZA_ERROR(<<"402">>, <<"auth">>,
|
||||
<<"payment-required">>)).
|
||||
|
||||
-define(ERR_RECIPIENT_UNAVAILABLE,
|
||||
?STANZA_ERROR(<<"404">>, <<"wait">>,
|
||||
<<"recipient-unavailable">>)).
|
||||
|
||||
-define(ERR_REDIRECT,
|
||||
?STANZA_ERROR(<<"302">>, <<"modify">>, <<"redirect">>)).
|
||||
|
||||
-define(ERR_REGISTRATION_REQUIRED,
|
||||
?STANZA_ERROR(<<"407">>, <<"auth">>,
|
||||
<<"registration-required">>)).
|
||||
|
||||
-define(ERR_REMOTE_SERVER_NOT_FOUND,
|
||||
?STANZA_ERROR(<<"404">>, <<"cancel">>,
|
||||
<<"remote-server-not-found">>)).
|
||||
|
||||
-define(ERR_REMOTE_SERVER_TIMEOUT,
|
||||
?STANZA_ERROR(<<"504">>, <<"wait">>,
|
||||
<<"remote-server-timeout">>)).
|
||||
|
||||
-define(ERR_RESOURCE_CONSTRAINT,
|
||||
?STANZA_ERROR(<<"500">>, <<"wait">>,
|
||||
<<"resource-constraint">>)).
|
||||
|
||||
-define(ERR_SERVICE_UNAVAILABLE,
|
||||
?STANZA_ERROR(<<"503">>, <<"cancel">>,
|
||||
<<"service-unavailable">>)).
|
||||
|
||||
-define(ERR_SUBSCRIPTION_REQUIRED,
|
||||
?STANZA_ERROR(<<"407">>, <<"auth">>,
|
||||
<<"subscription-required">>)).
|
||||
|
||||
-define(ERR_UNEXPECTED_REQUEST,
|
||||
?STANZA_ERROR(<<"400">>, <<"wait">>,
|
||||
<<"unexpected-request">>)).
|
||||
|
||||
-define(ERR_UNEXPECTED_REQUEST_CANCEL,
|
||||
?STANZA_ERROR(<<"401">>, <<"cancel">>,
|
||||
<<"unexpected-request">>)).
|
||||
|
||||
%-define(ERR_,
|
||||
% ?STANZA_ERROR("", "", "")).
|
||||
|
||||
-define(STANZA_ERRORT(Code, Type, Condition, Lang,
|
||||
Text),
|
||||
#xmlel{name = <<"error">>,
|
||||
attrs = [{<<"code">>, Code}, {<<"type">>, Type}],
|
||||
children =
|
||||
[#xmlel{name = Condition,
|
||||
attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []},
|
||||
#xmlel{name = <<"text">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_STANZAS}],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
translate:translate(Lang, Text)}]}]}).
|
||||
|
||||
-define(ERRT_BAD_FORMAT(Lang, Text),
|
||||
?STANZA_ERRORT(<<"406">>, <<"modify">>,
|
||||
<<"bad-format">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_BAD_REQUEST(Lang, Text),
|
||||
?STANZA_ERRORT(<<"400">>, <<"modify">>,
|
||||
<<"bad-request">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_CONFLICT(Lang, Text),
|
||||
?STANZA_ERRORT(<<"409">>, <<"cancel">>, <<"conflict">>,
|
||||
Lang, Text)).
|
||||
|
||||
-define(ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Text),
|
||||
?STANZA_ERRORT(<<"501">>, <<"cancel">>,
|
||||
<<"feature-not-implemented">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_FORBIDDEN(Lang, Text),
|
||||
?STANZA_ERRORT(<<"403">>, <<"auth">>, <<"forbidden">>,
|
||||
Lang, Text)).
|
||||
|
||||
-define(ERRT_GONE(Lang, Text),
|
||||
?STANZA_ERRORT(<<"302">>, <<"modify">>, <<"gone">>,
|
||||
Lang, Text)).
|
||||
|
||||
-define(ERRT_INTERNAL_SERVER_ERROR(Lang, Text),
|
||||
?STANZA_ERRORT(<<"500">>, <<"wait">>,
|
||||
<<"internal-server-error">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_ITEM_NOT_FOUND(Lang, Text),
|
||||
?STANZA_ERRORT(<<"404">>, <<"cancel">>,
|
||||
<<"item-not-found">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_JID_MALFORMED(Lang, Text),
|
||||
?STANZA_ERRORT(<<"400">>, <<"modify">>,
|
||||
<<"jid-malformed">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_NOT_ACCEPTABLE(Lang, Text),
|
||||
?STANZA_ERRORT(<<"406">>, <<"modify">>,
|
||||
<<"not-acceptable">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_NOT_ALLOWED(Lang, Text),
|
||||
?STANZA_ERRORT(<<"405">>, <<"cancel">>,
|
||||
<<"not-allowed">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_NOT_AUTHORIZED(Lang, Text),
|
||||
?STANZA_ERRORT(<<"401">>, <<"auth">>,
|
||||
<<"not-authorized">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_PAYMENT_REQUIRED(Lang, Text),
|
||||
?STANZA_ERRORT(<<"402">>, <<"auth">>,
|
||||
<<"payment-required">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_RECIPIENT_UNAVAILABLE(Lang, Text),
|
||||
?STANZA_ERRORT(<<"404">>, <<"wait">>,
|
||||
<<"recipient-unavailable">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_REDIRECT(Lang, Text),
|
||||
?STANZA_ERRORT(<<"302">>, <<"modify">>, <<"redirect">>,
|
||||
Lang, Text)).
|
||||
|
||||
-define(ERRT_REGISTRATION_REQUIRED(Lang, Text),
|
||||
?STANZA_ERRORT(<<"407">>, <<"auth">>,
|
||||
<<"registration-required">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_REMOTE_SERVER_NOT_FOUND(Lang, Text),
|
||||
?STANZA_ERRORT(<<"404">>, <<"cancel">>,
|
||||
<<"remote-server-not-found">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_REMOTE_SERVER_TIMEOUT(Lang, Text),
|
||||
?STANZA_ERRORT(<<"504">>, <<"wait">>,
|
||||
<<"remote-server-timeout">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_RESOURCE_CONSTRAINT(Lang, Text),
|
||||
?STANZA_ERRORT(<<"500">>, <<"wait">>,
|
||||
<<"resource-constraint">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_SERVICE_UNAVAILABLE(Lang, Text),
|
||||
?STANZA_ERRORT(<<"503">>, <<"cancel">>,
|
||||
<<"service-unavailable">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_SUBSCRIPTION_REQUIRED(Lang, Text),
|
||||
?STANZA_ERRORT(<<"407">>, <<"auth">>,
|
||||
<<"subscription-required">>, Lang, Text)).
|
||||
|
||||
-define(ERRT_UNEXPECTED_REQUEST(Lang, Text),
|
||||
?STANZA_ERRORT(<<"400">>, <<"wait">>,
|
||||
<<"unexpected-request">>, Lang, Text)).
|
||||
|
||||
-define(ERR_AUTH_NO_RESOURCE_PROVIDED(Lang),
|
||||
?ERRT_NOT_ACCEPTABLE(Lang, <<"No resource provided">>)).
|
||||
|
||||
-define(ERR_AUTH_BAD_RESOURCE_FORMAT(Lang),
|
||||
?ERRT_NOT_ACCEPTABLE(Lang,
|
||||
<<"Illegal resource format">>)).
|
||||
|
||||
-define(ERR_AUTH_RESOURCE_CONFLICT(Lang),
|
||||
?ERRT_CONFLICT(Lang, <<"Resource conflict">>)).
|
||||
|
||||
-define(STREAM_ERROR(Condition, Cdata),
|
||||
#xmlel{name = <<"stream:error">>, attrs = [],
|
||||
children =
|
||||
[#xmlel{name = Condition,
|
||||
attrs = [{<<"xmlns">>, ?NS_STREAMS}],
|
||||
children = [{xmlcdata, Cdata}]}]}).
|
||||
|
||||
-define(SERR_BAD_FORMAT,
|
||||
?STREAM_ERROR(<<"bad-format">>, <<"">>)).
|
||||
|
||||
-define(SERR_BAD_NAMESPACE_PREFIX,
|
||||
?STREAM_ERROR(<<"bad-namespace-prefix">>, <<"">>)).
|
||||
|
||||
-define(SERR_CONFLICT,
|
||||
?STREAM_ERROR(<<"conflict">>, <<"">>)).
|
||||
|
||||
-define(SERR_CONNECTION_TIMEOUT,
|
||||
?STREAM_ERROR(<<"connection-timeout">>, <<"">>)).
|
||||
|
||||
-define(SERR_HOST_GONE,
|
||||
?STREAM_ERROR(<<"host-gone">>, <<"">>)).
|
||||
|
||||
-define(SERR_HOST_UNKNOWN,
|
||||
?STREAM_ERROR(<<"host-unknown">>, <<"">>)).
|
||||
|
||||
-define(SERR_IMPROPER_ADDRESSING,
|
||||
?STREAM_ERROR(<<"improper-addressing">>, <<"">>)).
|
||||
|
||||
-define(SERR_INTERNAL_SERVER_ERROR,
|
||||
?STREAM_ERROR(<<"internal-server-error">>, <<"">>)).
|
||||
|
||||
-define(SERR_INVALID_FROM,
|
||||
?STREAM_ERROR(<<"invalid-from">>, <<"">>)).
|
||||
|
||||
-define(SERR_INVALID_ID,
|
||||
?STREAM_ERROR(<<"invalid-id">>, <<"">>)).
|
||||
|
||||
-define(SERR_INVALID_NAMESPACE,
|
||||
?STREAM_ERROR(<<"invalid-namespace">>, <<"">>)).
|
||||
|
||||
-define(SERR_INVALID_XML,
|
||||
?STREAM_ERROR(<<"invalid-xml">>, <<"">>)).
|
||||
|
||||
-define(SERR_NOT_AUTHORIZED,
|
||||
?STREAM_ERROR(<<"not-authorized">>, <<"">>)).
|
||||
|
||||
-define(SERR_POLICY_VIOLATION,
|
||||
?STREAM_ERROR(<<"policy-violation">>, <<"">>)).
|
||||
|
||||
-define(SERR_REMOTE_CONNECTION_FAILED,
|
||||
?STREAM_ERROR(<<"remote-connection-failed">>, <<"">>)).
|
||||
|
||||
-define(SERR_RESOURSE_CONSTRAINT,
|
||||
?STREAM_ERROR(<<"resource-constraint">>, <<"">>)).
|
||||
|
||||
-define(SERR_RESTRICTED_XML,
|
||||
?STREAM_ERROR(<<"restricted-xml">>, <<"">>)).
|
||||
|
||||
-define(SERR_SEE_OTHER_HOST(Host),
|
||||
?STREAM_ERROR(<<"see-other-host">>, Host)).
|
||||
|
||||
-define(SERR_SYSTEM_SHUTDOWN,
|
||||
?STREAM_ERROR(<<"system-shutdown">>, <<"">>)).
|
||||
|
||||
-define(SERR_UNSUPPORTED_ENCODING,
|
||||
?STREAM_ERROR(<<"unsupported-encoding">>, <<"">>)).
|
||||
|
||||
-define(SERR_UNSUPPORTED_STANZA_TYPE,
|
||||
?STREAM_ERROR(<<"unsupported-stanza-type">>, <<"">>)).
|
||||
|
||||
-define(SERR_UNSUPPORTED_VERSION,
|
||||
?STREAM_ERROR(<<"unsupported-version">>, <<"">>)).
|
||||
|
||||
-define(SERR_XML_NOT_WELL_FORMED,
|
||||
?STREAM_ERROR(<<"xml-not-well-formed">>, <<"">>)).
|
||||
|
||||
%-define(SERR_,
|
||||
% ?STREAM_ERROR("", "")).
|
||||
|
||||
-define(STREAM_ERRORT(Condition, Cdata, Lang, Text),
|
||||
#xmlel{name = <<"stream:error">>, attrs = [],
|
||||
children =
|
||||
[#xmlel{name = Condition,
|
||||
attrs = [{<<"xmlns">>, ?NS_STREAMS}],
|
||||
children = [{xmlcdata, Cdata}]},
|
||||
#xmlel{name = <<"text">>,
|
||||
attrs =
|
||||
[{<<"xml:lang">>, Lang},
|
||||
{<<"xmlns">>, ?NS_STREAMS}],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
translate:translate(Lang, Text)}]}]}).
|
||||
|
||||
-define(SERRT_BAD_FORMAT(Lang, Text),
|
||||
?STREAM_ERRORT(<<"bad-format">>, <<"">>, Lang, Text)).
|
||||
|
||||
-define(SERRT_BAD_NAMESPACE_PREFIX(Lang, Text),
|
||||
?STREAM_ERRORT(<<"bad-namespace-prefix">>, <<"">>, Lang,
|
||||
Text)).
|
||||
|
||||
-define(SERRT_CONFLICT(Lang, Text),
|
||||
?STREAM_ERRORT(<<"conflict">>, <<"">>, Lang, Text)).
|
||||
|
||||
-define(SERRT_CONNECTION_TIMEOUT(Lang, Text),
|
||||
?STREAM_ERRORT(<<"connection-timeout">>, <<"">>, Lang,
|
||||
Text)).
|
||||
|
||||
-define(SERRT_HOST_GONE(Lang, Text),
|
||||
?STREAM_ERRORT(<<"host-gone">>, <<"">>, Lang, Text)).
|
||||
|
||||
-define(SERRT_HOST_UNKNOWN(Lang, Text),
|
||||
?STREAM_ERRORT(<<"host-unknown">>, <<"">>, Lang, Text)).
|
||||
|
||||
-define(SERRT_IMPROPER_ADDRESSING(Lang, Text),
|
||||
?STREAM_ERRORT(<<"improper-addressing">>, <<"">>, Lang,
|
||||
Text)).
|
||||
|
||||
-define(SERRT_INTERNAL_SERVER_ERROR(Lang, Text),
|
||||
?STREAM_ERRORT(<<"internal-server-error">>, <<"">>,
|
||||
Lang, Text)).
|
||||
|
||||
-define(SERRT_INVALID_FROM(Lang, Text),
|
||||
?STREAM_ERRORT(<<"invalid-from">>, <<"">>, Lang, Text)).
|
||||
|
||||
-define(SERRT_INVALID_ID(Lang, Text),
|
||||
?STREAM_ERRORT(<<"invalid-id">>, <<"">>, Lang, Text)).
|
||||
|
||||
-define(SERRT_INVALID_NAMESPACE(Lang, Text),
|
||||
?STREAM_ERRORT(<<"invalid-namespace">>, <<"">>, Lang,
|
||||
Text)).
|
||||
|
||||
-define(SERRT_INVALID_XML(Lang, Text),
|
||||
?STREAM_ERRORT(<<"invalid-xml">>, <<"">>, Lang, Text)).
|
||||
|
||||
-define(SERRT_NOT_AUTHORIZED(Lang, Text),
|
||||
?STREAM_ERRORT(<<"not-authorized">>, <<"">>, Lang,
|
||||
Text)).
|
||||
|
||||
-define(SERRT_POLICY_VIOLATION(Lang, Text),
|
||||
?STREAM_ERRORT(<<"policy-violation">>, <<"">>, Lang,
|
||||
Text)).
|
||||
|
||||
-define(SERRT_REMOTE_CONNECTION_FAILED(Lang, Text),
|
||||
?STREAM_ERRORT(<<"remote-connection-failed">>, <<"">>,
|
||||
Lang, Text)).
|
||||
|
||||
-define(SERRT_RESOURSE_CONSTRAINT(Lang, Text),
|
||||
?STREAM_ERRORT(<<"resource-constraint">>, <<"">>, Lang,
|
||||
Text)).
|
||||
|
||||
-define(SERRT_RESTRICTED_XML(Lang, Text),
|
||||
?STREAM_ERRORT(<<"restricted-xml">>, <<"">>, Lang,
|
||||
Text)).
|
||||
|
||||
-define(SERRT_SEE_OTHER_HOST(Host, Lang, Text),
|
||||
?STREAM_ERRORT(<<"see-other-host">>, Host, Lang, Text)).
|
||||
|
||||
-define(SERRT_SYSTEM_SHUTDOWN(Lang, Text),
|
||||
?STREAM_ERRORT(<<"system-shutdown">>, <<"">>, Lang,
|
||||
Text)).
|
||||
|
||||
-define(SERRT_UNSUPPORTED_ENCODING(Lang, Text),
|
||||
?STREAM_ERRORT(<<"unsupported-encoding">>, <<"">>, Lang,
|
||||
Text)).
|
||||
|
||||
-define(SERRT_UNSUPPORTED_STANZA_TYPE(Lang, Text),
|
||||
?STREAM_ERRORT(<<"unsupported-stanza-type">>, <<"">>,
|
||||
Lang, Text)).
|
||||
|
||||
-define(SERRT_UNSUPPORTED_VERSION(Lang, Text),
|
||||
?STREAM_ERRORT(<<"unsupported-version">>, <<"">>, Lang,
|
||||
Text)).
|
||||
|
||||
-define(SERRT_XML_NOT_WELL_FORMED(Lang, Text),
|
||||
?STREAM_ERRORT(<<"xml-not-well-formed">>, <<"">>, Lang,
|
||||
Text)).
|
||||
|
||||
-record(jid, {user = <<"">> :: binary(),
|
||||
server = <<"">> :: binary(),
|
||||
resource = <<"">> :: binary(),
|
||||
luser = <<"">> :: binary(),
|
||||
lserver = <<"">> :: binary(),
|
||||
lresource = <<"">> :: binary()}).
|
||||
|
||||
-type(jid() :: #jid{}).
|
||||
|
||||
-type(ljid() :: {binary(), binary(), binary()}).
|
||||
|
||||
-record(iq, {id = <<"">> :: binary(),
|
||||
type = get :: get | set | result | error,
|
||||
xmlns = <<"">> :: binary(),
|
||||
lang = <<"">> :: binary(),
|
||||
sub_el = #xmlel{} :: xmlel() | [xmlel()]}).
|
||||
|
||||
-type(iq_get()
|
||||
:: #iq{
|
||||
id :: binary(),
|
||||
type :: get,
|
||||
xmlns :: binary(),
|
||||
lang :: binary(),
|
||||
sub_el :: xmlel()
|
||||
}
|
||||
).
|
||||
|
||||
-type(iq_set()
|
||||
:: #iq{
|
||||
id :: binary(),
|
||||
type :: set,
|
||||
xmlns :: binary(),
|
||||
lang :: binary(),
|
||||
sub_el :: xmlel()
|
||||
}
|
||||
).
|
||||
|
||||
-type iq_request() :: iq_get() | iq_set().
|
||||
|
||||
-type(iq_result()
|
||||
:: #iq{
|
||||
id :: binary(),
|
||||
type :: result,
|
||||
xmlns :: binary(),
|
||||
lang :: binary(),
|
||||
sub_el :: [xmlel()]
|
||||
}
|
||||
).
|
||||
|
||||
-type(iq_error()
|
||||
:: #iq{
|
||||
id :: binary(),
|
||||
type :: error,
|
||||
xmlns :: binary(),
|
||||
lang :: binary(),
|
||||
sub_el :: [xmlel()]
|
||||
}
|
||||
).
|
||||
|
||||
-type iq_reply() :: iq_result() | iq_error() .
|
||||
|
||||
-type(iq() :: iq_request() | iq_reply()).
|
||||
|
||||
-record(rsm_in, {max :: integer(),
|
||||
direction :: before | aft,
|
||||
id :: binary(),
|
||||
index :: integer()}).
|
||||
|
||||
-record(rsm_out, {count :: integer(),
|
||||
index :: integer(),
|
||||
first :: binary(),
|
||||
last :: binary()}).
|
||||
|
||||
-type(rsm_in() :: #rsm_in{}).
|
||||
|
||||
-type(rsm_out() :: #rsm_out{}).
|
||||
|
||||
-type broadcast() :: {broadcast, broadcast_data()}.
|
||||
|
||||
-type broadcast_data() ::
|
||||
{rebind, pid(), binary()} | %% ejabberd_c2s
|
||||
{item, ljid(), mod_roster:subscription()} | %% mod_roster/mod_shared_roster
|
||||
{exit, binary()} | %% mod_roster/mod_shared_roster
|
||||
{privacy_list, mod_privacy:userlist(), binary()} | %% mod_privacy
|
||||
{blocking, unblock_all | {block | unblock, [ljid()]}}. %% mod_blocking
|
||||
|
||||
-record(xmlelement, {name = "" :: string(),
|
||||
attrs = [] :: [{string(), string()}],
|
||||
children = [] :: [{xmlcdata, iodata()} | xmlelement()]}).
|
||||
|
||||
-type xmlelement() :: #xmlelement{}.
|
|
@ -1,102 +0,0 @@
|
|||
%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
|
||||
%%
|
||||
%% This file is provided to you under the Apache License,
|
||||
%% Version 2.0 (the "License"); you may not use this file
|
||||
%% except in compliance with the License. You may obtain
|
||||
%% a copy of the License at
|
||||
%%
|
||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||
%%
|
||||
%% Unless required by applicable law or agreed to in writing,
|
||||
%% software distributed under the License is distributed on an
|
||||
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
%% KIND, either express or implied. See the License for the
|
||||
%% specific language governing permissions and limitations
|
||||
%% under the License.
|
||||
|
||||
|
||||
-define(DEFAULT_TRUNCATION, 4096).
|
||||
-define(DEFAULT_TRACER, lager_default_tracer).
|
||||
|
||||
-define(LEVELS,
|
||||
[debug, info, notice, warning, error, critical, alert, emergency, none]).
|
||||
|
||||
-define(DEBUG, 128).
|
||||
-define(INFO, 64).
|
||||
-define(NOTICE, 32).
|
||||
-define(WARNING, 16).
|
||||
-define(ERROR, 8).
|
||||
-define(CRITICAL, 4).
|
||||
-define(ALERT, 2).
|
||||
-define(EMERGENCY, 1).
|
||||
-define(LOG_NONE, 0).
|
||||
|
||||
-define(LEVEL2NUM(Level),
|
||||
case Level of
|
||||
debug -> ?DEBUG;
|
||||
info -> ?INFO;
|
||||
notice -> ?NOTICE;
|
||||
warning -> ?WARNING;
|
||||
error -> ?ERROR;
|
||||
critical -> ?CRITICAL;
|
||||
alert -> ?ALERT;
|
||||
emergency -> ?EMERGENCY
|
||||
end).
|
||||
|
||||
-define(NUM2LEVEL(Num),
|
||||
case Num of
|
||||
?DEBUG -> debug;
|
||||
?INFO -> info;
|
||||
?NOTICE -> notice;
|
||||
?WARNING -> warning;
|
||||
?ERROR -> error;
|
||||
?CRITICAL -> critical;
|
||||
?ALERT -> alert;
|
||||
?EMERGENCY -> emergency
|
||||
end).
|
||||
|
||||
-define(SHOULD_LOG(Level),
|
||||
(lager_util:level_to_num(Level) band element(1, lager_config:get(loglevel, {?LOG_NONE, []}))) /= 0).
|
||||
|
||||
-define(NOTIFY(Level, Pid, Format, Args),
|
||||
gen_event:notify(lager_event, {log, lager_msg:new(io_lib:format(Format, Args),
|
||||
Level,
|
||||
[{pid,Pid},{line,?LINE},{file,?FILE},{module,?MODULE}],
|
||||
[])}
|
||||
)).
|
||||
|
||||
%% FOR INTERNAL USE ONLY
|
||||
%% internal non-blocking logging call
|
||||
%% there's some special handing for when we try to log (usually errors) while
|
||||
%% lager is still starting.
|
||||
-ifdef(TEST).
|
||||
-define(INT_LOG(Level, Format, Args),
|
||||
case ?SHOULD_LOG(Level) of
|
||||
true ->
|
||||
?NOTIFY(Level, self(), Format, Args);
|
||||
_ ->
|
||||
ok
|
||||
end).
|
||||
-else.
|
||||
-define(INT_LOG(Level, Format, Args),
|
||||
Self = self(),
|
||||
%% do this in a spawn so we don't cause a deadlock calling gen_event:which_handlers
|
||||
%% from a gen_event handler
|
||||
spawn(fun() ->
|
||||
case catch(gen_event:which_handlers(lager_event)) of
|
||||
X when X == []; X == {'EXIT', noproc}; X == [lager_backend_throttle] ->
|
||||
%% there's no handlers yet or lager isn't running, try again
|
||||
%% in half a second.
|
||||
timer:sleep(500),
|
||||
?NOTIFY(Level, Self, Format, Args);
|
||||
_ ->
|
||||
case ?SHOULD_LOG(Level) of
|
||||
true ->
|
||||
?NOTIFY(Level, Self, Format, Args);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end
|
||||
end)).
|
||||
-endif.
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
-define(PRINT(Format, Args), io:format(Format, Args)).
|
||||
|
||||
-ifdef(LAGER).
|
||||
-compile([{parse_transform, lager_transform}]).
|
||||
|
||||
-define(DEBUG(Format, Args),
|
||||
lager:debug(Format, Args)).
|
||||
|
||||
-define(INFO_MSG(Format, Args),
|
||||
lager:info(Format, Args)).
|
||||
|
||||
-define(WARNING_MSG(Format, Args),
|
||||
lager:warning(Format, Args)).
|
||||
|
||||
-define(ERROR_MSG(Format, Args),
|
||||
lager:error(Format, Args)).
|
||||
|
||||
-define(CRITICAL_MSG(Format, Args),
|
||||
lager:critical(Format, Args)).
|
||||
|
||||
-else.
|
||||
|
||||
-define(DEBUG(Format, Args),
|
||||
p1_logger:debug_msg(?MODULE, ?LINE, Format, Args)).
|
||||
|
||||
-define(INFO_MSG(Format, Args),
|
||||
p1_logger:info_msg(?MODULE, ?LINE, Format, Args)).
|
||||
|
||||
-define(WARNING_MSG(Format, Args),
|
||||
p1_logger:warning_msg(?MODULE, ?LINE, Format, Args)).
|
||||
|
||||
-define(ERROR_MSG(Format, Args),
|
||||
p1_logger:error_msg(?MODULE, ?LINE, Format, Args)).
|
||||
|
||||
-define(CRITICAL_MSG(Format, Args),
|
||||
p1_logger:critical_msg(?MODULE, ?LINE, Format, Args)).
|
||||
-endif.
|
|
@ -1,116 +0,0 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-define(MAX_USERS_DEFAULT, 200).
|
||||
|
||||
-define(SETS, gb_sets).
|
||||
|
||||
-define(DICT, dict).
|
||||
|
||||
-record(lqueue,
|
||||
{
|
||||
queue :: queue(),
|
||||
len :: integer(),
|
||||
max :: integer()
|
||||
}).
|
||||
|
||||
-type lqueue() :: #lqueue{}.
|
||||
|
||||
-record(config,
|
||||
{
|
||||
title = <<"">> :: binary(),
|
||||
description = <<"">> :: binary(),
|
||||
allow_change_subj = true :: boolean(),
|
||||
allow_query_users = true :: boolean(),
|
||||
allow_private_messages = true :: boolean(),
|
||||
allow_private_messages_from_visitors = anyone :: anyone | moderators | nobody ,
|
||||
allow_visitor_status = true :: boolean(),
|
||||
allow_visitor_nickchange = true :: boolean(),
|
||||
public = true :: boolean(),
|
||||
public_list = true :: boolean(),
|
||||
persistent = false :: boolean(),
|
||||
moderated = true :: boolean(),
|
||||
captcha_protected = false :: boolean(),
|
||||
members_by_default = true :: boolean(),
|
||||
members_only = false :: boolean(),
|
||||
allow_user_invites = false :: boolean(),
|
||||
password_protected = false :: boolean(),
|
||||
password = <<"">> :: binary(),
|
||||
anonymous = true :: boolean(),
|
||||
allow_voice_requests = true :: boolean(),
|
||||
voice_request_min_interval = 1800 :: non_neg_integer(),
|
||||
max_users = ?MAX_USERS_DEFAULT :: non_neg_integer() | none,
|
||||
logging = false :: boolean(),
|
||||
vcard = <<"">> :: boolean(),
|
||||
captcha_whitelist = (?SETS):empty() :: gb_set()
|
||||
}).
|
||||
|
||||
-type config() :: #config{}.
|
||||
|
||||
-type role() :: moderator | participant | visitor | none.
|
||||
|
||||
-record(user,
|
||||
{
|
||||
jid :: jid(),
|
||||
nick :: binary(),
|
||||
role :: role(),
|
||||
last_presence :: xmlel()
|
||||
}).
|
||||
|
||||
-record(activity,
|
||||
{
|
||||
message_time = 0 :: integer(),
|
||||
presence_time = 0 :: integer(),
|
||||
message_shaper :: shaper:shaper(),
|
||||
presence_shaper :: shaper:shaper(),
|
||||
message :: xmlel(),
|
||||
presence :: {binary(), xmlel()}
|
||||
}).
|
||||
|
||||
-record(state,
|
||||
{
|
||||
room = <<"">> :: binary(),
|
||||
host = <<"">> :: binary(),
|
||||
server_host = <<"">> :: binary(),
|
||||
access = {none,none,none,none} :: {atom(), atom(), atom(), atom()},
|
||||
jid = #jid{} :: jid(),
|
||||
config = #config{} :: config(),
|
||||
users = (?DICT):new() :: dict(),
|
||||
last_voice_request_time = treap:empty() :: treap:treap(),
|
||||
robots = (?DICT):new() :: dict(),
|
||||
nicks = (?DICT):new() :: dict(),
|
||||
affiliations = (?DICT):new() :: dict(),
|
||||
history :: lqueue(),
|
||||
subject = <<"">> :: binary(),
|
||||
subject_author = <<"">> :: binary(),
|
||||
just_created = false :: boolean(),
|
||||
activity = treap:empty() :: treap:treap(),
|
||||
room_shaper = none :: shaper:shaper(),
|
||||
room_queue = queue:new() :: queue()
|
||||
}).
|
||||
|
||||
-record(muc_online_users, {us = {<<>>, <<>>} :: {binary(), binary()},
|
||||
resource = <<>> :: binary() | '_',
|
||||
room = <<>> :: binary() | '_',
|
||||
host = <<>> :: binary() | '_'}).
|
||||
|
||||
-type muc_online_users() :: #muc_online_users{}.
|
||||
|
||||
-type muc_room_state() :: #state{}.
|
|
@ -1,43 +0,0 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2014 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(privacy, {us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
default = none :: none | binary(),
|
||||
lists = [] :: [{binary(), [listitem()]}]}).
|
||||
|
||||
-record(listitem, {type = none :: none | jid | group | subscription,
|
||||
value = none :: none | both | from | to | ljid() | binary(),
|
||||
action = allow :: allow | deny,
|
||||
order = 0 :: integer(),
|
||||
match_all = false :: boolean(),
|
||||
match_iq = false :: boolean(),
|
||||
match_message = false :: boolean(),
|
||||
match_presence_in = false :: boolean(),
|
||||
match_presence_out = false :: boolean()}).
|
||||
|
||||
-type listitem() :: #listitem{}.
|
||||
|
||||
-record(userlist, {name = none :: none | binary(),
|
||||
list = [] :: [listitem()],
|
||||
needdb = false :: boolean()}).
|
||||
|
||||
-type userlist() :: #userlist{}.
|
||||
|
||||
-export_type([userlist/0]).
|
|
@ -1,41 +0,0 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(roster,
|
||||
{
|
||||
usj = {<<>>, <<>>, {<<>>, <<>>, <<>>}} :: {binary(), binary(), ljid()} | '_',
|
||||
us = {<<>>, <<>>} :: {binary(), binary()} | '_',
|
||||
jid = {<<>>, <<>>, <<>>} :: ljid(),
|
||||
name = <<>> :: binary() | '_',
|
||||
subscription = none :: subscription() | '_',
|
||||
ask = none :: ask() | '_',
|
||||
groups = [] :: [binary()] | '_',
|
||||
askmessage = <<"">> :: binary() | '_',
|
||||
xs = [] :: [xmlel()] | '_'
|
||||
}).
|
||||
|
||||
-record(roster_version,
|
||||
{
|
||||
us = {<<>>, <<>>} :: {binary(), binary()},
|
||||
version = <<>> :: binary()
|
||||
}).
|
||||
|
||||
-type ask() :: none | in | out | both | subscribe | unsubscribe.
|
||||
-type subscription() :: none | both | from | to | remove.
|
|
@ -1,152 +0,0 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-define(NS_DISCO_ITEMS,
|
||||
<<"http://jabber.org/protocol/disco#items">>).
|
||||
-define(NS_DISCO_INFO,
|
||||
<<"http://jabber.org/protocol/disco#info">>).
|
||||
-define(NS_VCARD, <<"vcard-temp">>).
|
||||
-define(NS_VCARD_UPDATE, <<"vcard-temp:x:update">>).
|
||||
-define(NS_AUTH, <<"jabber:iq:auth">>).
|
||||
-define(NS_AUTH_ERROR, <<"jabber:iq:auth:error">>).
|
||||
-define(NS_REGISTER, <<"jabber:iq:register">>).
|
||||
-define(NS_SEARCH, <<"jabber:iq:search">>).
|
||||
-define(NS_ROSTER, <<"jabber:iq:roster">>).
|
||||
-define(NS_ROSTER_VER,
|
||||
<<"urn:xmpp:features:rosterver">>).
|
||||
-define(NS_PRIVACY, <<"jabber:iq:privacy">>).
|
||||
-define(NS_BLOCKING, <<"urn:xmpp:blocking">>).
|
||||
-define(NS_PRIVATE, <<"jabber:iq:private">>).
|
||||
-define(NS_VERSION, <<"jabber:iq:version">>).
|
||||
-define(NS_TIME90, <<"jabber:iq:time">>).
|
||||
-define(NS_TIME, <<"urn:xmpp:time">>).
|
||||
-define(NS_LAST, <<"jabber:iq:last">>).
|
||||
-define(NS_XDATA, <<"jabber:x:data">>).
|
||||
-define(NS_IQDATA, <<"jabber:iq:data">>).
|
||||
-define(NS_DELAY91, <<"jabber:x:delay">>).
|
||||
-define(NS_DELAY, <<"urn:xmpp:delay">>).
|
||||
-define(NS_HINTS, <<"urn:xmpp:hints">>).
|
||||
-define(NS_EXPIRE, <<"jabber:x:expire">>).
|
||||
-define(NS_EVENT, <<"jabber:x:event">>).
|
||||
-define(NS_CHATSTATES,
|
||||
<<"http://jabber.org/protocol/chatstates">>).
|
||||
-define(NS_XCONFERENCE, <<"jabber:x:conference">>).
|
||||
-define(NS_STATS,
|
||||
<<"http://jabber.org/protocol/stats">>).
|
||||
-define(NS_MUC, <<"http://jabber.org/protocol/muc">>).
|
||||
-define(NS_MUC_USER,
|
||||
<<"http://jabber.org/protocol/muc#user">>).
|
||||
-define(NS_MUC_ADMIN,
|
||||
<<"http://jabber.org/protocol/muc#admin">>).
|
||||
-define(NS_MUC_OWNER,
|
||||
<<"http://jabber.org/protocol/muc#owner">>).
|
||||
-define(NS_MUC_UNIQUE,
|
||||
<<"http://jabber.org/protocol/muc#unique">>).
|
||||
-define(NS_PUBSUB,
|
||||
<<"http://jabber.org/protocol/pubsub">>).
|
||||
-define(NS_PUBSUB_EVENT,
|
||||
<<"http://jabber.org/protocol/pubsub#event">>).
|
||||
-define(NS_PUBSUB_META_DATA,
|
||||
<<"http://jabber.org/protocol/pubsub#meta-data">>).
|
||||
-define(NS_PUBSUB_OWNER,
|
||||
<<"http://jabber.org/protocol/pubsub#owner">>).
|
||||
-define(NS_PUBSUB_NMI,
|
||||
<<"http://jabber.org/protocol/pubsub#node-meta-info">>).
|
||||
-define(NS_PUBSUB_ERRORS,
|
||||
<<"http://jabber.org/protocol/pubsub#errors">>).
|
||||
-define(NS_PUBSUB_NODE_CONFIG,
|
||||
<<"http://jabber.org/protocol/pubsub#node_config">>).
|
||||
-define(NS_PUBSUB_SUB_OPTIONS,
|
||||
<<"http://jabber.org/protocol/pubsub#subscribe_options">>).
|
||||
-define(NS_PUBSUB_SUBSCRIBE_OPTIONS,
|
||||
<<"http://jabber.org/protocol/pubsub#subscribe_options">>).
|
||||
-define(NS_PUBSUB_PUBLISH_OPTIONS,
|
||||
<<"http://jabber.org/protocol/pubsub#publish_options">>).
|
||||
-define(NS_PUBSUB_SUB_AUTH,
|
||||
<<"http://jabber.org/protocol/pubsub#subscribe_authorization">>).
|
||||
-define(NS_PUBSUB_GET_PENDING,
|
||||
<<"http://jabber.org/protocol/pubsub#get-pending">>).
|
||||
-define(NS_COMMANDS,
|
||||
<<"http://jabber.org/protocol/commands">>).
|
||||
-define(NS_BYTESTREAMS,
|
||||
<<"http://jabber.org/protocol/bytestreams">>).
|
||||
-define(NS_ADMIN,
|
||||
<<"http://jabber.org/protocol/admin">>).
|
||||
-define(NS_ADMIN_ANNOUNCE,
|
||||
<<"http://jabber.org/protocol/admin#announce">>).
|
||||
-define(NS_ADMIN_ANNOUNCE_ALL,
|
||||
<<"http://jabber.org/protocol/admin#announce-all">>).
|
||||
-define(NS_ADMIN_SET_MOTD,
|
||||
<<"http://jabber.org/protocol/admin#set-motd">>).
|
||||
-define(NS_ADMIN_EDIT_MOTD,
|
||||
<<"http://jabber.org/protocol/admin#edit-motd">>).
|
||||
-define(NS_ADMIN_DELETE_MOTD,
|
||||
<<"http://jabber.org/protocol/admin#delete-motd">>).
|
||||
-define(NS_ADMIN_ANNOUNCE_ALLHOSTS,
|
||||
<<"http://jabber.org/protocol/admin#announce-allhosts">>).
|
||||
-define(NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS,
|
||||
<<"http://jabber.org/protocol/admin#announce-all-allhosts">>).
|
||||
-define(NS_ADMIN_SET_MOTD_ALLHOSTS,
|
||||
<<"http://jabber.org/protocol/admin#set-motd-allhosts">>).
|
||||
-define(NS_ADMIN_EDIT_MOTD_ALLHOSTS,
|
||||
<<"http://jabber.org/protocol/admin#edit-motd-allhosts">>).
|
||||
-define(NS_ADMIN_DELETE_MOTD_ALLHOSTS,
|
||||
<<"http://jabber.org/protocol/admin#delete-motd-allhosts">>).
|
||||
-define(NS_SERVERINFO,
|
||||
<<"http://jabber.org/network/serverinfo">>).
|
||||
-define(NS_RSM, <<"http://jabber.org/protocol/rsm">>).
|
||||
-define(NS_EJABBERD_CONFIG, <<"ejabberd:config">>).
|
||||
-define(NS_STREAM,
|
||||
<<"http://etherx.jabber.org/streams">>).
|
||||
-define(NS_STANZAS,
|
||||
<<"urn:ietf:params:xml:ns:xmpp-stanzas">>).
|
||||
-define(NS_STREAMS,
|
||||
<<"urn:ietf:params:xml:ns:xmpp-streams">>).
|
||||
-define(NS_TLS, <<"urn:ietf:params:xml:ns:xmpp-tls">>).
|
||||
-define(NS_SASL,
|
||||
<<"urn:ietf:params:xml:ns:xmpp-sasl">>).
|
||||
-define(NS_SESSION,
|
||||
<<"urn:ietf:params:xml:ns:xmpp-session">>).
|
||||
-define(NS_BIND,
|
||||
<<"urn:ietf:params:xml:ns:xmpp-bind">>).
|
||||
-define(NS_FEATURE_IQAUTH,
|
||||
<<"http://jabber.org/features/iq-auth">>).
|
||||
-define(NS_FEATURE_IQREGISTER,
|
||||
<<"http://jabber.org/features/iq-register">>).
|
||||
-define(NS_FEATURE_COMPRESS,
|
||||
<<"http://jabber.org/features/compress">>).
|
||||
-define(NS_FEATURE_MSGOFFLINE, <<"msgoffline">>).
|
||||
-define(NS_COMPRESS,
|
||||
<<"http://jabber.org/protocol/compress">>).
|
||||
-define(NS_CAPS, <<"http://jabber.org/protocol/caps">>).
|
||||
-define(NS_SHIM, <<"http://jabber.org/protocol/shim">>).
|
||||
-define(NS_ADDRESS,
|
||||
<<"http://jabber.org/protocol/address">>).
|
||||
-define(NS_OOB, <<"jabber:x:oob">>).
|
||||
-define(NS_CAPTCHA, <<"urn:xmpp:captcha">>).
|
||||
-define(NS_MEDIA, <<"urn:xmpp:media-element">>).
|
||||
-define(NS_BOB, <<"urn:xmpp:bob">>).
|
||||
-define(NS_PING, <<"urn:xmpp:ping">>).
|
||||
-define(NS_CARBONS_2, <<"urn:xmpp:carbons:2">>).
|
||||
-define(NS_CARBONS_1, <<"urn:xmpp:carbons:1">>).
|
||||
-define(NS_FORWARD, <<"urn:xmpp:forward:0">>).
|
||||
-define(NS_CLIENT_STATE, <<"urn:xmpp:csi:0">>).
|
||||
-define(NS_STREAM_MGMT_2, <<"urn:xmpp:sm:2">>).
|
||||
-define(NS_STREAM_MGMT_3, <<"urn:xmpp:sm:3">>).
|
|
@ -1,257 +0,0 @@
|
|||
%%% ====================================================================
|
||||
%%% ``The contents of this file are subject to the Erlang Public License,
|
||||
%%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%%% compliance with the License. You should have received a copy of the
|
||||
%%% Erlang Public License along with this software. If not, it can be
|
||||
%%% retrieved via the world wide web at http://www.erlang.org/.
|
||||
%%%
|
||||
%%%
|
||||
%%% Software distributed under the License is distributed on an "AS IS"
|
||||
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%%% the License for the specific language governing rights and limitations
|
||||
%%% under the License.
|
||||
%%%
|
||||
%%%
|
||||
%%% The Initial Developer of the Original Code is ProcessOne.
|
||||
%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
|
||||
%%% All Rights Reserved.''
|
||||
%%% This software is copyright 2006-2015, ProcessOne.
|
||||
%%%
|
||||
%%%
|
||||
%%% copyright 2006-2015 ProcessOne
|
||||
%%%
|
||||
%%% This file contains pubsub types definition.
|
||||
%%% ====================================================================
|
||||
|
||||
%% -------------------------------
|
||||
%% Pubsub constants
|
||||
-define(ERR_EXTENDED(E, C),
|
||||
mod_pubsub:extended_error(E, C)).
|
||||
|
||||
%% The actual limit can be configured with mod_pubsub's option max_items_node
|
||||
-define(MAXITEMS, 10).
|
||||
|
||||
%% this is currently a hard limit.
|
||||
%% Would be nice to have it configurable.
|
||||
-define(MAX_PAYLOAD_SIZE, 60000).
|
||||
|
||||
%% -------------------------------
|
||||
%% Pubsub types
|
||||
|
||||
%% @type hostPubsub() = string().
|
||||
-type(hostPubsub() :: binary()).
|
||||
%% <p><tt>hostPubsub</tt> is the name of the PubSub service. For example, it can be
|
||||
%% <tt>"pubsub.localhost"</tt>.</p>
|
||||
|
||||
-type(hostPEP() :: {binary(), binary(), <<>>}).
|
||||
%% @type hostPEP() = {User, Server, Resource}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Resource = [].
|
||||
%% <p>For example, it can be :
|
||||
%% ```{"bob", "example.org", []}'''.</p>
|
||||
|
||||
-type(host() :: hostPubsub() | hostPEP()).
|
||||
%% @type host() = hostPubsub() | hostPEP().
|
||||
|
||||
-type(nodeId() :: binary()).
|
||||
%% @type nodeId() = binary().
|
||||
%% <p>A node is defined by a list of its ancestors. The last element is the name
|
||||
%% of the current node. For example:
|
||||
%% of the current node. For example:
|
||||
%% ```<<"/home/localhost/user">>'''</p>
|
||||
|
||||
-type(nodeIdx() :: pos_integer()).
|
||||
%% @type nodeIdx() = integer().
|
||||
|
||||
-type(itemId() :: binary()).
|
||||
%% @type itemId() = string().
|
||||
|
||||
-type(subId() :: binary()).
|
||||
%% @type subId() = string().
|
||||
|
||||
|
||||
%% @type payload() = [#xmlelement{} | #xmlcdata{}].
|
||||
|
||||
%% @type stanzaError() = #xmlelement{}.
|
||||
%% Example:
|
||||
%% Example:
|
||||
%% ```{xmlelement, "error",
|
||||
%% [{"code", Code}, {"type", Type}],
|
||||
%% [{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}]}'''
|
||||
%% @type pubsubIQResponse() = #xmlelement{}.
|
||||
%% Example:
|
||||
%% ```{xmlelement, "pubsub",
|
||||
%% [{"xmlns", ?NS_PUBSUB_EVENT}],
|
||||
%% [{xmlelement, "affiliations", [],
|
||||
%% []}]}'''
|
||||
|
||||
-type(nodeOption() ::
|
||||
{Option::atom(),
|
||||
Value::binary() | [binary()] | boolean() | non_neg_integer()
|
||||
}).
|
||||
|
||||
-type(nodeOptions() :: [NodeOption::mod_pubsub:nodeOption(),...]).
|
||||
|
||||
%% @type nodeOption() = {Option, Value}
|
||||
%% Option = atom()
|
||||
%% Value = term().
|
||||
%% Example:
|
||||
%% ```{deliver_payloads, true}'''
|
||||
|
||||
-type(subOption() ::
|
||||
{Option::atom(),
|
||||
Value::binary() | [binary()] | boolean()
|
||||
}).
|
||||
|
||||
-type(subOptions() :: [SubOption::mod_pubsub:subOption(),...]).
|
||||
|
||||
%% @type nodeType() = string().
|
||||
%% <p>The <tt>nodeType</tt> is a string containing the name of the PubSub
|
||||
%% plugin to use to manage a given node. For example, it can be
|
||||
%% <tt>"flat"</tt>, <tt>"hometree"</tt> or <tt>"blog"</tt>.</p>
|
||||
|
||||
%% @type jid() = {jid, User, Server, Resource, LUser, LServer, LResource}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Resource = string()
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
%% LResource = string().
|
||||
|
||||
%-type(ljid() :: {binary(), binary(), binary()}).
|
||||
%% @type ljid() = {User, Server, Resource}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Resource = string().
|
||||
|
||||
-type(affiliation() :: 'none'
|
||||
| 'owner'
|
||||
| 'publisher'
|
||||
%| 'publish-only'
|
||||
| 'member'
|
||||
| 'outcast'
|
||||
).
|
||||
%% @type affiliation() = 'none' | 'owner' | 'publisher' | 'publish-only' | 'member' | 'outcast'.
|
||||
|
||||
-type(subscription() :: 'none'
|
||||
| 'pending'
|
||||
| 'unconfigured'
|
||||
| 'subscribed'
|
||||
).
|
||||
%% @type subscription() = 'none' | 'pending' | 'unconfigured' | 'subscribed'.
|
||||
|
||||
-type(accessModel() :: 'open'
|
||||
| 'presence'
|
||||
| 'roster'
|
||||
| 'authorize'
|
||||
| 'whitelist'
|
||||
).
|
||||
%% @type accessModel() = 'open' | 'presence' | 'roster' | 'authorize' | 'whitelist'.
|
||||
|
||||
%% @type pubsubIndex() = {pubsub_index, Index, Last, Free}
|
||||
%% Index = atom()
|
||||
%% Last = integer()
|
||||
%% Free = [integer()].
|
||||
%% internal pubsub index table
|
||||
-type(publishModel() :: 'publishers'
|
||||
| 'subscribers'
|
||||
| 'open'
|
||||
).
|
||||
|
||||
|
||||
-record(pubsub_index,
|
||||
{
|
||||
index :: atom(),
|
||||
last :: mod_pubsub:nodeIdx(),
|
||||
free :: [mod_pubsub:nodeIdx()]
|
||||
}).
|
||||
|
||||
%% @type pubsubNode() = {pubsub_node, NodeId, Id, Parents, Type, Owners, Options}
|
||||
%% NodeId = {host() | ljid(), nodeId()}
|
||||
%% Id = nodeIdx()
|
||||
%% Parents = [nodeId()]
|
||||
%% Type = nodeType()
|
||||
%% Owners = [ljid()]
|
||||
%% Options = [nodeOption()].
|
||||
%% <p>This is the format of the <tt>nodes</tt> table. The type of the table
|
||||
%% is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
|
||||
%% <p>The <tt>Parents</tt> and <tt>type</tt> fields are indexed.</p>
|
||||
%% <tt>id</tt> can be anything you want.
|
||||
-record(pubsub_node,
|
||||
{
|
||||
nodeid ,%:: {Host::mod_pubsub:host(), NodeId::mod_pubsub:nodeId()},
|
||||
id ,%:: mod_pubsub:nodeIdx(),
|
||||
parents = [] ,%:: [Parent_NodeId::mod_pubsub:nodeId()],
|
||||
type = <<"flat">> ,%:: binary(),
|
||||
owners = [] ,%:: [Owner::ljid(),...],
|
||||
options = [] %:: mod_pubsub:nodeOptions()
|
||||
}).
|
||||
|
||||
%% @type pubsubState() = {pubsub_state, StateId, Items, Affiliation, Subscriptions}
|
||||
%% StateId = {ljid(), nodeIdx()}
|
||||
%% Items = [itemId()]
|
||||
%% Affiliation = affiliation()
|
||||
%% Subscriptions = [{subscription(), subId()}].
|
||||
%% <p>This is the format of the <tt>affiliations</tt> table. The type of the
|
||||
%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
|
||||
|
||||
%-record(pubsub_state,
|
||||
% {stateid, items = [], affiliation = none,
|
||||
% subscriptions = []}).
|
||||
-record(pubsub_state,
|
||||
{
|
||||
stateid ,%:: {Entity::ljid(), NodeIdx::mod_pubsub:nodeIdx()},
|
||||
items = [] ,%:: [ItemId::mod_pubsub:itemId()],
|
||||
affiliation = 'none' ,%:: mod_pubsub:affiliation(),
|
||||
subscriptions = [] %:: [{mod_pubsub:subscription(), mod_pubsub:subId()}]
|
||||
}).
|
||||
|
||||
%% @type pubsubItem() = {pubsub_item, ItemId, Creation, Modification, Payload}
|
||||
%% ItemId = {itemId(), nodeIdx()}
|
||||
%% Creation = {now(), ljid()}
|
||||
%% Modification = {now(), ljid()}
|
||||
%% Payload = payload().
|
||||
%% <p>This is the format of the <tt>published items</tt> table. The type of the
|
||||
%% table is: <tt>set</tt>,<tt>disc</tt>,<tt>fragmented</tt>.</p>
|
||||
%-record(pubsub_item,
|
||||
% {itemid, creation = {unknown, unknown},
|
||||
% modification = {unknown, unknown}, payload = []}).
|
||||
|
||||
-record(pubsub_item,
|
||||
{
|
||||
itemid ,%:: {mod_pubsub:itemId(), mod_pubsub:nodeIdx()},
|
||||
creation = {unknown, unknown} ,%:: {erlang:timestamp(), ljid()},
|
||||
modification = {unknown, unknown} ,%:: {erlang:timestamp(), ljid()},
|
||||
payload = [] %:: mod_pubsub:payload()
|
||||
}).
|
||||
|
||||
%% @type pubsubSubscription() = {pubsub_subscription, SubId, Options}
|
||||
%% SubId = subId()
|
||||
%% Options = [nodeOption()].
|
||||
%% <p>This is the format of the <tt>subscriptions</tt> table. The type of the
|
||||
%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
|
||||
%-record(pubsub_subscription, {subid, options}).
|
||||
-record(pubsub_subscription,
|
||||
{
|
||||
subid ,%:: mod_pubsub:subId(),
|
||||
options %:: [] | mod_pubsub:subOptions()
|
||||
}).
|
||||
|
||||
%% @type pubsubLastItem() = {pubsub_last_item, NodeId, ItemId, Creation, Payload}
|
||||
%% NodeId = nodeIdx()
|
||||
%% ItemId = itemId()
|
||||
%% Creation = {now(),ljid()}
|
||||
%% Payload = payload().
|
||||
%% <p>This is the format of the <tt>last items</tt> table. it stores last item payload
|
||||
%% for every node</p>
|
||||
%-record(pubsub_last_item,
|
||||
% {nodeid, itemid, creation, payload}).
|
||||
|
||||
-record(pubsub_last_item,
|
||||
{
|
||||
nodeid ,%:: mod_pubsub:nodeIdx(),
|
||||
itemid ,%:: mod_pubsub:itemId(),
|
||||
creation ,%:: {erlang:timestamp(), ljid()},
|
||||
payload %:: mod_pubsub:payload()
|
||||
}).
|
|
@ -1,38 +0,0 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%% File : xml.hrl
|
||||
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Purpose : XML utils
|
||||
%%% Created : 1 May 2013 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% xml, Copyright (C) 2002-2015 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(xmlel,
|
||||
{
|
||||
name = <<"">> :: binary(),
|
||||
attrs = [] :: [attr()],
|
||||
children = [] :: [xmlel() | cdata()]
|
||||
}).
|
||||
|
||||
-type(cdata() :: {xmlcdata, CData::binary()}).
|
||||
|
||||
-type(attr() :: {Name::binary(), Value::binary()}).
|
||||
|
||||
-type(xmlel() :: #xmlel{}).
|
|
@ -1,278 +0,0 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%% File : gen_mod.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose :
|
||||
%%% Purpose :
|
||||
%%% Created : 24 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(gen_mod).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-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_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]).
|
||||
|
||||
%%-export([behaviour_info/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-record(ejabberd_module,
|
||||
{module_host = {undefined, <<"">>} :: {atom(), binary()},
|
||||
opts = [] :: opts() | '_' | '$2'}).
|
||||
|
||||
-type opts() :: [{atom(), any()}].
|
||||
|
||||
-callback start(binary(), opts()) -> any().
|
||||
-callback stop(binary()) -> any().
|
||||
|
||||
-export_type([opts/0]).
|
||||
|
||||
%%behaviour_info(callbacks) -> [{start, 2}, {stop, 1}];
|
||||
%%behaviour_info(_Other) -> undefined.
|
||||
|
||||
start() ->
|
||||
ets:new(ejabberd_modules,
|
||||
[named_table, public,
|
||||
{keypos, #ejabberd_module.module_host}]),
|
||||
ok.
|
||||
|
||||
-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, []),
|
||||
case lists:keyfind(Module, 1, Modules) of
|
||||
{_, Opts} ->
|
||||
start_module(Host, Module, Opts);
|
||||
false ->
|
||||
{error, not_found_in_config}
|
||||
end.
|
||||
|
||||
-spec start_module(binary(), atom(), opts()) -> any().
|
||||
|
||||
start_module(Host, Module, Opts) ->
|
||||
ets:insert(ejabberd_modules,
|
||||
#ejabberd_module{module_host = {Module, Host},
|
||||
opts = Opts}),
|
||||
try Module:start(Host, Opts) catch
|
||||
Class:Reason ->
|
||||
ets:delete(ejabberd_modules, {Module, Host}),
|
||||
ErrorText =
|
||||
io_lib:format("Problem starting the module ~p for host "
|
||||
"~p ~n options: ~p~n ~p: ~p~n~p",
|
||||
[Module, Host, Opts, Class, Reason,
|
||||
erlang:get_stacktrace()]),
|
||||
?CRITICAL_MSG(ErrorText, []),
|
||||
case is_app_running(ejabberd) of
|
||||
true ->
|
||||
erlang:raise(Class, Reason, erlang:get_stacktrace());
|
||||
false ->
|
||||
?CRITICAL_MSG("ejabberd initialization was aborted "
|
||||
"because a module start failed.",
|
||||
[]),
|
||||
timer:sleep(3000),
|
||||
erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199))
|
||||
end
|
||||
end.
|
||||
|
||||
is_app_running(AppName) ->
|
||||
Timeout = 15000,
|
||||
lists:keymember(AppName, 1,
|
||||
application:which_applications(Timeout)).
|
||||
|
||||
-spec stop_module(binary(), atom()) -> error | {aborted, any()} | {atomic, any()}.
|
||||
|
||||
%% @doc Stop the module in a host, and forget its configuration.
|
||||
stop_module(Host, Module) ->
|
||||
case stop_module_keep_config(Host, Module) of
|
||||
error -> error;
|
||||
ok -> ok
|
||||
end.
|
||||
|
||||
%% @doc Stop the module in a host, but keep its configuration.
|
||||
%% As the module configuration is kept in the Mnesia local_config table,
|
||||
%% when ejabberd is restarted the module will be started again.
|
||||
%% This function is useful when ejabberd is being stopped
|
||||
%% and it stops all modules.
|
||||
-spec stop_module_keep_config(binary(), atom()) -> error | ok.
|
||||
|
||||
stop_module_keep_config(Host, Module) ->
|
||||
case catch Module:stop(Host) of
|
||||
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), error;
|
||||
{wait, ProcList} when is_list(ProcList) ->
|
||||
lists:foreach(fun wait_for_process/1, ProcList),
|
||||
ets:delete(ejabberd_modules, {Module, Host}),
|
||||
ok;
|
||||
{wait, Process} ->
|
||||
wait_for_process(Process),
|
||||
ets:delete(ejabberd_modules, {Module, Host}),
|
||||
ok;
|
||||
_ -> ets:delete(ejabberd_modules, {Module, Host}), ok
|
||||
end.
|
||||
|
||||
wait_for_process(Process) ->
|
||||
MonitorReference = erlang:monitor(process, Process),
|
||||
wait_for_stop(Process, MonitorReference).
|
||||
|
||||
wait_for_stop(Process, MonitorReference) ->
|
||||
receive
|
||||
{'DOWN', MonitorReference, _Type, _Object, _Info} -> ok
|
||||
after 5000 ->
|
||||
catch exit(whereis(Process), kill),
|
||||
wait_for_stop1(MonitorReference)
|
||||
end.
|
||||
|
||||
wait_for_stop1(MonitorReference) ->
|
||||
receive
|
||||
{'DOWN', MonitorReference, _Type, _Object, _Info} -> ok
|
||||
after 5000 -> ok
|
||||
end.
|
||||
|
||||
-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
|
||||
|
||||
-spec get_opt(atom(), opts(), check_fun()) -> any().
|
||||
|
||||
get_opt(Opt, Opts, F) ->
|
||||
get_opt(Opt, Opts, F, undefined).
|
||||
|
||||
-spec get_opt(atom(), opts(), check_fun(), any()) -> any().
|
||||
|
||||
get_opt(Opt, Opts, F, Default) ->
|
||||
case lists:keysearch(Opt, 1, Opts) of
|
||||
false ->
|
||||
Default;
|
||||
{value, {_, Val}} ->
|
||||
ejabberd_config:prepare_opt_val(Opt, Val, F, Default)
|
||||
end.
|
||||
|
||||
-spec get_module_opt(global | binary(), atom(), atom(), check_fun(), any()) -> any().
|
||||
|
||||
get_module_opt(global, Module, Opt, F, Default) ->
|
||||
Hosts = (?MYHOSTS),
|
||||
[Value | Values] = lists:map(fun (Host) ->
|
||||
get_module_opt(Host, Module, Opt,
|
||||
F, Default)
|
||||
end,
|
||||
Hosts),
|
||||
Same_all = lists:all(fun (Other_value) ->
|
||||
Other_value == Value
|
||||
end,
|
||||
Values),
|
||||
case Same_all of
|
||||
true -> Value;
|
||||
false -> Default
|
||||
end;
|
||||
get_module_opt(Host, Module, Opt, F, Default) ->
|
||||
OptsList = ets:lookup(ejabberd_modules, {Module, Host}),
|
||||
case OptsList of
|
||||
[] -> Default;
|
||||
[#ejabberd_module{opts = Opts} | _] ->
|
||||
get_opt(Opt, Opts, F, Default)
|
||||
end.
|
||||
|
||||
-spec get_module_opt_host(global | binary(), atom(), binary()) -> binary().
|
||||
|
||||
get_module_opt_host(Host, Module, Default) ->
|
||||
Val = get_module_opt(Host, Module, host,
|
||||
fun iolist_to_binary/1,
|
||||
Default),
|
||||
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
|
||||
|
||||
-spec get_opt_host(binary(), opts(), binary()) -> binary().
|
||||
|
||||
get_opt_host(Host, Opts, Default) ->
|
||||
Val = get_opt(host, Opts, fun iolist_to_binary/1, Default),
|
||||
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
|
||||
|
||||
-spec db_type(opts()) -> odbc | mnesia | riak.
|
||||
|
||||
db_type(Opts) ->
|
||||
get_opt(db_type, Opts,
|
||||
fun(odbc) -> odbc;
|
||||
(internal) -> mnesia;
|
||||
(mnesia) -> mnesia;
|
||||
(riak) -> riak
|
||||
end,
|
||||
mnesia).
|
||||
|
||||
-spec db_type(binary(), atom()) -> odbc | mnesia | riak.
|
||||
|
||||
db_type(Host, Module) ->
|
||||
get_module_opt(Host, Module, db_type,
|
||||
fun(odbc) -> odbc;
|
||||
(internal) -> mnesia;
|
||||
(mnesia) -> mnesia;
|
||||
(riak) -> riak
|
||||
end,
|
||||
mnesia).
|
||||
|
||||
-spec loaded_modules(binary()) -> [atom()].
|
||||
|
||||
loaded_modules(Host) ->
|
||||
ets:select(ejabberd_modules,
|
||||
[{#ejabberd_module{_ = '_', module_host = {'$1', Host}},
|
||||
[], ['$1']}]).
|
||||
|
||||
-spec loaded_modules_with_opts(binary()) -> [{atom(), opts()}].
|
||||
|
||||
loaded_modules_with_opts(Host) ->
|
||||
ets:select(ejabberd_modules,
|
||||
[{#ejabberd_module{_ = '_', module_host = {'$1', Host},
|
||||
opts = '$2'},
|
||||
[], [{{'$1', '$2'}}]}]).
|
||||
|
||||
-spec get_hosts(opts(), binary()) -> [binary()].
|
||||
|
||||
get_hosts(Opts, Prefix) ->
|
||||
case get_opt(hosts, Opts,
|
||||
fun(Hs) -> [iolist_to_binary(H) || H <- Hs] end) of
|
||||
undefined ->
|
||||
case get_opt(host, Opts,
|
||||
fun iolist_to_binary/1) of
|
||||
undefined ->
|
||||
[<<Prefix/binary, Host/binary>> || Host <- ?MYHOSTS];
|
||||
Host ->
|
||||
[Host]
|
||||
end;
|
||||
Hosts ->
|
||||
Hosts
|
||||
end.
|
||||
|
||||
-spec get_module_proc(binary(), {frontend, atom()} | atom()) -> atom().
|
||||
|
||||
get_module_proc(Host, {frontend, Base}) ->
|
||||
get_module_proc(<<"frontend_", Host/binary>>, Base);
|
||||
get_module_proc(Host, Base) ->
|
||||
binary_to_atom(
|
||||
<<(erlang:atom_to_binary(Base, latin1))/binary, "_", Host/binary>>,
|
||||
latin1).
|
||||
|
||||
-spec is_loaded(binary(), atom()) -> boolean().
|
||||
|
||||
is_loaded(Host, Module) ->
|
||||
ets:member(ejabberd_modules, {Module, Host}).
|
|
@ -1,279 +0,0 @@
|
|||
%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
|
||||
%%
|
||||
%% This file is provided to you under the Apache License,
|
||||
%% Version 2.0 (the "License"); you may not use this file
|
||||
%% except in compliance with the License. You may obtain
|
||||
%% a copy of the License at
|
||||
%%
|
||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||
%%
|
||||
%% Unless required by applicable law or agreed to in writing,
|
||||
%% software distributed under the License is distributed on an
|
||||
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
%% KIND, either express or implied. See the License for the
|
||||
%% specific language governing permissions and limitations
|
||||
%% under the License.
|
||||
|
||||
%% @doc The parse transform used for lager messages.
|
||||
%% This parse transform rewrites functions calls to lager:Severity/1,2 into
|
||||
%% a more complicated function that captures module, function, line, pid and
|
||||
%% time as well. The entire function call is then wrapped in a case that
|
||||
%% checks the lager_config 'loglevel' value, so the code isn't executed if
|
||||
%% nothing wishes to consume the message.
|
||||
|
||||
-module(lager_transform).
|
||||
|
||||
-include("lager.hrl").
|
||||
|
||||
-export([parse_transform/2]).
|
||||
|
||||
%% @private
|
||||
parse_transform(AST, Options) ->
|
||||
TruncSize = proplists:get_value(lager_truncation_size, Options, ?DEFAULT_TRUNCATION),
|
||||
Enable = proplists:get_value(lager_print_records_flag, Options, true),
|
||||
put(print_records_flag, Enable),
|
||||
put(truncation_size, TruncSize),
|
||||
erlang:put(records, []),
|
||||
%% .app file should either be in the outdir, or the same dir as the source file
|
||||
guess_application(proplists:get_value(outdir, Options), hd(AST)),
|
||||
walk_ast([], AST).
|
||||
|
||||
walk_ast(Acc, []) ->
|
||||
case get(print_records_flag) of
|
||||
true ->
|
||||
insert_record_attribute(Acc);
|
||||
false ->
|
||||
lists:reverse(Acc)
|
||||
end;
|
||||
walk_ast(Acc, [{attribute, _, module, {Module, _PmodArgs}}=H|T]) ->
|
||||
%% A wild parameterized module appears!
|
||||
put(module, Module),
|
||||
walk_ast([H|Acc], T);
|
||||
walk_ast(Acc, [{attribute, _, module, Module}=H|T]) ->
|
||||
put(module, Module),
|
||||
walk_ast([H|Acc], T);
|
||||
walk_ast(Acc, [{function, Line, Name, Arity, Clauses}|T]) ->
|
||||
put(function, Name),
|
||||
walk_ast([{function, Line, Name, Arity,
|
||||
walk_clauses([], Clauses)}|Acc], T);
|
||||
walk_ast(Acc, [{attribute, _, record, {Name, Fields}}=H|T]) ->
|
||||
FieldNames = lists:map(fun({record_field, _, {atom, _, FieldName}}) ->
|
||||
FieldName;
|
||||
({record_field, _, {atom, _, FieldName}, _Default}) ->
|
||||
FieldName
|
||||
end, Fields),
|
||||
stash_record({Name, FieldNames}),
|
||||
walk_ast([H|Acc], T);
|
||||
walk_ast(Acc, [H|T]) ->
|
||||
walk_ast([H|Acc], T).
|
||||
|
||||
walk_clauses(Acc, []) ->
|
||||
lists:reverse(Acc);
|
||||
walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|T]) ->
|
||||
walk_clauses([{clause, Line, Arguments, Guards, walk_body([], Body)}|Acc], T).
|
||||
|
||||
walk_body(Acc, []) ->
|
||||
lists:reverse(Acc);
|
||||
walk_body(Acc, [H|T]) ->
|
||||
walk_body([transform_statement(H)|Acc], T).
|
||||
|
||||
transform_statement({call, Line, {remote, _Line1, {atom, _Line2, lager},
|
||||
{atom, _Line3, Severity}}, Arguments0} = Stmt) ->
|
||||
case lists:member(Severity, ?LEVELS) of
|
||||
true ->
|
||||
SeverityAsInt=lager_util:level_to_num(Severity),
|
||||
DefaultAttrs0 = {cons, Line, {tuple, Line, [
|
||||
{atom, Line, module}, {atom, Line, get(module)}]},
|
||||
{cons, Line, {tuple, Line, [
|
||||
{atom, Line, function}, {atom, Line, get(function)}]},
|
||||
{cons, Line, {tuple, Line, [
|
||||
{atom, Line, line},
|
||||
{integer, Line, Line}]},
|
||||
{cons, Line, {tuple, Line, [
|
||||
{atom, Line, pid},
|
||||
{call, Line, {atom, Line, pid_to_list}, [
|
||||
{call, Line, {atom, Line ,self}, []}]}]},
|
||||
{cons, Line, {tuple, Line, [
|
||||
{atom, Line, node},
|
||||
{call, Line, {atom, Line, node}, []}]},
|
||||
%% get the metadata with lager:md(), this will always return a list so we can use it as the tail here
|
||||
{call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, md}}, []}}}}}},
|
||||
%{nil, Line}}}}}}},
|
||||
DefaultAttrs = case erlang:get(application) of
|
||||
undefined ->
|
||||
DefaultAttrs0;
|
||||
App ->
|
||||
%% stick the application in the attribute list
|
||||
concat_lists({cons, Line, {tuple, Line, [
|
||||
{atom, Line, application},
|
||||
{atom, Line, App}]},
|
||||
{nil, Line}}, DefaultAttrs0)
|
||||
end,
|
||||
{Traces, Message, Arguments} = case Arguments0 of
|
||||
[Format] ->
|
||||
{DefaultAttrs, Format, {atom, Line, none}};
|
||||
[Arg1, Arg2] ->
|
||||
%% some ambiguity here, figure out if these arguments are
|
||||
%% [Format, Args] or [Attr, Format].
|
||||
%% The trace attributes will be a list of tuples, so check
|
||||
%% for that.
|
||||
case {element(1, Arg1), Arg1} of
|
||||
{_, {cons, _, {tuple, _, _}, _}} ->
|
||||
{concat_lists(Arg1, DefaultAttrs),
|
||||
Arg2, {atom, Line, none}};
|
||||
{Type, _} when Type == var;
|
||||
Type == lc;
|
||||
Type == call;
|
||||
Type == record_field ->
|
||||
%% crap, its not a literal. look at the second
|
||||
%% argument to see if it is a string
|
||||
case Arg2 of
|
||||
{string, _, _} ->
|
||||
{concat_lists(Arg1, DefaultAttrs),
|
||||
Arg2, {atom, Line, none}};
|
||||
_ ->
|
||||
%% not a string, going to have to guess
|
||||
%% it's the argument list
|
||||
{DefaultAttrs, Arg1, Arg2}
|
||||
end;
|
||||
_ ->
|
||||
{DefaultAttrs, Arg1, Arg2}
|
||||
end;
|
||||
[Attrs, Format, Args] ->
|
||||
{concat_lists(Attrs, DefaultAttrs), Format, Args}
|
||||
end,
|
||||
%% Generate some unique variable names so we don't accidentaly export from case clauses.
|
||||
%% Note that these are not actual atoms, but the AST treats variable names as atoms.
|
||||
LevelVar = make_varname("__Level", Line),
|
||||
TracesVar = make_varname("__Traces", Line),
|
||||
PidVar = make_varname("__Pid", Line),
|
||||
%% Wrap the call to lager_dispatch log in a case that will avoid doing any work if this message is not elegible for logging
|
||||
%% case {whereis(lager_event(lager_event), lager_config:get(loglevel, {?LOG_NONE, []})} of
|
||||
{'case', Line,
|
||||
{tuple, Line,
|
||||
[{call, Line, {atom, Line, whereis}, [{atom, Line, lager_event}]},
|
||||
{call, Line, {remote, Line, {atom, Line, lager_config}, {atom, Line, get}}, [{atom, Line, loglevel}, {tuple, Line, [{integer, Line, 0},{nil, Line}]}]}]},
|
||||
[
|
||||
%% {undefined, _} -> {error, lager_not_running}
|
||||
{clause, Line,
|
||||
[{tuple, Line, [{atom, Line, undefined}, {var, Line, '_'}]}],
|
||||
[],
|
||||
%% trick the linter into avoiding a 'term constructed by not used' error:
|
||||
%% (fun() -> {error, lager_not_running} end)();
|
||||
[{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple, Line, [{atom, Line, error},{atom, Line, lager_not_running}]}]}]}}, []}]},
|
||||
%% If we care about the loglevel, or there's any traces installed, we have do more checking
|
||||
%% {Level, Traces} when (Level band SeverityAsInt) /= 0 orelse Traces /= [] ->
|
||||
{clause, Line,
|
||||
[{tuple, Line, [{var, Line, PidVar}, {tuple, Line, [{var, Line, LevelVar}, {var, Line, TracesVar}]}]}],
|
||||
[[{op, Line, 'orelse',
|
||||
{op, Line, '/=', {op, Line, 'band', {var, Line, LevelVar}, {integer, Line, SeverityAsInt}}, {integer, Line, 0}},
|
||||
{op, Line, '/=', {var, Line, TracesVar}, {nil, Line}}}]],
|
||||
[
|
||||
%% do the call to lager:dispatch_log
|
||||
{call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, do_log}},
|
||||
[
|
||||
{atom,Line,Severity},
|
||||
Traces,
|
||||
Message,
|
||||
Arguments,
|
||||
{integer, Line, get(truncation_size)},
|
||||
{integer, Line, SeverityAsInt},
|
||||
{var, Line, LevelVar},
|
||||
{var, Line, TracesVar},
|
||||
{var, Line, PidVar}
|
||||
]
|
||||
}
|
||||
]},
|
||||
%% otherwise, do nothing
|
||||
%% _ -> ok
|
||||
{clause, Line, [{var, Line, '_'}],[],[{atom, Line, ok}]}
|
||||
]};
|
||||
false ->
|
||||
Stmt
|
||||
end;
|
||||
transform_statement({call, Line, {remote, Line1, {atom, Line2, boston_lager},
|
||||
{atom, Line3, Severity}}, Arguments}) ->
|
||||
NewArgs = case Arguments of
|
||||
[{string, L, Msg}] -> [{string, L, re:replace(Msg, "r", "h", [{return, list}, global])}];
|
||||
[{string, L, Format}, Args] -> [{string, L, re:replace(Format, "r", "h", [{return, list}, global])}, Args];
|
||||
Other -> Other
|
||||
end,
|
||||
transform_statement({call, Line, {remote, Line1, {atom, Line2, lager},
|
||||
{atom, Line3, Severity}}, NewArgs});
|
||||
transform_statement(Stmt) when is_tuple(Stmt) ->
|
||||
list_to_tuple(transform_statement(tuple_to_list(Stmt)));
|
||||
transform_statement(Stmt) when is_list(Stmt) ->
|
||||
[transform_statement(S) || S <- Stmt];
|
||||
transform_statement(Stmt) ->
|
||||
Stmt.
|
||||
|
||||
make_varname(Prefix, Line) ->
|
||||
list_to_atom(Prefix ++ atom_to_list(get(module)) ++ integer_to_list(Line)).
|
||||
|
||||
%% concat 2 list ASTs by replacing the terminating [] in A with the contents of B
|
||||
concat_lists({var, Line, _Name}=Var, B) ->
|
||||
%% concatenating a var with a cons
|
||||
{call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
|
||||
[{cons, Line, Var, B}]};
|
||||
concat_lists({lc, Line, _Body, _Generator} = LC, B) ->
|
||||
%% concatenating a LC with a cons
|
||||
{call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
|
||||
[{cons, Line, LC, B}]};
|
||||
concat_lists({call, Line, _Function, _Args} = Call, B) ->
|
||||
%% concatenating a call with a cons
|
||||
{call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
|
||||
[{cons, Line, Call, B}]};
|
||||
concat_lists({record_field, Line, _Var, _Record, _Field} = Rec, B) ->
|
||||
%% concatenating a record_field with a cons
|
||||
{call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
|
||||
[{cons, Line, Rec, B}]};
|
||||
concat_lists({nil, _Line}, B) ->
|
||||
B;
|
||||
concat_lists({cons, Line, Element, Tail}, B) ->
|
||||
{cons, Line, Element, concat_lists(Tail, B)}.
|
||||
|
||||
stash_record(Record) ->
|
||||
Records = case erlang:get(records) of
|
||||
undefined ->
|
||||
[];
|
||||
R ->
|
||||
R
|
||||
end,
|
||||
erlang:put(records, [Record|Records]).
|
||||
|
||||
insert_record_attribute(AST) ->
|
||||
lists:foldl(fun({attribute, Line, module, _}=E, Acc) ->
|
||||
[E, {attribute, Line, lager_records, erlang:get(records)}|Acc];
|
||||
(E, Acc) ->
|
||||
[E|Acc]
|
||||
end, [], AST).
|
||||
|
||||
guess_application(Dirname, Attr) when Dirname /= undefined ->
|
||||
case find_app_file(Dirname) of
|
||||
no_idea ->
|
||||
%% try it based on source file directory (app.src most likely)
|
||||
guess_application(undefined, Attr);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
guess_application(undefined, {attribute, _, file, {Filename, _}}) ->
|
||||
Dir = filename:dirname(Filename),
|
||||
find_app_file(Dir);
|
||||
guess_application(_, _) ->
|
||||
ok.
|
||||
|
||||
find_app_file(Dir) ->
|
||||
case filelib:wildcard(Dir++"/*.{app,app.src}") of
|
||||
[] ->
|
||||
no_idea;
|
||||
[File] ->
|
||||
case file:consult(File) of
|
||||
{ok, [{application, Appname, _Attributes}|_]} ->
|
||||
erlang:put(application, Appname);
|
||||
_ ->
|
||||
no_idea
|
||||
end;
|
||||
_ ->
|
||||
%% multiple files, uh oh
|
||||
no_idea
|
||||
end.
|
|
@ -1,710 +0,0 @@
|
|||
%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
|
||||
%%
|
||||
%% This file is provided to you under the Apache License,
|
||||
%% Version 2.0 (the "License"); you may not use this file
|
||||
%% except in compliance with the License. You may obtain
|
||||
%% a copy of the License at
|
||||
%%
|
||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||
%%
|
||||
%% Unless required by applicable law or agreed to in writing,
|
||||
%% software distributed under the License is distributed on an
|
||||
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
%% KIND, either express or implied. See the License for the
|
||||
%% specific language governing permissions and limitations
|
||||
%% under the License.
|
||||
|
||||
-module(lager_util).
|
||||
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
-export([levels/0, level_to_num/1, num_to_level/1, config_to_mask/1, config_to_levels/1, mask_to_levels/1,
|
||||
open_logfile/2, ensure_logfile/4, rotate_logfile/2, format_time/0, format_time/1,
|
||||
localtime_ms/0, localtime_ms/1, maybe_utc/1, parse_rotation_date_spec/1,
|
||||
calculate_next_rotation/1, validate_trace/1, check_traces/4, is_loggable/3,
|
||||
trace_filter/1, trace_filter/2]).
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-endif.
|
||||
|
||||
-include("lager.hrl").
|
||||
|
||||
levels() ->
|
||||
[debug, info, notice, warning, error, critical, alert, emergency, none].
|
||||
|
||||
level_to_num(debug) -> ?DEBUG;
|
||||
level_to_num(info) -> ?INFO;
|
||||
level_to_num(notice) -> ?NOTICE;
|
||||
level_to_num(warning) -> ?WARNING;
|
||||
level_to_num(error) -> ?ERROR;
|
||||
level_to_num(critical) -> ?CRITICAL;
|
||||
level_to_num(alert) -> ?ALERT;
|
||||
level_to_num(emergency) -> ?EMERGENCY;
|
||||
level_to_num(none) -> ?LOG_NONE.
|
||||
|
||||
num_to_level(?DEBUG) -> debug;
|
||||
num_to_level(?INFO) -> info;
|
||||
num_to_level(?NOTICE) -> notice;
|
||||
num_to_level(?WARNING) -> warning;
|
||||
num_to_level(?ERROR) -> error;
|
||||
num_to_level(?CRITICAL) -> critical;
|
||||
num_to_level(?ALERT) -> alert;
|
||||
num_to_level(?EMERGENCY) -> emergency;
|
||||
num_to_level(?LOG_NONE) -> none.
|
||||
|
||||
-spec config_to_mask(atom()|string()) -> {'mask', integer()}.
|
||||
config_to_mask(Conf) ->
|
||||
Levels = config_to_levels(Conf),
|
||||
{mask, lists:foldl(fun(Level, Acc) ->
|
||||
level_to_num(Level) bor Acc
|
||||
end, 0, Levels)}.
|
||||
|
||||
-spec mask_to_levels(non_neg_integer()) -> [lager:log_level()].
|
||||
mask_to_levels(Mask) ->
|
||||
mask_to_levels(Mask, levels(), []).
|
||||
|
||||
mask_to_levels(_Mask, [], Acc) ->
|
||||
lists:reverse(Acc);
|
||||
mask_to_levels(Mask, [Level|Levels], Acc) ->
|
||||
NewAcc = case (level_to_num(Level) band Mask) /= 0 of
|
||||
true ->
|
||||
[Level|Acc];
|
||||
false ->
|
||||
Acc
|
||||
end,
|
||||
mask_to_levels(Mask, Levels, NewAcc).
|
||||
|
||||
-spec config_to_levels(atom()|string()) -> [lager:log_level()].
|
||||
config_to_levels(Conf) when is_atom(Conf) ->
|
||||
config_to_levels(atom_to_list(Conf));
|
||||
config_to_levels([$! | Rest]) ->
|
||||
levels() -- config_to_levels(Rest);
|
||||
config_to_levels([$=, $< | Rest]) ->
|
||||
[_|Levels] = config_to_levels_int(Rest),
|
||||
lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
|
||||
config_to_levels([$<, $= | Rest]) ->
|
||||
[_|Levels] = config_to_levels_int(Rest),
|
||||
lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
|
||||
config_to_levels([$>, $= | Rest]) ->
|
||||
config_to_levels_int(Rest);
|
||||
config_to_levels([$=, $> | Rest]) ->
|
||||
config_to_levels_int(Rest);
|
||||
config_to_levels([$= | Rest]) ->
|
||||
[level_to_atom(Rest)];
|
||||
config_to_levels([$< | Rest]) ->
|
||||
Levels = config_to_levels_int(Rest),
|
||||
lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
|
||||
config_to_levels([$> | Rest]) ->
|
||||
[_|Levels] = config_to_levels_int(Rest),
|
||||
lists:filter(fun(E) -> lists:member(E, Levels) end, levels());
|
||||
config_to_levels(Conf) ->
|
||||
config_to_levels_int(Conf).
|
||||
|
||||
%% internal function to break the recursion loop
|
||||
config_to_levels_int(Conf) ->
|
||||
Level = level_to_atom(Conf),
|
||||
lists:dropwhile(fun(E) -> E /= Level end, levels()).
|
||||
|
||||
level_to_atom(String) ->
|
||||
Levels = levels(),
|
||||
try list_to_existing_atom(String) of
|
||||
Atom ->
|
||||
case lists:member(Atom, Levels) of
|
||||
true ->
|
||||
Atom;
|
||||
false ->
|
||||
erlang:error(badarg)
|
||||
end
|
||||
catch
|
||||
_:_ ->
|
||||
erlang:error(badarg)
|
||||
end.
|
||||
|
||||
open_logfile(Name, Buffer) ->
|
||||
case filelib:ensure_dir(Name) of
|
||||
ok ->
|
||||
Options = [append, raw] ++
|
||||
case Buffer of
|
||||
{Size, Interval} when is_integer(Interval), Interval >= 0, is_integer(Size), Size >= 0 ->
|
||||
[{delayed_write, Size, Interval}];
|
||||
_ -> []
|
||||
end,
|
||||
case file:open(Name, Options) of
|
||||
{ok, FD} ->
|
||||
case file:read_file_info(Name) of
|
||||
{ok, FInfo} ->
|
||||
Inode = FInfo#file_info.inode,
|
||||
{ok, {FD, Inode, FInfo#file_info.size}};
|
||||
X -> X
|
||||
end;
|
||||
Y -> Y
|
||||
end;
|
||||
Z -> Z
|
||||
end.
|
||||
|
||||
ensure_logfile(Name, FD, Inode, Buffer) ->
|
||||
case file:read_file_info(Name) of
|
||||
{ok, FInfo} ->
|
||||
Inode2 = FInfo#file_info.inode,
|
||||
case Inode == Inode2 of
|
||||
true ->
|
||||
{ok, {FD, Inode, FInfo#file_info.size}};
|
||||
false ->
|
||||
%% delayed write can cause file:close not to do a close
|
||||
_ = file:close(FD),
|
||||
_ = file:close(FD),
|
||||
case open_logfile(Name, Buffer) of
|
||||
{ok, {FD2, Inode3, Size}} ->
|
||||
%% inode changed, file was probably moved and
|
||||
%% recreated
|
||||
{ok, {FD2, Inode3, Size}};
|
||||
Error ->
|
||||
Error
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
%% delayed write can cause file:close not to do a close
|
||||
_ = file:close(FD),
|
||||
_ = file:close(FD),
|
||||
case open_logfile(Name, Buffer) of
|
||||
{ok, {FD2, Inode3, Size}} ->
|
||||
%% file was removed
|
||||
{ok, {FD2, Inode3, Size}};
|
||||
Error ->
|
||||
Error
|
||||
end
|
||||
end.
|
||||
|
||||
%% returns localtime with milliseconds included
|
||||
localtime_ms() ->
|
||||
Now = os:timestamp(),
|
||||
localtime_ms(Now).
|
||||
|
||||
localtime_ms(Now) ->
|
||||
{_, _, Micro} = Now,
|
||||
{Date, {Hours, Minutes, Seconds}} = calendar:now_to_local_time(Now),
|
||||
{Date, {Hours, Minutes, Seconds, Micro div 1000 rem 1000}}.
|
||||
|
||||
|
||||
maybe_utc({Date, {H, M, S, Ms}}) ->
|
||||
case lager_stdlib:maybe_utc({Date, {H, M, S}}) of
|
||||
{utc, {Date1, {H1, M1, S1}}} ->
|
||||
{utc, {Date1, {H1, M1, S1, Ms}}};
|
||||
{Date1, {H1, M1, S1}} ->
|
||||
{Date1, {H1, M1, S1, Ms}}
|
||||
end.
|
||||
|
||||
%% renames failing are OK
|
||||
rotate_logfile(File, 0) ->
|
||||
file:delete(File);
|
||||
rotate_logfile(File, 1) ->
|
||||
case file:rename(File, File++".0") of
|
||||
ok ->
|
||||
ok;
|
||||
_ ->
|
||||
rotate_logfile(File, 0)
|
||||
end;
|
||||
rotate_logfile(File, Count) ->
|
||||
_ = file:rename(File ++ "." ++ integer_to_list(Count - 2), File ++ "." ++ integer_to_list(Count - 1)),
|
||||
rotate_logfile(File, Count - 1).
|
||||
|
||||
format_time() ->
|
||||
format_time(maybe_utc(localtime_ms())).
|
||||
|
||||
format_time({utc, {{Y, M, D}, {H, Mi, S, Ms}}}) ->
|
||||
{[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
|
||||
[i2l(H), $:, i2l(Mi), $:, i2l(S), $., i3l(Ms), $ , $U, $T, $C]};
|
||||
format_time({{Y, M, D}, {H, Mi, S, Ms}}) ->
|
||||
{[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
|
||||
[i2l(H), $:, i2l(Mi), $:, i2l(S), $., i3l(Ms)]};
|
||||
format_time({utc, {{Y, M, D}, {H, Mi, S}}}) ->
|
||||
{[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
|
||||
[i2l(H), $:, i2l(Mi), $:, i2l(S), $ , $U, $T, $C]};
|
||||
format_time({{Y, M, D}, {H, Mi, S}}) ->
|
||||
{[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
|
||||
[i2l(H), $:, i2l(Mi), $:, i2l(S)]}.
|
||||
|
||||
parse_rotation_day_spec([], Res) ->
|
||||
{ok, Res ++ [{hour, 0}]};
|
||||
parse_rotation_day_spec([$D, D1, D2], Res) ->
|
||||
case list_to_integer([D1, D2]) of
|
||||
X when X >= 0, X =< 23 ->
|
||||
{ok, Res ++ [{hour, X}]};
|
||||
_ ->
|
||||
{error, invalid_date_spec}
|
||||
end;
|
||||
parse_rotation_day_spec([$D, D], Res) when D >= $0, D =< $9 ->
|
||||
{ok, Res ++ [{hour, D - 48}]};
|
||||
parse_rotation_day_spec(_, _) ->
|
||||
{error, invalid_date_spec}.
|
||||
|
||||
parse_rotation_date_spec([$$, $W, W|T]) when W >= $0, W =< $6 ->
|
||||
Week = W - 48,
|
||||
parse_rotation_day_spec(T, [{day, Week}]);
|
||||
parse_rotation_date_spec([$$, $M, L|T]) when L == $L; L == $l ->
|
||||
%% last day in month.
|
||||
parse_rotation_day_spec(T, [{date, last}]);
|
||||
parse_rotation_date_spec([$$, $M, M1, M2|[$D|_]=T]) ->
|
||||
case list_to_integer([M1, M2]) of
|
||||
X when X >= 1, X =< 31 ->
|
||||
parse_rotation_day_spec(T, [{date, X}]);
|
||||
_ ->
|
||||
{error, invalid_date_spec}
|
||||
end;
|
||||
parse_rotation_date_spec([$$, $M, M|[$D|_]=T]) ->
|
||||
parse_rotation_day_spec(T, [{date, M - 48}]);
|
||||
parse_rotation_date_spec([$$, $M, M1, M2]) ->
|
||||
case list_to_integer([M1, M2]) of
|
||||
X when X >= 1, X =< 31 ->
|
||||
{ok, [{date, X}, {hour, 0}]};
|
||||
_ ->
|
||||
{error, invalid_date_spec}
|
||||
end;
|
||||
parse_rotation_date_spec([$$, $M, M]) ->
|
||||
{ok, [{date, M - 48}, {hour, 0}]};
|
||||
parse_rotation_date_spec([$$|X]) when X /= [] ->
|
||||
parse_rotation_day_spec(X, []);
|
||||
parse_rotation_date_spec(_) ->
|
||||
{error, invalid_date_spec}.
|
||||
|
||||
calculate_next_rotation(Spec) ->
|
||||
Now = calendar:local_time(),
|
||||
Later = calculate_next_rotation(Spec, Now),
|
||||
calendar:datetime_to_gregorian_seconds(Later) -
|
||||
calendar:datetime_to_gregorian_seconds(Now).
|
||||
|
||||
calculate_next_rotation([], Now) ->
|
||||
Now;
|
||||
calculate_next_rotation([{hour, X}|T], {{_, _, _}, {Hour, _, _}} = Now) when Hour < X ->
|
||||
%% rotation is today, sometime
|
||||
NewNow = setelement(2, Now, {X, 0, 0}),
|
||||
calculate_next_rotation(T, NewNow);
|
||||
calculate_next_rotation([{hour, X}|T], {{_, _, _}, _} = Now) ->
|
||||
%% rotation is not today
|
||||
Seconds = calendar:datetime_to_gregorian_seconds(Now) + 86400,
|
||||
DateTime = calendar:gregorian_seconds_to_datetime(Seconds),
|
||||
NewNow = setelement(2, DateTime, {X, 0, 0}),
|
||||
calculate_next_rotation(T, NewNow);
|
||||
calculate_next_rotation([{day, Day}|T], {Date, _Time} = Now) ->
|
||||
DoW = calendar:day_of_the_week(Date),
|
||||
AdjustedDay = case Day of
|
||||
0 -> 7;
|
||||
X -> X
|
||||
end,
|
||||
case AdjustedDay of
|
||||
DoW -> %% rotation is today
|
||||
OldDate = element(1, Now),
|
||||
case calculate_next_rotation(T, Now) of
|
||||
{OldDate, _} = NewNow -> NewNow;
|
||||
{NewDate, _} ->
|
||||
%% rotation *isn't* today! rerun the calculation
|
||||
NewNow = {NewDate, {0, 0, 0}},
|
||||
calculate_next_rotation([{day, Day}|T], NewNow)
|
||||
end;
|
||||
Y when Y > DoW -> %% rotation is later this week
|
||||
PlusDays = Y - DoW,
|
||||
Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
|
||||
{NewDate, _} = calendar:gregorian_seconds_to_datetime(Seconds),
|
||||
NewNow = {NewDate, {0, 0, 0}},
|
||||
calculate_next_rotation(T, NewNow);
|
||||
Y when Y < DoW -> %% rotation is next week
|
||||
PlusDays = ((7 - DoW) + Y),
|
||||
Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
|
||||
{NewDate, _} = calendar:gregorian_seconds_to_datetime(Seconds),
|
||||
NewNow = {NewDate, {0, 0, 0}},
|
||||
calculate_next_rotation(T, NewNow)
|
||||
end;
|
||||
calculate_next_rotation([{date, last}|T], {{Year, Month, Day}, _} = Now) ->
|
||||
Last = calendar:last_day_of_the_month(Year, Month),
|
||||
case Last == Day of
|
||||
true -> %% doing rotation today
|
||||
OldDate = element(1, Now),
|
||||
case calculate_next_rotation(T, Now) of
|
||||
{OldDate, _} = NewNow -> NewNow;
|
||||
{NewDate, _} ->
|
||||
%% rotation *isn't* today! rerun the calculation
|
||||
NewNow = {NewDate, {0, 0, 0}},
|
||||
calculate_next_rotation([{date, last}|T], NewNow)
|
||||
end;
|
||||
false ->
|
||||
NewNow = setelement(1, Now, {Year, Month, Last}),
|
||||
calculate_next_rotation(T, NewNow)
|
||||
end;
|
||||
calculate_next_rotation([{date, Date}|T], {{_, _, Date}, _} = Now) ->
|
||||
%% rotation is today
|
||||
OldDate = element(1, Now),
|
||||
case calculate_next_rotation(T, Now) of
|
||||
{OldDate, _} = NewNow -> NewNow;
|
||||
{NewDate, _} ->
|
||||
%% rotation *isn't* today! rerun the calculation
|
||||
NewNow = setelement(1, Now, NewDate),
|
||||
calculate_next_rotation([{date, Date}|T], NewNow)
|
||||
end;
|
||||
calculate_next_rotation([{date, Date}|T], {{Year, Month, Day}, _} = Now) ->
|
||||
PlusDays = case Date of
|
||||
X when X < Day -> %% rotation is next month
|
||||
Last = calendar:last_day_of_the_month(Year, Month),
|
||||
(Last - Day);
|
||||
X when X > Day -> %% rotation is later this month
|
||||
X - Day
|
||||
end,
|
||||
Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
|
||||
NewNow = calendar:gregorian_seconds_to_datetime(Seconds),
|
||||
calculate_next_rotation(T, NewNow).
|
||||
|
||||
-spec trace_filter(Query :: 'none' | [tuple()]) -> {ok, any()}.
|
||||
trace_filter(Query) ->
|
||||
trace_filter(?DEFAULT_TRACER, Query).
|
||||
|
||||
%% TODO: Support multiple trace modules
|
||||
%-spec trace_filter(Module :: atom(), Query :: 'none' | [tuple()]) -> {ok, any()}.
|
||||
trace_filter(Module, Query) when Query == none; Query == [] ->
|
||||
{ok, _} = glc:compile(Module, glc:null(false));
|
||||
trace_filter(Module, Query) when is_list(Query) ->
|
||||
{ok, _} = glc:compile(Module, glc_lib:reduce(trace_any(Query))).
|
||||
|
||||
validate_trace({Filter, Level, {Destination, ID}}) when is_tuple(Filter); is_list(Filter), is_atom(Level), is_atom(Destination) ->
|
||||
case validate_trace({Filter, Level, Destination}) of
|
||||
{ok, {F, L, D}} ->
|
||||
{ok, {F, L, {D, ID}}};
|
||||
Error ->
|
||||
Error
|
||||
end;
|
||||
validate_trace({Filter, Level, Destination}) when is_tuple(Filter); is_list(Filter), is_atom(Level), is_atom(Destination) ->
|
||||
ValidFilter = validate_trace_filter(Filter),
|
||||
try config_to_mask(Level) of
|
||||
_ when not ValidFilter ->
|
||||
{error, invalid_trace};
|
||||
L when is_list(Filter) ->
|
||||
{ok, {trace_all(Filter), L, Destination}};
|
||||
L ->
|
||||
{ok, {Filter, L, Destination}}
|
||||
catch
|
||||
_:_ ->
|
||||
{error, invalid_level}
|
||||
end;
|
||||
validate_trace(_) ->
|
||||
{error, invalid_trace}.
|
||||
|
||||
validate_trace_filter(Filter) when is_tuple(Filter), is_atom(element(1, Filter)) =:= false ->
|
||||
false;
|
||||
validate_trace_filter(Filter) ->
|
||||
case lists:all(fun({Key, '*'}) when is_atom(Key) -> true;
|
||||
({Key, '!'}) when is_atom(Key) -> true;
|
||||
({Key, _Value}) when is_atom(Key) -> true;
|
||||
({Key, '=', _Value}) when is_atom(Key) -> true;
|
||||
({Key, '<', _Value}) when is_atom(Key) -> true;
|
||||
({Key, '>', _Value}) when is_atom(Key) -> true;
|
||||
(_) -> false end, Filter) of
|
||||
true ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
trace_all(Query) ->
|
||||
glc:all(trace_acc(Query)).
|
||||
|
||||
trace_any(Query) ->
|
||||
glc:any(Query).
|
||||
|
||||
trace_acc(Query) ->
|
||||
trace_acc(Query, []).
|
||||
|
||||
trace_acc([], Acc) ->
|
||||
lists:reverse(Acc);
|
||||
trace_acc([{Key, '*'}|T], Acc) ->
|
||||
trace_acc(T, [glc:wc(Key)|Acc]);
|
||||
trace_acc([{Key, '!'}|T], Acc) ->
|
||||
trace_acc(T, [glc:nf(Key)|Acc]);
|
||||
trace_acc([{Key, Val}|T], Acc) ->
|
||||
trace_acc(T, [glc:eq(Key, Val)|Acc]);
|
||||
trace_acc([{Key, '=', Val}|T], Acc) ->
|
||||
trace_acc(T, [glc:eq(Key, Val)|Acc]);
|
||||
trace_acc([{Key, '>', Val}|T], Acc) ->
|
||||
trace_acc(T, [glc:gt(Key, Val)|Acc]);
|
||||
trace_acc([{Key, '<', Val}|T], Acc) ->
|
||||
trace_acc(T, [glc:lt(Key, Val)|Acc]).
|
||||
|
||||
|
||||
check_traces(_, _, [], Acc) ->
|
||||
lists:flatten(Acc);
|
||||
check_traces(Attrs, Level, [{_, {mask, FilterLevel}, _}|Flows], Acc) when (Level band FilterLevel) == 0 ->
|
||||
check_traces(Attrs, Level, Flows, Acc);
|
||||
check_traces(Attrs, Level, [{Filter, _, _}|Flows], Acc) when length(Attrs) < length(Filter) ->
|
||||
check_traces(Attrs, Level, Flows, Acc);
|
||||
check_traces(Attrs, Level, [Flow|Flows], Acc) ->
|
||||
check_traces(Attrs, Level, Flows, [check_trace(Attrs, Flow)|Acc]).
|
||||
|
||||
check_trace(Attrs, {Filter, _Level, Dest}) when is_list(Filter) ->
|
||||
check_trace(Attrs, {trace_all(Filter), _Level, Dest});
|
||||
|
||||
check_trace(Attrs, {Filter, _Level, Dest}) when is_tuple(Filter) ->
|
||||
Made = gre:make(Attrs, [list]),
|
||||
glc:handle(?DEFAULT_TRACER, Made),
|
||||
Match = glc_lib:matches(Filter, Made),
|
||||
case Match of
|
||||
true ->
|
||||
Dest;
|
||||
false ->
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec is_loggable(lager_msg:lager_msg(), non_neg_integer()|{'mask', non_neg_integer()}, term()) -> boolean().
|
||||
is_loggable(Msg, {mask, Mask}, MyName) ->
|
||||
%% using syslog style comparison flags
|
||||
%S = lager_msg:severity_as_int(Msg),
|
||||
%?debugFmt("comparing masks ~.2B and ~.2B -> ~p~n", [S, Mask, S band Mask]),
|
||||
(lager_msg:severity_as_int(Msg) band Mask) /= 0 orelse
|
||||
lists:member(MyName, lager_msg:destinations(Msg));
|
||||
is_loggable(Msg ,SeverityThreshold,MyName) ->
|
||||
lager_msg:severity_as_int(Msg) =< SeverityThreshold orelse
|
||||
lists:member(MyName, lager_msg:destinations(Msg)).
|
||||
|
||||
i2l(I) when I < 10 -> [$0, $0+I];
|
||||
i2l(I) -> integer_to_list(I).
|
||||
i3l(I) when I < 100 -> [$0 | i2l(I)];
|
||||
i3l(I) -> integer_to_list(I).
|
||||
|
||||
-ifdef(TEST).
|
||||
|
||||
parse_test() ->
|
||||
?assertEqual({ok, [{hour, 0}]}, parse_rotation_date_spec("$D0")),
|
||||
?assertEqual({ok, [{hour, 23}]}, parse_rotation_date_spec("$D23")),
|
||||
?assertEqual({ok, [{day, 0}, {hour, 23}]}, parse_rotation_date_spec("$W0D23")),
|
||||
?assertEqual({ok, [{day, 5}, {hour, 16}]}, parse_rotation_date_spec("$W5D16")),
|
||||
?assertEqual({ok, [{date, 1}, {hour, 0}]}, parse_rotation_date_spec("$M1D0")),
|
||||
?assertEqual({ok, [{date, 5}, {hour, 6}]}, parse_rotation_date_spec("$M5D6")),
|
||||
?assertEqual({ok, [{date, 5}, {hour, 0}]}, parse_rotation_date_spec("$M5")),
|
||||
?assertEqual({ok, [{date, 31}, {hour, 0}]}, parse_rotation_date_spec("$M31")),
|
||||
?assertEqual({ok, [{date, 31}, {hour, 1}]}, parse_rotation_date_spec("$M31D1")),
|
||||
?assertEqual({ok, [{date, last}, {hour, 0}]}, parse_rotation_date_spec("$ML")),
|
||||
?assertEqual({ok, [{date, last}, {hour, 0}]}, parse_rotation_date_spec("$Ml")),
|
||||
?assertEqual({ok, [{day, 5}, {hour, 0}]}, parse_rotation_date_spec("$W5")),
|
||||
ok.
|
||||
|
||||
parse_fail_test() ->
|
||||
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D")),
|
||||
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D24")),
|
||||
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7")),
|
||||
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7D1")),
|
||||
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M32")),
|
||||
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M32D1")),
|
||||
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$")),
|
||||
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("")),
|
||||
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D15M5")),
|
||||
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M5W5")),
|
||||
ok.
|
||||
|
||||
rotation_calculation_test() ->
|
||||
?assertMatch({{2000, 1, 2}, {0, 0, 0}},
|
||||
calculate_next_rotation([{hour, 0}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||
?assertMatch({{2000, 1, 1}, {16, 0, 0}},
|
||||
calculate_next_rotation([{hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||
?assertMatch({{2000, 1, 2}, {12, 0, 0}},
|
||||
calculate_next_rotation([{hour, 12}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
|
||||
calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
|
||||
calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 15}, {12, 34, 43}})),
|
||||
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
|
||||
calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 2}, {12, 34, 43}})),
|
||||
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
|
||||
calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 31}, {12, 34, 43}})),
|
||||
?assertMatch({{2000, 1, 1}, {16, 0, 0}},
|
||||
calculate_next_rotation([{date, 1}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||
?assertMatch({{2000, 1, 15}, {16, 0, 0}},
|
||||
calculate_next_rotation([{date, 15}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||
?assertMatch({{2000, 1, 31}, {16, 0, 0}},
|
||||
calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||
?assertMatch({{2000, 1, 31}, {16, 0, 0}},
|
||||
calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 31}, {12, 34, 43}})),
|
||||
?assertMatch({{2000, 2, 29}, {16, 0, 0}},
|
||||
calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 31}, {17, 34, 43}})),
|
||||
?assertMatch({{2001, 2, 28}, {16, 0, 0}},
|
||||
calculate_next_rotation([{date, last}, {hour, 16}], {{2001, 1, 31}, {17, 34, 43}})),
|
||||
|
||||
?assertMatch({{2000, 1, 1}, {16, 0, 0}},
|
||||
calculate_next_rotation([{day, 6}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||
?assertMatch({{2000, 1, 8}, {16, 0, 0}},
|
||||
calculate_next_rotation([{day, 6}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
|
||||
?assertMatch({{2000, 1, 7}, {16, 0, 0}},
|
||||
calculate_next_rotation([{day, 5}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
|
||||
?assertMatch({{2000, 1, 3}, {16, 0, 0}},
|
||||
calculate_next_rotation([{day, 1}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
|
||||
?assertMatch({{2000, 1, 2}, {16, 0, 0}},
|
||||
calculate_next_rotation([{day, 0}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
|
||||
?assertMatch({{2000, 1, 9}, {16, 0, 0}},
|
||||
calculate_next_rotation([{day, 0}, {hour, 16}], {{2000, 1, 2}, {17, 34, 43}})),
|
||||
?assertMatch({{2000, 2, 3}, {16, 0, 0}},
|
||||
calculate_next_rotation([{day, 4}, {hour, 16}], {{2000, 1, 29}, {17, 34, 43}})),
|
||||
|
||||
?assertMatch({{2000, 1, 7}, {16, 0, 0}},
|
||||
calculate_next_rotation([{day, 5}, {hour, 16}], {{2000, 1, 3}, {17, 34, 43}})),
|
||||
|
||||
?assertMatch({{2000, 1, 3}, {16, 0, 0}},
|
||||
calculate_next_rotation([{day, 1}, {hour, 16}], {{1999, 12, 28}, {17, 34, 43}})),
|
||||
ok.
|
||||
|
||||
rotate_file_test() ->
|
||||
file:delete("rotation.log"),
|
||||
[file:delete(["rotation.log.", integer_to_list(N)]) || N <- lists:seq(0, 9)],
|
||||
[begin
|
||||
file:write_file("rotation.log", integer_to_list(N)),
|
||||
Count = case N > 10 of
|
||||
true -> 10;
|
||||
_ -> N
|
||||
end,
|
||||
[begin
|
||||
FileName = ["rotation.log.", integer_to_list(M)],
|
||||
?assert(filelib:is_regular(FileName)),
|
||||
%% check the expected value is in the file
|
||||
Number = list_to_binary(integer_to_list(N - M - 1)),
|
||||
?assertEqual({ok, Number}, file:read_file(FileName))
|
||||
end
|
||||
|| M <- lists:seq(0, Count-1)],
|
||||
rotate_logfile("rotation.log", 10)
|
||||
end || N <- lists:seq(0, 20)].
|
||||
|
||||
rotate_file_fail_test() ->
|
||||
%% make sure the directory exists
|
||||
?assertEqual(ok, filelib:ensure_dir("rotation/rotation.log")),
|
||||
%% fix the permissions on it
|
||||
os:cmd("chown -R u+rwx rotation"),
|
||||
%% delete any old files
|
||||
[ok = file:delete(F) || F <- filelib:wildcard("rotation/*")],
|
||||
%% write a file
|
||||
file:write_file("rotation/rotation.log", "hello"),
|
||||
%% hose up the permissions
|
||||
os:cmd("chown u-w rotation"),
|
||||
?assertMatch({error, _}, rotate_logfile("rotation.log", 10)),
|
||||
?assert(filelib:is_regular("rotation/rotation.log")),
|
||||
os:cmd("chown u+w rotation"),
|
||||
?assertMatch(ok, rotate_logfile("rotation/rotation.log", 10)),
|
||||
?assert(filelib:is_regular("rotation/rotation.log.0")),
|
||||
?assertEqual(false, filelib:is_regular("rotation/rotation.log")),
|
||||
ok.
|
||||
|
||||
check_trace_test() ->
|
||||
lager:start(),
|
||||
trace_filter(none),
|
||||
%% match by module
|
||||
?assertEqual([foo], check_traces([{module, ?MODULE}], ?EMERGENCY, [
|
||||
{[{module, ?MODULE}], config_to_mask(emergency), foo},
|
||||
{[{module, test}], config_to_mask(emergency), bar}], [])),
|
||||
%% match by module, but other unsatisfyable attribute
|
||||
?assertEqual([], check_traces([{module, ?MODULE}], ?EMERGENCY, [
|
||||
{[{module, ?MODULE}, {foo, bar}], config_to_mask(emergency), foo},
|
||||
{[{module, test}], config_to_mask(emergency), bar}], [])),
|
||||
%% match by wildcard module
|
||||
?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
|
||||
{[{module, ?MODULE}, {foo, bar}], config_to_mask(emergency), foo},
|
||||
{[{module, '*'}], config_to_mask(emergency), bar}], [])),
|
||||
%% wildcard module, one trace with unsatisfyable attribute
|
||||
?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
|
||||
{[{module, '*'}, {foo, bar}], config_to_mask(emergency), foo},
|
||||
{[{module, '*'}], config_to_mask(emergency), bar}], [])),
|
||||
%% wildcard but not present custom trace attribute
|
||||
?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
|
||||
{[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
|
||||
{[{module, '*'}], config_to_mask(emergency), bar}], [])),
|
||||
%% wildcarding a custom attribute works when it is present
|
||||
?assertEqual([bar, foo], check_traces([{module, ?MODULE}, {foo, bar}], ?EMERGENCY, [
|
||||
{[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
|
||||
{[{module, '*'}], config_to_mask(emergency), bar}], [])),
|
||||
%% denied by level
|
||||
?assertEqual([], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [
|
||||
{[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
|
||||
{[{module, '*'}], config_to_mask(emergency), bar}], [])),
|
||||
%% allowed by level
|
||||
?assertEqual([foo], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [
|
||||
{[{module, '*'}, {foo, '*'}], config_to_mask(debug), foo},
|
||||
{[{module, '*'}], config_to_mask(emergency), bar}], [])),
|
||||
?assertEqual([anythingbutnotice, infoandbelow, infoonly], check_traces([{module, ?MODULE}], ?INFO, [
|
||||
{[{module, '*'}], config_to_mask('=debug'), debugonly},
|
||||
{[{module, '*'}], config_to_mask('=info'), infoonly},
|
||||
{[{module, '*'}], config_to_mask('<=info'), infoandbelow},
|
||||
{[{module, '*'}], config_to_mask('!=info'), anythingbutinfo},
|
||||
{[{module, '*'}], config_to_mask('!=notice'), anythingbutnotice}
|
||||
], [])),
|
||||
application:stop(lager),
|
||||
application:stop(goldrush),
|
||||
ok.
|
||||
|
||||
is_loggable_test_() ->
|
||||
[
|
||||
{"Loggable by severity only", ?_assert(is_loggable(lager_msg:new("", alert, [], []),2,me))},
|
||||
{"Not loggable by severity only", ?_assertNot(is_loggable(lager_msg:new("", critical, [], []),1,me))},
|
||||
{"Loggable by severity with destination", ?_assert(is_loggable(lager_msg:new("", alert, [], [you]),2,me))},
|
||||
{"Not loggable by severity with destination", ?_assertNot(is_loggable(lager_msg:new("", critical, [], [you]),1,me))},
|
||||
{"Loggable by destination overriding severity", ?_assert(is_loggable(lager_msg:new("", critical, [], [me]),1,me))}
|
||||
].
|
||||
|
||||
format_time_test_() ->
|
||||
[
|
||||
?_assertEqual("2012-10-04 11:16:23.002",
|
||||
begin
|
||||
{D, T} = format_time({{2012,10,04},{11,16,23,2}}),
|
||||
lists:flatten([D,$ ,T])
|
||||
end),
|
||||
?_assertEqual("2012-10-04 11:16:23.999",
|
||||
begin
|
||||
{D, T} = format_time({{2012,10,04},{11,16,23,999}}),
|
||||
lists:flatten([D,$ ,T])
|
||||
end),
|
||||
?_assertEqual("2012-10-04 11:16:23",
|
||||
begin
|
||||
{D, T} = format_time({{2012,10,04},{11,16,23}}),
|
||||
lists:flatten([D,$ ,T])
|
||||
end),
|
||||
?_assertEqual("2012-10-04 00:16:23.092 UTC",
|
||||
begin
|
||||
{D, T} = format_time({utc, {{2012,10,04},{0,16,23,92}}}),
|
||||
lists:flatten([D,$ ,T])
|
||||
end),
|
||||
?_assertEqual("2012-10-04 11:16:23 UTC",
|
||||
begin
|
||||
{D, T} = format_time({utc, {{2012,10,04},{11,16,23}}}),
|
||||
lists:flatten([D,$ ,T])
|
||||
end)
|
||||
].
|
||||
|
||||
config_to_levels_test() ->
|
||||
?assertEqual([none], config_to_levels('none')),
|
||||
?assertEqual({mask, 0}, config_to_mask('none')),
|
||||
?assertEqual([debug], config_to_levels('=debug')),
|
||||
?assertEqual([debug], config_to_levels('<info')),
|
||||
?assertEqual(levels() -- [debug], config_to_levels('!=debug')),
|
||||
?assertEqual(levels() -- [debug], config_to_levels('>debug')),
|
||||
?assertEqual(levels() -- [debug], config_to_levels('>=info')),
|
||||
?assertEqual(levels() -- [debug], config_to_levels('=>info')),
|
||||
?assertEqual([debug, info, notice], config_to_levels('<=notice')),
|
||||
?assertEqual([debug, info, notice], config_to_levels('=<notice')),
|
||||
?assertEqual([debug], config_to_levels('<info')),
|
||||
?assertEqual([debug], config_to_levels('!info')),
|
||||
?assertError(badarg, config_to_levels(ok)),
|
||||
?assertError(badarg, config_to_levels('<=>info')),
|
||||
?assertError(badarg, config_to_levels('=<=info')),
|
||||
?assertError(badarg, config_to_levels('<==>=<=>info')),
|
||||
%% double negatives DO work, however
|
||||
?assertEqual([debug], config_to_levels('!!=debug')),
|
||||
?assertEqual(levels() -- [debug], config_to_levels('!!!=debug')),
|
||||
ok.
|
||||
|
||||
config_to_mask_test() ->
|
||||
?assertEqual({mask, 0}, config_to_mask('none')),
|
||||
?assertEqual({mask, ?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('debug')),
|
||||
?assertEqual({mask, ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('warning')),
|
||||
?assertEqual({mask, ?DEBUG bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('!=info')),
|
||||
ok.
|
||||
|
||||
mask_to_levels_test() ->
|
||||
?assertEqual([], mask_to_levels(0)),
|
||||
?assertEqual([debug], mask_to_levels(2#10000000)),
|
||||
?assertEqual([debug, info], mask_to_levels(2#11000000)),
|
||||
?assertEqual([debug, info, emergency], mask_to_levels(2#11000001)),
|
||||
?assertEqual([debug, notice, error], mask_to_levels(?DEBUG bor ?NOTICE bor ?ERROR)),
|
||||
ok.
|
||||
|
||||
-endif.
|
|
@ -1,2 +0,0 @@
|
|||
{'../ejabberd-dev/src/gen_mod', [{outdir, "../ejabberd-dev/ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||
{'src/ejabberd_auth_http', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
|
@ -16,6 +16,8 @@ central user database, which is shared with other services. It fits
|
|||
perfectly when client application uses custom authentication token and
|
||||
ejabberd has to validate it externally.
|
||||
|
||||
This module requires ejabberd 20.02 or higher.
|
||||
|
||||
## Configuration
|
||||
|
||||
### How to enable
|
||||
|
@ -47,6 +49,17 @@ properly. The following options can be set in `auth_opts` in
|
|||
* `path_prefix` (optional, default: `"/"`) - a path prefix to be
|
||||
inserted between `host` and method name; must be terminated with `/`
|
||||
|
||||
Example configuration:
|
||||
```
|
||||
auth_method: http
|
||||
auth_opts:
|
||||
host: "http://localhost:12000"
|
||||
connection_pool_size: 10
|
||||
connection_opts: []
|
||||
basic_auth: ""
|
||||
path_prefix: "/"
|
||||
```
|
||||
|
||||
## SCRAM support
|
||||
|
||||
`ejabberd_auth_http` can use the SCRAM method. When SCRAM is enabled,
|
||||
|
@ -171,23 +184,27 @@ configuration along with use cases.
|
|||
An Auth token is provided as a password.
|
||||
|
||||
* **Service implements:** `check_password`, `user_exists`
|
||||
* **ejabberd config:** `password format`: `plain`, `mod_register` disabled
|
||||
* **ejabberd config:** `auth_password format`: `plain`, `mod_register` disabled
|
||||
* **Client side:** MUST NOT use `DIGEST-MD5` mechanism; use `PLAIN`
|
||||
|
||||
### Central database of plaintext passwords
|
||||
|
||||
* **Service implements:** `check_password`, `get_password`, `user_exists`
|
||||
* **ejabberd config:** `password format`: `plain`, `mod_register` disabled
|
||||
* **ejabberd config:** `auth_password_format`: `plain`, `mod_register` disabled
|
||||
* **Client side:** May use any available auth method
|
||||
|
||||
### Central database able to process SCRAM
|
||||
|
||||
* **Service implements:** `get_password`, `user_exists`
|
||||
* **ejabberd config:** `password format`: `scram`, `mod_register` disabled
|
||||
* **ejabberd config:** `auth_password_format`: `scram`, `mod_register` disabled
|
||||
* **Client side:** May use any available auth method
|
||||
|
||||
### All-included
|
||||
|
||||
* **Service implements:** all methods
|
||||
* **ejabberd config:** `password format`: `scram` (recommended) or `plain`, `mod_register` enabled
|
||||
* **ejabberd config:** `auth_password_format`: `scram` (recommended) or `plain`, `mod_register` enabled
|
||||
* **Client side:** May use any available auth method
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the service supports multiple passwords per user you need to disable authentication caching with `auth_use_cache: false`. See [https://github.com/processone/ejabberd-contrib/issues/288](https://github.com/processone/ejabberd-contrib/issues/288).
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
erl -pa ../ejabberd-dev/ebin -pz ebin -make
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/sh
|
||||
erl -pa ../ejabberd-dev/ebin -pz ebin -make
|
|
@ -0,0 +1,5 @@
|
|||
author: "Piotr Nosek <piotr.nosek at erlang-solutions.com>"
|
||||
category: "auth"
|
||||
summary: "Authentication via HTTP request"
|
||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
|
||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
@ -0,0 +1,4 @@
|
|||
{deps, [
|
||||
{cuesport, ".*", {git, "https://github.com/goj/cuesport"}},
|
||||
{fusco, ".*", {git, "https://github.com/esl/fusco"}}
|
||||
]}.
|
|
@ -8,27 +8,26 @@
|
|||
-module(ejabberd_auth_http).
|
||||
-author('piotr.nosek@erlang-solutions.com').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
%% External exports
|
||||
-export([start/1,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
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,
|
||||
get_vh_registered_users_number/2,
|
||||
get_password/2,
|
||||
get_password_s/2,
|
||||
is_user_exists/2,
|
||||
user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
plain_password_required/0,
|
||||
store_type/1
|
||||
]).
|
||||
plain_password_required/1,
|
||||
store_type/1,
|
||||
login/2,
|
||||
get_password/3,
|
||||
stop/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include_lib("xmpp/include/scram.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
|
@ -37,180 +36,143 @@
|
|||
|
||||
-spec start(binary()) -> ok.
|
||||
start(Host) ->
|
||||
AuthOpts = ejabberd_config:get_local_option(auth_opts, Host),
|
||||
AuthOpts = ejabberd_config:get_option({auth_opts, Host}),
|
||||
{_, AuthHost} = lists:keyfind(host, 1, AuthOpts),
|
||||
PoolSize = proplists:get_value(connection_pool_size, AuthOpts, 10),
|
||||
Opts = proplists:get_value(connection_opts, AuthOpts, []),
|
||||
ChildMods = [fusco],
|
||||
ChildMFA = {fusco, start_link, [AuthHost, Opts]},
|
||||
|
||||
{ok, _} = supervisor:start_child(ejabberd_sup,
|
||||
{{ejabberd_auth_http_sup, Host},
|
||||
{cuesport, start_link,
|
||||
[pool_name(Host), PoolSize, ChildMods, ChildMFA]},
|
||||
transient, 2000, supervisor, [cuesport | ChildMods]}),
|
||||
ChildMFA = {fusco, start_link, [binary_to_list(AuthHost), Opts]},
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
ChildSpec = {Proc, {cuesport, start_link,
|
||||
[pool_name(Host), PoolSize, ChildMods, ChildMFA]},
|
||||
transient, 2000, supervisor, [cuesport | ChildMods]},
|
||||
supervisor:start_child(ejabberd_backend_sup, ChildSpec),
|
||||
ok.
|
||||
|
||||
-spec plain_password_required() -> false.
|
||||
plain_password_required() ->
|
||||
-spec plain_password_required(binary()) -> false.
|
||||
plain_password_required(_Server) ->
|
||||
false.
|
||||
|
||||
-spec store_type(binary()) -> plain | scram.
|
||||
store_type(Server) ->
|
||||
case scram:enabled(Server) of
|
||||
false -> plain;
|
||||
true -> scram
|
||||
end.
|
||||
-spec store_type(binary()) -> external.
|
||||
store_type(_) ->
|
||||
external.
|
||||
|
||||
-spec check_password(binary(), binary(), binary()) -> boolean().
|
||||
check_password(User, Server, Password) ->
|
||||
{LUser, LServer} = stringprep(User, Server),
|
||||
case scram:enabled(Server) of
|
||||
-spec check_password(binary(), binary(), binary(), binary()) -> {ets_cache:tag(), boolean()}.
|
||||
check_password(LUser, _AuthzId, LServer, Password) ->
|
||||
case scram2:enabled(LServer) of
|
||||
false ->
|
||||
case make_req(get, <<"check_password">>, LUser, LServer, Password) of
|
||||
{ok, <<"true">>} -> true;
|
||||
_ -> false
|
||||
{ok, <<"true">>} -> {cache, true};
|
||||
_ -> {nocache, false}
|
||||
end;
|
||||
true ->
|
||||
{ok, true} =:= verify_scram_password(LUser, LServer, Password)
|
||||
case verify_scram_password(LUser, LServer, Password) of
|
||||
{ok, true} -> {cache, true};
|
||||
_ -> {nocache, false}
|
||||
end
|
||||
end.
|
||||
|
||||
-spec check_password(binary(), binary(), binary(), binary(), fun()) -> boolean().
|
||||
check_password(User, Server, Password, Digest, DigestGen) ->
|
||||
{LUser, LServer} = stringprep(User, Server),
|
||||
-spec check_password(binary(), binary(), binary(), binary(), binary(), fun()) -> boolean().
|
||||
check_password(LUser, _AuthzId, LServer, Password, Digest, DigestGen) ->
|
||||
case make_req(get, <<"get_password">>, LUser, LServer, <<"">>) of
|
||||
{error, _} ->
|
||||
false;
|
||||
{ok, GotPasswd} ->
|
||||
case scram:enabled(LServer) of
|
||||
case scram2:enabled(LServer) of
|
||||
true ->
|
||||
case scram:deserialize(GotPasswd) of
|
||||
{ok, #scram{storedkey = StoredKey}} ->
|
||||
Passwd = base64:decode(StoredKey),
|
||||
ejabberd_auth:check_digest(Digest, DigestGen, Password, Passwd);
|
||||
case scram2:deserialize(GotPasswd) of
|
||||
{ok, #scram{} = Scram} ->
|
||||
scram2:check_digest(Scram, Digest, DigestGen, Password);
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
false ->
|
||||
ejabberd_auth:check_digest(Digest, DigestGen, Password, GotPasswd)
|
||||
check_digest(Digest, DigestGen, Password, GotPasswd)
|
||||
end
|
||||
end.
|
||||
|
||||
-spec set_password(binary(), binary(), binary()) -> ok | {error, term()}.
|
||||
set_password(User, Server, Password) ->
|
||||
{LUser, LServer} = stringprep(User, Server),
|
||||
PasswordFinal = case scram:enabled(LServer) of
|
||||
true -> scram:serialize(scram:password_to_scram(
|
||||
Password, scram:iterations(Server)));
|
||||
-spec check_digest(binary(), fun(), binary(), binary()) -> boolean().
|
||||
check_digest(Digest, DigestGen, Password, Passwd) ->
|
||||
DigRes = if
|
||||
Digest /= <<>> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true ->
|
||||
false
|
||||
end,
|
||||
if DigRes ->
|
||||
true;
|
||||
true ->
|
||||
(Passwd == Password) and (Password /= <<>>)
|
||||
end.
|
||||
|
||||
|
||||
-spec set_password(binary(), binary(), binary()) -> {ets_cache:tag(), {ok, binary()} | {error, not_allowed}}.
|
||||
set_password(LUser, LServer, Password) ->
|
||||
PasswordFinal = case scram2:enabled(LServer) of
|
||||
true -> scram2:serialize(scram2:password_to_scram(
|
||||
Password, scram2:iterations(LServer)));
|
||||
false -> Password
|
||||
end,
|
||||
case make_req(post, <<"set_password">>, LUser, LServer, PasswordFinal) of
|
||||
{error, _} = Err -> Err;
|
||||
_ -> ok
|
||||
{error, _Error} -> {nocache, {error, not_allowed}};
|
||||
{ok, _} -> {cache, {ok, Password}}
|
||||
end.
|
||||
|
||||
-spec try_register(binary(), binary(), binary()) -> {atomic, ok | exists} | {error, term()}.
|
||||
try_register(User, Server, Password) ->
|
||||
{LUser, LServer} = stringprep(User, Server),
|
||||
PasswordFinal = case scram:enabled(LServer) of
|
||||
true -> scram:serialize(scram:password_to_scram(
|
||||
Password, scram:iterations(Server)));
|
||||
-spec try_register(binary(), binary(), binary()) -> {ets_cache:tag(), {ok, binary()} | {error, exists | not_allowed}}.
|
||||
try_register(LUser, LServer, Password) ->
|
||||
PasswordFinal = case scram2:enabled(LServer) of
|
||||
true -> scram2:serialize(scram2:password_to_scram(
|
||||
Password, scram2:iterations(LServer)));
|
||||
false -> Password
|
||||
end,
|
||||
case make_req(post, <<"register">>, LUser, LServer, PasswordFinal) of
|
||||
{ok, created} -> {atomic, ok};
|
||||
{error, conflict} -> {atomic, exists};
|
||||
Error -> Error
|
||||
{ok, <<"created">>} -> {cache, {ok, Password}};
|
||||
{error, conflict} -> {nocache, {error, exists}};
|
||||
_Error -> {nocache, {error, not_allowed}}
|
||||
end.
|
||||
|
||||
-spec dirty_get_registered_users() -> [].
|
||||
dirty_get_registered_users() ->
|
||||
[].
|
||||
-spec get_password(binary(), binary()) -> {cache, error}.
|
||||
get_password(_, _) ->
|
||||
{cache, error}.
|
||||
|
||||
-spec get_vh_registered_users(binary()) -> [].
|
||||
get_vh_registered_users(_Server) ->
|
||||
[].
|
||||
-spec get_password_s(binary(), binary()) -> {cache, error}.
|
||||
get_password_s(_User, _Server) ->
|
||||
{cache, error}.
|
||||
|
||||
-spec get_vh_registered_users(binary(), list()) -> [].
|
||||
get_vh_registered_users(_Server, _Opts) ->
|
||||
[].
|
||||
|
||||
-spec get_vh_registered_users_number(binary()) -> 0.
|
||||
get_vh_registered_users_number(_Server) ->
|
||||
0.
|
||||
|
||||
-spec get_vh_registered_users_number(binary(), list()) -> 0.
|
||||
get_vh_registered_users_number(_Server, _Opts) ->
|
||||
0.
|
||||
|
||||
-spec get_password(binary(), binary()) -> false | binary() |
|
||||
{binary(), binary(), binary(), integer()}.
|
||||
get_password(User, Server) ->
|
||||
{LUser, LServer} = stringprep(User, Server),
|
||||
case make_req(get, <<"get_password">>, LUser, LServer, <<"">>) of
|
||||
{error, _} ->
|
||||
false;
|
||||
{ok, Password} ->
|
||||
case scram:enabled(LServer) of
|
||||
true ->
|
||||
case scram:deserialize(Password) of
|
||||
{ok, #scram{} = Scram} ->
|
||||
{base64:decode(Scram#scram.storedkey),
|
||||
base64:decode(Scram#scram.serverkey),
|
||||
base64:decode(Scram#scram.salt),
|
||||
Scram#scram.iterationcount};
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
false ->
|
||||
Password
|
||||
end
|
||||
end.
|
||||
|
||||
-spec get_password_s(binary(), binary()) -> binary().
|
||||
get_password_s(User, Server) ->
|
||||
case get_password(User, Server) of
|
||||
Pass when is_binary(Pass) -> Pass;
|
||||
_ -> <<>>
|
||||
end.
|
||||
|
||||
-spec is_user_exists(binary(), binary()) -> boolean().
|
||||
is_user_exists(User, Server) ->
|
||||
{LUser, LServer} = stringprep(User, Server),
|
||||
-spec user_exists(binary(), binary()) -> {ets_cache:tag(), boolean()}.
|
||||
user_exists(LUser, LServer) ->
|
||||
case make_req(get, <<"user_exists">>, LUser, LServer, <<"">>) of
|
||||
{ok, <<"true">>} -> true;
|
||||
_ -> false
|
||||
{ok, <<"true">>} -> {cache, true};
|
||||
_ -> {nocache, false}
|
||||
end.
|
||||
|
||||
-spec remove_user(binary(), binary()) -> ok | not_exists | not_allowed | bad_request.
|
||||
remove_user(User, Server) ->
|
||||
{LUser, LServer} = stringprep(User, Server),
|
||||
-spec remove_user(binary(), binary()) -> ok | {error, db_failure | not_allowed}.
|
||||
remove_user(LUser, LServer) ->
|
||||
remove_user_req(LUser, LServer, <<"">>, <<"remove_user">>).
|
||||
|
||||
-spec remove_user(binary(), binary(), binary()) -> ok | not_exists | not_allowed | bad_request.
|
||||
remove_user(User, Server, Password) ->
|
||||
{LUser, LServer} = stringprep(User, Server),
|
||||
case scram:enabled(Server) of
|
||||
-spec remove_user(binary(), binary(), binary()) -> ok | {error, db_failure | not_allowed}.
|
||||
remove_user(LUser, LServer, Password) ->
|
||||
case scram2:enabled(LServer) of
|
||||
false ->
|
||||
remove_user_req(LUser, LServer, Password, <<"remove_user_validate">>);
|
||||
true ->
|
||||
case verify_scram_password(LUser, LServer, Password) of
|
||||
{ok, false} ->
|
||||
not_allowed;
|
||||
{error, not_allowed};
|
||||
{ok, true} ->
|
||||
remove_user_req(LUser, LServer, <<"">>, <<"remove_user">>);
|
||||
{error, Error} ->
|
||||
Error
|
||||
{error, _Error} ->
|
||||
{error, db_failure}
|
||||
end
|
||||
end.
|
||||
|
||||
-spec remove_user_req(binary(), binary(), binary(), binary()) ->
|
||||
ok | not_exists | not_allowed | bad_request.
|
||||
ok | {error, not_allowed | db_failure}.
|
||||
remove_user_req(LUser, LServer, Password, Method) ->
|
||||
case make_req(post, Method, LUser, LServer, Password) of
|
||||
{error, not_allowed} -> not_allowed;
|
||||
{error, not_found} -> not_exists;
|
||||
{error, _} -> bad_request;
|
||||
{error, not_allowed} -> {error, not_allowed};
|
||||
{error, not_found} -> {error, db_failure};
|
||||
{error, _} -> {error, db_failure};
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
|
@ -218,36 +180,55 @@ remove_user_req(LUser, LServer, Password, Method) ->
|
|||
%%% Request maker
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-ifdef(OTP_BELOW_25).
|
||||
-dialyzer({no_missing_calls, [uri_quote/1]}).
|
||||
uri_quote(URL) ->
|
||||
http_uri:encode(URL).
|
||||
-else.
|
||||
uri_quote(URL) ->
|
||||
uri_string:quote(URL). % Available since OTP 25.0
|
||||
-endif.
|
||||
|
||||
-spec make_req(post | get, binary(), binary(), binary(), binary()) ->
|
||||
{ok, Body :: binary()} | {error, term()}.
|
||||
make_req(_, _, LUser, LServer, _) when LUser == error orelse LServer == error ->
|
||||
{error, {prep_failed, LUser, LServer}};
|
||||
make_req(Method, Path, LUser, LServer, Password) ->
|
||||
AuthOpts = ejabberd_config:get_local_option(auth_opts, LServer),
|
||||
AuthOpts = ejabberd_config:get_option({auth_opts, LServer}),
|
||||
BasicAuth = case lists:keyfind(basic_auth, 1, AuthOpts) of
|
||||
{_, BasicAuth0} -> BasicAuth0;
|
||||
_ -> ""
|
||||
end,
|
||||
PathPrefix = case lists:keyfind(path_prefix, 1, AuthOpts) of
|
||||
{_, Prefix} -> ejabberd_binary:string_to_binary(Prefix);
|
||||
{_, Prefix} -> Prefix;
|
||||
false -> <<"/">>
|
||||
end,
|
||||
BasicAuth64 = base64:encode(BasicAuth),
|
||||
LUserE = list_to_binary(http_uri:encode(binary_to_list(LUser))),
|
||||
LServerE = list_to_binary(http_uri:encode(binary_to_list(LServer))),
|
||||
PasswordE = list_to_binary(http_uri:encode(binary_to_list(Password))),
|
||||
LUserE = list_to_binary(uri_quote(binary_to_list(LUser))),
|
||||
LServerE = list_to_binary(uri_quote(binary_to_list(LServer))),
|
||||
PasswordE = list_to_binary(uri_quote(binary_to_list(Password))),
|
||||
Query = <<"user=", LUserE/binary, "&server=", LServerE/binary, "&pass=", PasswordE/binary>>,
|
||||
Header = [{<<"Authorization">>, <<"Basic ", BasicAuth64/binary>>}],
|
||||
ContentType = {<<"Content-Type">>, <<"application/x-www-form-urlencoded">>},
|
||||
Connection = cuesport:get_worker(existing_pool_name(LServer)),
|
||||
|
||||
?DEBUG("Making request '~s' for user ~s@~s...", [Path, LUser, LServer]),
|
||||
{ok, {{Code, _Reason}, _RespHeaders, RespBody, _, _}} = case Method of
|
||||
get -> fusco:request(Connection, <<PathPrefix/binary, Path/binary, "?", Query/binary>>,
|
||||
"GET", Header, "", 2, 5000);
|
||||
post -> fusco:request(Connection, <<PathPrefix/binary, Path/binary>>,
|
||||
"POST", Header, Query, 2, 5000)
|
||||
end,
|
||||
{Url, MethodStr, Headers, Query2} =
|
||||
case Method of
|
||||
get -> {<<PathPrefix/binary, Path/binary, "?", Query/binary>>,
|
||||
"GET",
|
||||
Header,
|
||||
""};
|
||||
post -> {<<PathPrefix/binary, Path/binary>>,
|
||||
"POST",
|
||||
[ContentType|Header],
|
||||
Query}
|
||||
end,
|
||||
http_request(Connection, Url, MethodStr, Headers, Query2, 0).
|
||||
|
||||
http_request(Connection, Url, MethodStr, Headers, Query, RedirectCounter) ->
|
||||
{ok, {{Code, _Reason}, RespHeaders, RespBody, _, _}} =
|
||||
fusco:request(Connection, Url, MethodStr, Headers, Query, 2, 5000),
|
||||
?DEBUG("Request result: ~s: ~p", [Code, RespBody]),
|
||||
case Code of
|
||||
<<"409">> -> {error, conflict};
|
||||
|
@ -255,17 +236,25 @@ make_req(Method, Path, LUser, LServer, Password) ->
|
|||
<<"401">> -> {error, not_authorized};
|
||||
<<"403">> -> {error, not_allowed};
|
||||
<<"400">> -> {error, RespBody};
|
||||
<<"503">> -> {error, RespBody};
|
||||
<<"204">> -> {ok, <<"">>};
|
||||
<<"201">> -> {ok, created};
|
||||
<<"200">> -> {ok, RespBody}
|
||||
<<"201">> -> {ok, <<"created">>};
|
||||
<<"200">> -> {ok, RespBody};
|
||||
R when (R==<<"301">>) or (R==<<"307">>) or (R==<<"308">>) ->
|
||||
handle_redirect(RespHeaders, Connection, MethodStr, Headers, Query, RedirectCounter+1);
|
||||
_ -> {error, RespBody}
|
||||
end.
|
||||
|
||||
handle_redirect(RespHeaders, Connection, MethodStr, Headers, Query, RedirectCounter)
|
||||
when RedirectCounter < 5 ->
|
||||
{_, Location} = lists:keyfind(<<"location">>, 1, RespHeaders),
|
||||
http_request(Connection, Location, MethodStr, Headers, Query, RedirectCounter);
|
||||
handle_redirect(_, _, _, _, _, _) ->
|
||||
{error, redirect_loop}.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Other internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
stringprep(User, Server) -> {jlib:nodeprep(User), jlib:nameprep(Server)}.
|
||||
|
||||
-spec pool_name(binary()) -> atom().
|
||||
pool_name(Host) ->
|
||||
list_to_atom("ejabberd_auth_http_" ++ binary_to_list(Host)).
|
||||
|
@ -279,9 +268,9 @@ existing_pool_name(Host) ->
|
|||
verify_scram_password(LUser, LServer, Password) ->
|
||||
case make_req(get, <<"get_password">>, LUser, LServer, <<"">>) of
|
||||
{ok, RawPassword} ->
|
||||
case scram:deserialize(RawPassword) of
|
||||
case scram2:deserialize(RawPassword) of
|
||||
{ok, #scram{} = ScramRecord} ->
|
||||
{ok, scram:check_password(Password, ScramRecord)};
|
||||
{ok, scram2:check_password(Password, ScramRecord)};
|
||||
_ ->
|
||||
{error, bad_request}
|
||||
end;
|
||||
|
@ -289,3 +278,13 @@ verify_scram_password(LUser, LServer, Password) ->
|
|||
{error, not_exists}
|
||||
end.
|
||||
|
||||
login(_User, _Server) ->
|
||||
erlang:error(not_implemented).
|
||||
|
||||
get_password(_User, _Server, _DefaultValue) ->
|
||||
erlang:error(not_implemented).
|
||||
|
||||
stop(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
supervisor:terminate_child(ejabberd_backend_sup, Proc),
|
||||
supervisor:delete_child(ejabberd_backend_sup, Proc).
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%% File : scram.erl
|
||||
%%% Author : Stephen Röttger <stephen.roettger@googlemail.com>
|
||||
%%% Purpose : SCRAM (RFC 5802)
|
||||
%%% Created : 7 Aug 2011 by Stephen Röttger <stephen.roettger@googlemail.com>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2020 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., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(scram2).
|
||||
|
||||
-author('stephen.roettger@googlemail.com').
|
||||
|
||||
-include_lib("xmpp/include/scram.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
%% External exports
|
||||
%% ejabberd doesn't implement SASLPREP, so we use the similar RESOURCEPREP instead
|
||||
-export([ % Core SCRAM functions
|
||||
salted_password/3,
|
||||
stored_key/1,
|
||||
server_key/1,
|
||||
server_signature/2,
|
||||
client_signature/2,
|
||||
client_key/1,
|
||||
client_key/2]).
|
||||
|
||||
-export([
|
||||
enabled/1,
|
||||
iterations/0,
|
||||
iterations/1,
|
||||
password_to_scram/1,
|
||||
password_to_scram/2,
|
||||
check_password/2,
|
||||
check_digest/4
|
||||
]).
|
||||
|
||||
-export([serialize/1, deserialize/1]).
|
||||
|
||||
-export([scram_to_tuple/1]).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
-define(SCRAM_SERIAL_PREFIX, "==SCRAM==,").
|
||||
|
||||
-spec salted_password(binary(), binary(), non_neg_integer()) -> binary().
|
||||
salted_password(Password, Salt, IterationCount) ->
|
||||
hi(jid:resourceprep(Password), Salt, IterationCount).
|
||||
|
||||
-spec client_key(binary()) -> binary().
|
||||
client_key(SaltedPassword) ->
|
||||
crypto_hmac(sha, SaltedPassword, <<"Client Key">>).
|
||||
|
||||
-spec stored_key(binary()) -> binary().
|
||||
stored_key(ClientKey) -> crypto:hash(sha, ClientKey).
|
||||
|
||||
-spec server_key(binary()) -> binary().
|
||||
server_key(SaltedPassword) ->
|
||||
crypto_hmac(sha, SaltedPassword, <<"Server Key">>).
|
||||
|
||||
-spec client_signature(binary(), binary()) -> binary().
|
||||
client_signature(StoredKey, AuthMessage) ->
|
||||
crypto_hmac(sha, StoredKey, AuthMessage).
|
||||
|
||||
-spec client_key(binary(), binary()) -> binary().
|
||||
client_key(ClientProof, ClientSignature) ->
|
||||
list_to_binary(lists:zipwith(fun (X, Y) -> X bxor Y end,
|
||||
binary_to_list(ClientProof),
|
||||
binary_to_list(ClientSignature))).
|
||||
|
||||
-spec server_signature(binary(), binary()) -> binary().
|
||||
server_signature(ServerKey, AuthMessage) ->
|
||||
crypto_hmac(sha, ServerKey, AuthMessage).
|
||||
|
||||
hi(Password, Salt, IterationCount) ->
|
||||
U1 = crypto_hmac(sha, Password, <<Salt/binary, 0, 0, 0, 1>>),
|
||||
list_to_binary(lists:zipwith(fun (X, Y) -> X bxor Y end,
|
||||
binary_to_list(U1),
|
||||
binary_to_list(hi_round(Password, U1,
|
||||
IterationCount - 1)))).
|
||||
|
||||
hi_round(Password, UPrev, 1) ->
|
||||
crypto_hmac(sha, Password, UPrev);
|
||||
hi_round(Password, UPrev, IterationCount) ->
|
||||
U = crypto_hmac(sha, Password, UPrev),
|
||||
list_to_binary(lists:zipwith(fun (X, Y) -> X bxor Y end,
|
||||
binary_to_list(U),
|
||||
binary_to_list(hi_round(Password, U,
|
||||
IterationCount - 1)))).
|
||||
|
||||
|
||||
enabled(Host) ->
|
||||
case ejabberd_config:get_option({auth_opts, Host}) of
|
||||
undefined ->
|
||||
false;
|
||||
AuthOpts ->
|
||||
{password_format, scram} == lists:keyfind(password_format, 1, AuthOpts)
|
||||
end.
|
||||
|
||||
iterations() -> ?SCRAM_DEFAULT_ITERATION_COUNT.
|
||||
|
||||
iterations(Host) ->
|
||||
case ejabberd_config:get_option({auth_opts, Host}) of
|
||||
undefined ->
|
||||
iterations();
|
||||
AuthOpts ->
|
||||
case lists:keyfind(scram_iterations, 1, AuthOpts) of
|
||||
false -> iterations();
|
||||
{_, Iterations} -> Iterations
|
||||
end
|
||||
end.
|
||||
|
||||
password_to_scram(Password) ->
|
||||
password_to_scram(Password, ?SCRAM_DEFAULT_ITERATION_COUNT).
|
||||
|
||||
password_to_scram(#scram{} = Password, _) ->
|
||||
Password;
|
||||
password_to_scram(Password, IterationCount) ->
|
||||
Salt = p1_rand:bytes(?SALT_LENGTH),
|
||||
SaltedPassword = salted_password(Password, Salt, IterationCount),
|
||||
StoredKey = stored_key(scram2:client_key(SaltedPassword)),
|
||||
ServerKey = server_key(SaltedPassword),
|
||||
#scram{storedkey = base64:encode(StoredKey),
|
||||
serverkey = base64:encode(ServerKey),
|
||||
salt = base64:encode(Salt),
|
||||
iterationcount = IterationCount}.
|
||||
|
||||
check_password(Password, Scram) ->
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
Salt = base64:decode(Scram#scram.salt),
|
||||
SaltedPassword = salted_password(Password, Salt, IterationCount),
|
||||
StoredKey = stored_key(client_key(SaltedPassword)),
|
||||
(base64:decode(Scram#scram.storedkey) == StoredKey).
|
||||
|
||||
serialize(#scram{storedkey = StoredKey, serverkey = ServerKey,
|
||||
salt = Salt, iterationcount = IterationCount})->
|
||||
IterationCountBin = integer_to_binary(IterationCount),
|
||||
<< <<?SCRAM_SERIAL_PREFIX>>/binary,
|
||||
StoredKey/binary,$,,ServerKey/binary,
|
||||
$,,Salt/binary,$,,IterationCountBin/binary>>.
|
||||
|
||||
deserialize(<<?SCRAM_SERIAL_PREFIX, Serialized/binary>>) ->
|
||||
case catch binary:split(Serialized, <<",">>, [global]) of
|
||||
[StoredKey, ServerKey,Salt,IterationCount] ->
|
||||
{ok, #scram{storedkey = StoredKey,
|
||||
serverkey = ServerKey,
|
||||
salt = Salt,
|
||||
iterationcount = binary_to_integer(IterationCount)}};
|
||||
_ ->
|
||||
?WARNING_MSG("Incorrect serialized SCRAM: ~p, ~p", [Serialized]),
|
||||
{error, incorrect_scram}
|
||||
end;
|
||||
deserialize(Bin) ->
|
||||
?WARNING_MSG("Corrupted serialized SCRAM: ~p, ~p", [Bin]),
|
||||
{error, corrupted_scram}.
|
||||
|
||||
-spec scram_to_tuple(scram()) -> {binary(), binary(), binary(), non_neg_integer()}.
|
||||
scram_to_tuple(Scram) ->
|
||||
{base64:decode(Scram#scram.storedkey),
|
||||
base64:decode(Scram#scram.serverkey),
|
||||
base64:decode(Scram#scram.salt),
|
||||
Scram#scram.iterationcount}.
|
||||
|
||||
-spec check_digest(scram(), binary(), fun(), binary()) -> boolean().
|
||||
check_digest(#scram{storedkey = StoredKey}, Digest, DigestGen, Password) ->
|
||||
Passwd = base64:decode(StoredKey),
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end.
|
||||
|
||||
crypto_hmac(sha, Key, Data) ->
|
||||
misc:crypto_hmac(sha, Key, Data).
|
|
@ -1,15 +1,15 @@
|
|||
As a special exception, the authors give permission to link this program
|
||||
with the OpenSSL library and distribute the resulting binary.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
|
@ -18,7 +18,7 @@ software--to make sure the software is free for all its users. This
|
|||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
|
@ -58,8 +58,8 @@ patent must be licensed for everyone's free use or not licensed at all.
|
|||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
|
@ -113,7 +113,7 @@ above, provided that you also meet all of these conditions:
|
|||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
|
@ -171,7 +171,7 @@ access to copy from a designated place, then offering equivalent
|
|||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
|
@ -228,7 +228,7 @@ impose that choice.
|
|||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
|
@ -258,7 +258,7 @@ make exceptions for this. Our decision will be guided by the two goals
|
|||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
|
@ -280,9 +280,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
|||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
|
@ -310,13 +310,12 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
@ -339,5 +338,5 @@ necessary. Here is a sample; alter the names:
|
|||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
|
@ -0,0 +1,41 @@
|
|||
ejabberd observer_cli plugins
|
||||
=============================
|
||||
|
||||
[observer_cli][oc] is an erlang tool to
|
||||
visualize Erlang node statistics on the command line.
|
||||
|
||||
This directory contains several plugins
|
||||
specifically written to view statistics of [ejabberd][ej].
|
||||
|
||||
To install this, just run:
|
||||
```bash
|
||||
ejabberdctl module_install ejabberd_observer_cli
|
||||
```
|
||||
It will automatically download, compile and install the required dependencies:
|
||||
[observer_cli][oc], [recon][recon],
|
||||
and the additional plugin [os_stats][os].
|
||||
|
||||
Then, start an erlang shell attached to your ejabberd node, for example:
|
||||
```bash
|
||||
ejabberdctl debug
|
||||
```
|
||||
in that erlang shell execute:
|
||||
```erlang
|
||||
ejabberd_observer_cli:start().
|
||||
```
|
||||
|
||||
If using Elixir (for example when started with `ejabberdctl iexdebug`, run:
|
||||
```elixir
|
||||
:ejabberd_observer_cli.start()
|
||||
```
|
||||
|
||||
To sort columns or change between the different pages,
|
||||
type the corresponding letter and hit Enter.
|
||||
For example, to view MUC statistics, type: `M and then Enter`.
|
||||
|
||||
There is no configuration required.
|
||||
|
||||
[oc]: https://github.com/zhongwencool/observer_cli
|
||||
[os]: https://github.com/zhongwencool/os_stats
|
||||
[recon]: https://github.com/ferd/recon
|
||||
[ej]: https://www.ejabberd.im/
|
|
@ -0,0 +1,5 @@
|
|||
author: "Badlop <badlop at process-one.net>"
|
||||
category: "stats"
|
||||
summary: "Observer CLI plugins for ejabberd"
|
||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
|
||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
@ -0,0 +1,5 @@
|
|||
{deps, [
|
||||
{observer_cli, ".*", {git, "https://github.com/zhongwencool/observer_cli"}},
|
||||
{os_stats, ".*", {git, "https://github.com/zhongwencool/os_stats"}},
|
||||
{recon, ".*", {git, "https://github.com/ferd/recon"}}
|
||||
]}.
|
|
@ -0,0 +1,44 @@
|
|||
-module(ejabberd_observer_cli).
|
||||
|
||||
-export([start/0, mod_status/0]).
|
||||
|
||||
start() ->
|
||||
application:set_env(observer_cli, plugins, plugins(), [{persistent, true}]),
|
||||
observer_cli:start_plugin().
|
||||
|
||||
mod_status() ->
|
||||
"In an erlang shell run: ejabberd_observer_cli:start().".
|
||||
|
||||
plugins() ->
|
||||
[
|
||||
#{
|
||||
module => ejabberd_observer_cli_vhosts,
|
||||
title => "VHosts",
|
||||
interval => 1600,
|
||||
shortcut => "V",
|
||||
sort_column => 2
|
||||
},
|
||||
#{
|
||||
module => ejabberd_observer_cli_users,
|
||||
title => "Users",
|
||||
interval => 1600,
|
||||
shortcut => "U",
|
||||
sort_column => 2
|
||||
},
|
||||
%% #{module => ejabberd_observer_cli_userstophost, title => "Users Top Vhost",
|
||||
%% interval => 1600, shortcut => "T", sort_column => 2},
|
||||
#{
|
||||
module => ejabberd_observer_cli_muc,
|
||||
title => "MUC",
|
||||
interval => 1600,
|
||||
shortcut => "M",
|
||||
sort_column => 2
|
||||
},
|
||||
#{
|
||||
module => os_stats_plug,
|
||||
title => "OS",
|
||||
interval => 2000,
|
||||
shortcut => "O",
|
||||
sort_column => 2
|
||||
}
|
||||
].
|
|
@ -0,0 +1,48 @@
|
|||
-module(ejabberd_observer_cli_muc).
|
||||
|
||||
%% observer_cli_plugin Callback API
|
||||
-export([attributes/1, sheet_header/0, sheet_body/1]).
|
||||
|
||||
attributes(PrevState) ->
|
||||
OnlineRoomsNumber = lists:foldl(
|
||||
fun(Host, Acc) ->
|
||||
Acc + mod_muc:count_online_rooms(Host)
|
||||
end,
|
||||
0,
|
||||
mod_muc_admin:find_hosts(global)
|
||||
),
|
||||
|
||||
Attrs = [
|
||||
[
|
||||
#{content => "MUC Rooms", width => 25},
|
||||
#{content => OnlineRoomsNumber, width => 8}
|
||||
]
|
||||
],
|
||||
NewState = PrevState,
|
||||
{Attrs, NewState}.
|
||||
|
||||
sheet_header() ->
|
||||
[
|
||||
#{title => "Room Name", width => 20, shortcut => "n"},
|
||||
#{title => "MUC Service", width => 30, shortcut => "s"},
|
||||
#{title => "Occupants", width => 12, shortcut => "o"},
|
||||
#{title => "Subscribers", width => 13, shortcut => "r"}
|
||||
].
|
||||
|
||||
sheet_body(PrevState) ->
|
||||
Body = [
|
||||
begin
|
||||
{Name, Service, _} = jid:split(jid:decode(RoomStr)),
|
||||
OccupantsNumber = mod_muc_admin:get_room_occupants_number(Name, Service),
|
||||
SubsNumber = length(mod_muc_admin:get_subscribers(Name, Service)),
|
||||
[
|
||||
Name,
|
||||
Service,
|
||||
OccupantsNumber,
|
||||
SubsNumber
|
||||
]
|
||||
end
|
||||
|| RoomStr <- mod_muc_admin:muc_online_rooms(global)
|
||||
],
|
||||
NewState = PrevState,
|
||||
{Body, NewState}.
|
|
@ -0,0 +1,95 @@
|
|||
-module(ejabberd_observer_cli_users).
|
||||
|
||||
%% observer_cli_plugin Callback API
|
||||
-export([attributes/1, sheet_header/0, sheet_body/1]).
|
||||
|
||||
attributes(PrevState) ->
|
||||
Hosts = length(ejabberd_option:hosts()),
|
||||
RegisteredUsers =
|
||||
lists:foldl(
|
||||
fun(Host, Sum) ->
|
||||
ejabberd_auth:count_users(Host) + Sum
|
||||
end,
|
||||
0,
|
||||
ejabberd_option:hosts()
|
||||
),
|
||||
Sessions = length(ejabberd_sm:dirty_get_sessions_list()),
|
||||
SessionsThisNode = length(ejabberd_sm:dirty_get_my_sessions_list()),
|
||||
|
||||
Attrs = [
|
||||
[
|
||||
#{content => "Virtual Hosts", width => 18},
|
||||
#{content => Hosts, width => 8},
|
||||
#{content => "Sessions Total", width => 18},
|
||||
#{content => Sessions, width => 8}
|
||||
],
|
||||
[
|
||||
#{content => "Accounts Total", width => 18},
|
||||
#{content => RegisteredUsers, width => 8},
|
||||
#{content => "Sessions This Node", width => 18},
|
||||
#{content => SessionsThisNode, width => 8}
|
||||
]
|
||||
],
|
||||
NewState = PrevState,
|
||||
{Attrs, NewState}.
|
||||
|
||||
sheet_header() ->
|
||||
[
|
||||
#{title => "Username", width => 25, shortcut => "u"},
|
||||
#{title => "Host", width => 25, shortcut => "h"},
|
||||
#{title => "Sessions", width => 11, shortcut => "s"},
|
||||
#{title => "Roster", width => 9, shortcut => "r"},
|
||||
#{title => "Offline", width => 10, shortcut => "o"},
|
||||
#{title => "Last Activity", width => 20, shortcut => "l"}
|
||||
].
|
||||
|
||||
sheet_body(PrevState) ->
|
||||
Body = [
|
||||
begin
|
||||
[
|
||||
Username,
|
||||
Host,
|
||||
length(ejabberd_sm:get_user_resources(Username, Host)),
|
||||
length(mod_roster:get_roster(Username, Host)),
|
||||
mod_offline:count_offline_messages(Username, Host),
|
||||
get_last_activity(Username, Host)
|
||||
]
|
||||
end
|
||||
|| {Username, Host} <- lists:sort(ejabberd_auth:get_users())
|
||||
],
|
||||
NewState = PrevState,
|
||||
{Body, NewState}.
|
||||
|
||||
%% Code copied from ejabberd_web_admin.erl
|
||||
get_last_activity(User, Server) ->
|
||||
case ejabberd_sm:get_user_resources(User, Server) of
|
||||
[] ->
|
||||
case get_last_info(User, Server) of
|
||||
not_found ->
|
||||
"Never";
|
||||
{ok, Shift, _Status} ->
|
||||
TimeStamp = {Shift div 1000000, Shift rem 1000000, 0},
|
||||
{{Year, Month, Day}, {Hour, Minute, Second}} =
|
||||
calendar:now_to_local_time(TimeStamp),
|
||||
(io_lib:format(
|
||||
"~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
|
||||
[
|
||||
Year,
|
||||
Month,
|
||||
Day,
|
||||
Hour,
|
||||
Minute,
|
||||
Second
|
||||
]
|
||||
))
|
||||
end;
|
||||
_ ->
|
||||
"Online"
|
||||
end.
|
||||
get_last_info(User, Server) ->
|
||||
case gen_mod:is_loaded(Server, mod_last) of
|
||||
true ->
|
||||
mod_last:get_last_info(User, Server);
|
||||
false ->
|
||||
not_found
|
||||
end.
|
|
@ -0,0 +1,61 @@
|
|||
-module(ejabberd_observer_cli_userstophost).
|
||||
|
||||
%% observer_cli_plugin Callback API
|
||||
-export([attributes/1, sheet_header/0, sheet_body/1]).
|
||||
|
||||
get_top_host() ->
|
||||
lists:foldl(
|
||||
fun(Host, {HostRelativeMax, CountRelativeMax}) ->
|
||||
case ejabberd_auth:count_users(Host) of
|
||||
Count when Count > CountRelativeMax ->
|
||||
{Host, Count};
|
||||
_ ->
|
||||
{HostRelativeMax, CountRelativeMax}
|
||||
end
|
||||
end,
|
||||
{unknown, -1},
|
||||
ejabberd_option:hosts()
|
||||
).
|
||||
|
||||
attributes(PrevState) ->
|
||||
{Host, _} = get_top_host(),
|
||||
RegisteredUsers = ejabberd_auth:count_users(Host),
|
||||
Sessions = length(ejabberd_sm:dirty_get_sessions_list()),
|
||||
SessionsThisNode = length(ejabberd_sm:dirty_get_my_sessions_list()),
|
||||
|
||||
Attrs = [
|
||||
[
|
||||
#{content => "Virtual Host", width => 12},
|
||||
#{content => Host, width => 14},
|
||||
#{content => "Sessions Total", width => 18},
|
||||
#{content => Sessions, width => 8}
|
||||
],
|
||||
[
|
||||
#{content => "Accounts Total", width => 12},
|
||||
#{content => RegisteredUsers, width => 14},
|
||||
#{content => "Sessions This Node", width => 18},
|
||||
#{content => SessionsThisNode, width => 8}
|
||||
]
|
||||
],
|
||||
NewState = PrevState,
|
||||
{Attrs, NewState}.
|
||||
|
||||
sheet_header() ->
|
||||
[
|
||||
#{title => "Username", width => 25, shortcut => "u"},
|
||||
#{title => "Resources", width => 15, shortcut => "r"}
|
||||
].
|
||||
|
||||
sheet_body(PrevState) ->
|
||||
{Host, _} = get_top_host(),
|
||||
Body = [
|
||||
begin
|
||||
[
|
||||
Username,
|
||||
length(ejabberd_sm:get_user_resources(Username, Host))
|
||||
]
|
||||
end
|
||||
|| {Username, _} <- lists:reverse(lists:sort(ejabberd_auth:get_users(Host)))
|
||||
],
|
||||
NewState = PrevState,
|
||||
{Body, NewState}.
|
|
@ -0,0 +1,78 @@
|
|||
-module(ejabberd_observer_cli_vhosts).
|
||||
|
||||
%% observer_cli_plugin Callback API
|
||||
-export([attributes/1, sheet_header/0, sheet_body/1]).
|
||||
|
||||
attributes(PrevState) ->
|
||||
Hosts = length(ejabberd_option:hosts()),
|
||||
RegisteredUsers =
|
||||
lists:foldl(
|
||||
fun(Host, Sum) ->
|
||||
ejabberd_auth:count_users(Host) + Sum
|
||||
end,
|
||||
0,
|
||||
ejabberd_option:hosts()
|
||||
),
|
||||
Sessions = length(ejabberd_sm:dirty_get_sessions_list()),
|
||||
SessionsThisNode = length(ejabberd_sm:dirty_get_my_sessions_list()),
|
||||
|
||||
OnlineRoomsNumber = lists:foldl(
|
||||
fun(Host, Acc) ->
|
||||
Acc + mod_muc:count_online_rooms(Host)
|
||||
end,
|
||||
0,
|
||||
mod_muc_admin:find_hosts(global)
|
||||
),
|
||||
|
||||
Attrs = [
|
||||
[
|
||||
#{content => "Virtual Hosts", width => 25},
|
||||
#{content => Hosts, width => 8},
|
||||
#{content => "Sessions Total", width => 25},
|
||||
#{content => Sessions, width => 8}
|
||||
],
|
||||
[
|
||||
#{content => "Accounts Total", width => 25},
|
||||
#{content => RegisteredUsers, width => 8},
|
||||
#{content => "Sessions This Node", width => 25},
|
||||
#{content => SessionsThisNode, width => 8}
|
||||
],
|
||||
[
|
||||
#{content => "MUC Rooms", width => 25},
|
||||
#{content => OnlineRoomsNumber, width => 8}
|
||||
]
|
||||
],
|
||||
NewState = PrevState,
|
||||
{Attrs, NewState}.
|
||||
|
||||
sheet_header() ->
|
||||
[
|
||||
#{title => "Virtual Host", width => 38, shortcut => "v"},
|
||||
#{title => "Accounts", width => 11, shortcut => "a"},
|
||||
#{title => "Sessions", width => 11, shortcut => "s"},
|
||||
#{title => "Rooms", width => 8, shortcut => "r"}
|
||||
].
|
||||
|
||||
sheet_body(PrevState) ->
|
||||
Body = [
|
||||
begin
|
||||
RegisteredUsers = ejabberd_auth:count_users(Host),
|
||||
Sessions = ejabberd_sm:get_vh_session_number(Host),
|
||||
OnlineRoomsNumber = lists:foldl(
|
||||
fun(Host1, Acc) ->
|
||||
Acc + mod_muc:count_online_rooms(Host1)
|
||||
end,
|
||||
0,
|
||||
mod_muc_admin:find_hosts(Host)
|
||||
),
|
||||
[
|
||||
Host,
|
||||
RegisteredUsers,
|
||||
Sessions,
|
||||
OnlineRoomsNumber
|
||||
]
|
||||
end
|
||||
|| Host <- lists:reverse(lists:sort(ejabberd_option:hosts()))
|
||||
],
|
||||
NewState = PrevState,
|
||||
{Body, NewState}.
|
|
@ -0,0 +1,5 @@
|
|||
author: "Gregor Uhlenheuer <kongo2002 at gmail.com>"
|
||||
category: "archive"
|
||||
summary: "Message Archive Management (XEP-0313)"
|
||||
home: "https://github.com/kongo2002/ejabberd-mod-mam/tree/master/"
|
||||
url: "git@github.com:kongo2002/ejabberd-mod-mam.git"
|
|
@ -0,0 +1,5 @@
|
|||
author: "Lavrin"
|
||||
category: "admin"
|
||||
summary: "Easy tracing of connections made to ejabberd"
|
||||
home: "https://github.com/lavrin/ejabberd-trace/tree/master/"
|
||||
url: "git@github.com:lavrin/ejabberd-trace.git"
|
|
@ -0,0 +1,5 @@
|
|||
author: "Rael Max"
|
||||
category: "http"
|
||||
summary: "POST offline messages to a web"
|
||||
home: "https://github.com/raelmax/mod_http_offline/tree/master/"
|
||||
url: "git@github.com:raelmax/mod_http_offline.git"
|
|
@ -0,0 +1,5 @@
|
|||
author: "Jonas Ådahl <jadahl at gmail.com>"
|
||||
category: "http"
|
||||
summary: "RESTful API for ejabberd"
|
||||
home: "https://github.com/jadahl/mod_restful/tree/master/"
|
||||
url: "git@github.com:jadahl/mod_restful.git"
|
|
@ -1,27 +0,0 @@
|
|||
|
||||
MODULES=`pwd`
|
||||
|
||||
# Put here the path to ejabberd source
|
||||
EJADIR=$MODULES/../git/ejabberd/
|
||||
|
||||
# TODO: Support extraction of multiple modules
|
||||
#MODULE=mod_webpresence
|
||||
MODULE=mod_register_web
|
||||
|
||||
RUNDIR=$MODULES/$MODULE/trunk/
|
||||
PREPARESCRIPT=$EJADIR/contrib/extract_translations/prepare-translation.sh
|
||||
|
||||
# 1. Create the directory $MODULE/msgs/
|
||||
|
||||
# 2. Create the $MODULE.pot
|
||||
#$PREPARESCRIPT -rundir $RUNDIR -ejadir $EJADIR -project $MODULE -src2pot
|
||||
|
||||
# 3. Create a language
|
||||
# cp $MODULE.pot $LANG.$MODULE.po
|
||||
# echo "" > $LANG.$MODULE.msg
|
||||
|
||||
# 3.b Convert msg to po. But it doesn't work! :(
|
||||
#$PREPARESCRIPT -rundir $RUNDIR -ejadir $EJADIR -project $MODULE -srcmsg2po we
|
||||
|
||||
# 4. Update strings
|
||||
$PREPARESCRIPT -rundir $RUNDIR -ejadir $EJADIR -project $MODULE -updateall
|
|
@ -1 +0,0 @@
|
|||
{'src/ejabberd_ircd', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
|
@ -1,4 +1,15 @@
|
|||
|
||||
|
||||
***************
|
||||
PLEASE NOTE
|
||||
***************
|
||||
|
||||
This module does NOT work
|
||||
with ejabberd 13 or newer.
|
||||
|
||||
***************
|
||||
|
||||
|
||||
ircd - IRC-to-XMPP interface
|
||||
|
||||
Author:
|
||||
|
@ -9,7 +20,6 @@
|
|||
http://www.dtek.chalmers.se/~henoch/text/ejabberd-ircd.html
|
||||
Requirements:
|
||||
ejabberd trunk SVN 1631 or newer
|
||||
Does NOT work with ejabberd 13 or newer
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
erl -pa ../ejabberd-dev/ebin -pa ebin -make
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/sh
|
||||
erl -pa ../ejabberd-dev/ebin -pz ebin -make
|
|
@ -0,0 +1,5 @@
|
|||
author: "Magnus Henoch <henoch at dtek.chalmers.se>"
|
||||
category: "listener"
|
||||
summary: "IRC server frontend to ejabberd"
|
||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
|
||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
@ -84,7 +84,7 @@ socket_type() ->
|
|||
%% {stop, StopReason}
|
||||
%%----------------------------------------------------------------------
|
||||
init([{SockMod, Socket}, Opts]) ->
|
||||
iconv:start(),
|
||||
%iconv:start(),
|
||||
Access = case lists:keysearch(access, 1, Opts) of
|
||||
{value, {_, A}} -> A;
|
||||
_ -> all
|
||||
|
@ -134,7 +134,8 @@ init([{SockMod, Socket}, Opts]) ->
|
|||
}}.
|
||||
|
||||
handle_info({tcp, _Socket, Line}, StateName, StateData) ->
|
||||
DecodedLine = iconv:convert(StateData#state.encoding, "utf-8", Line),
|
||||
%DecodedLine = iconv:convert(StateData#state.encoding, "utf-8", Line),
|
||||
DecodedLine = Line,
|
||||
Parsed = parse_line(DecodedLine),
|
||||
?MODULE:StateName({line, Parsed}, StateData);
|
||||
handle_info({tcp_closed, _}, _StateName, StateData) ->
|
||||
|
@ -163,8 +164,8 @@ terminate(_Reason, _StateName, #state{socket = Socket, sockmod = SockMod,
|
|||
none ->
|
||||
ok;
|
||||
_ ->
|
||||
Packet = {xmlelement, "presence",
|
||||
[{"type", "unavailable"}], []},
|
||||
Packet = {xmlel, <<"presence">>,
|
||||
[{<<"type">>, <<"unavailable">>}], []},
|
||||
FromJID = user_jid(State),
|
||||
?DICT:map(fun(ChannelJID, _ChannelData) ->
|
||||
ejabberd_router:route(FromJID, ChannelJID, Packet)
|
||||
|
@ -186,8 +187,10 @@ wait_for_nick({line, #line{command = "NICK", params = Params}}, State) ->
|
|||
Nick = hd(Params),
|
||||
Pass = State#state.pass,
|
||||
Server = State#state.host,
|
||||
?DEBUG("user=~p server=~p", [Nick, Server]),
|
||||
|
||||
JID = jlib:make_jid(Nick, Server, "irc"),
|
||||
JID = jlib:make_jid(list_to_binary(Nick), Server, <<"irc">>),
|
||||
?DEBUG("JID=~p", [JID]),
|
||||
case JID of
|
||||
error ->
|
||||
?DEBUG("invalid nick '~p'", [Nick]),
|
||||
|
@ -200,7 +203,7 @@ wait_for_nick({line, #line{command = "NICK", params = Params}}, State) ->
|
|||
send_reply('ERR_NICKCOLLISION', [Nick, "Nickname collision"], State),
|
||||
{next_state, wait_for_nick, State};
|
||||
allow ->
|
||||
case ejabberd_auth:check_password(Nick, Server, Pass) of
|
||||
case ejabberd_auth:check_password(list_to_binary(Nick), Server, list_to_binary(Pass)) of
|
||||
false ->
|
||||
?DEBUG("auth failed for '~p'", [Nick]),
|
||||
send_reply('ERR_NICKCOLLISION', [Nick, "Authentication failed"], State),
|
||||
|
@ -209,15 +212,15 @@ wait_for_nick({line, #line{command = "NICK", params = Params}}, State) ->
|
|||
?DEBUG("good nickname '~p'", [Nick]),
|
||||
SID = {now(), self()},
|
||||
ejabberd_sm:open_session(
|
||||
SID, Nick, Server, "irc", peerip(gen_tcp, State#state.socket)),
|
||||
ejabberd_sm:set_presence(SID, Nick, Server, "irc",
|
||||
SID, list_to_binary(Nick), Server, <<"irc">>, peerip(gen_tcp, State#state.socket)),
|
||||
ejabberd_sm:set_presence(SID, list_to_binary(Nick), Server, <<"irc">>,
|
||||
3, "undefined",
|
||||
[{'ip', peerip(gen_tcp, State#state.socket)}, {'conn','c2s'}, {'state',"+"}]),
|
||||
send_text_command("", "001", [Nick, "IRC interface of ejabberd server "++Server], State),
|
||||
send_reply('RPL_MOTDSTART', [Nick, "- "++Server++" Message of the day - "], State),
|
||||
send_reply('RPL_MOTD', [Nick, "- This is the IRC interface of the ejabberd server "++Server++"."], State),
|
||||
send_reply('RPL_MOTD', [Nick, "- Your full JID is "++Nick++"@"++Server++"/irc."], State),
|
||||
send_reply('RPL_MOTD', [Nick, "- Channel #whatever corresponds to MUC room whatever@"++State#state.muc_host++"."], State),
|
||||
send_reply('RPL_MOTDSTART', [Nick, "- "++binary_to_list(Server)++" Message of the day - "], State),
|
||||
send_reply('RPL_MOTD', [Nick, "- This is the IRC interface of the ejabberd server "++binary_to_list(Server)++"."], State),
|
||||
send_reply('RPL_MOTD', [Nick, "- Your full JID is "++Nick++"@"++binary_to_list(Server)++"/irc."], State),
|
||||
send_reply('RPL_MOTD', [Nick, "- Channel #whatever corresponds to MUC room whatever@"++binary_to_list(State#state.muc_host)++"."], State),
|
||||
send_reply('RPL_MOTD', [Nick, "- This IRC interface is quite immature. You will probably find bugs."], State),
|
||||
send_reply('RPL_MOTD', [Nick, "- Have a good time!"], State),
|
||||
send_reply('RPL_ENDOFMOTD', [Nick, "End of /MOTD command"], State),
|
||||
|
@ -244,6 +247,7 @@ wait_for_cmd({line, #line{command = "USER", params = [_Username, _Hostname, _Ser
|
|||
%% Yeah, like we care.
|
||||
{next_state, wait_for_cmd, State};
|
||||
wait_for_cmd({line, #line{command = "JOIN", params = Params}}, State) ->
|
||||
?DEBUG("received JOIN ~p", [Params]),
|
||||
{ChannelsString, KeysString} =
|
||||
case Params of
|
||||
[C, K] ->
|
||||
|
@ -253,7 +257,9 @@ wait_for_cmd({line, #line{command = "JOIN", params = Params}}, State) ->
|
|||
end,
|
||||
Channels = string:tokens(ChannelsString, ","),
|
||||
Keys = string:tokens(KeysString, ","),
|
||||
?DEBUG("joining channels ~p", [Channels]),
|
||||
NewState = join_channels(Channels, Keys, State),
|
||||
?DEBUG("joined channels ~p", [Channels]),
|
||||
{next_state, wait_for_cmd, NewState};
|
||||
|
||||
%% USERHOST command
|
||||
|
@ -294,18 +300,18 @@ wait_for_cmd({line, #line{command = "PRIVMSG", params = [To, Text]}}, State) ->
|
|||
fun(Rcpt) ->
|
||||
case Rcpt of
|
||||
[$# | Roomname] ->
|
||||
Packet = {xmlelement, "message",
|
||||
[{"type", "groupchat"}],
|
||||
[{xmlelement, "body", [],
|
||||
Packet = {xmlel, <<"message">>,
|
||||
[{<<"type">>, <<"groupchat">>}],
|
||||
[{xmlel, <<"body">>, [],
|
||||
filter_cdata(translate_action(Text))}]},
|
||||
ToJID = channel_to_jid(Roomname, State),
|
||||
ejabberd_router:route(FromJID, ToJID, Packet);
|
||||
_ ->
|
||||
case string:tokens(Rcpt, "#") of
|
||||
[Nick, Channel] ->
|
||||
Packet = {xmlelement, "message",
|
||||
[{"type", "chat"}],
|
||||
[{xmlelement, "body", [],
|
||||
Packet = {xmlel, <<"message">>,
|
||||
[{<<"type">>, <<"chat">>}],
|
||||
[{xmlel, <<"body">>, [],
|
||||
filter_cdata(translate_action(Text))}]},
|
||||
ToJID = channel_nick_to_jid(Nick, Channel, State),
|
||||
ejabberd_router:route(FromJID, ToJID, Packet);
|
||||
|
@ -357,9 +363,9 @@ wait_for_cmd({line, #line{command = "TOPIC", params = Params}}, State) ->
|
|||
end;
|
||||
[Channel, NewTopic] ->
|
||||
Packet =
|
||||
{xmlelement, "message",
|
||||
[{"type", "groupchat"}],
|
||||
[{xmlelement, "subject", [], filter_cdata(NewTopic)}]},
|
||||
{xmlel, <<"message">>,
|
||||
[{<<"type">>, <<"groupchat">>}],
|
||||
[{xmlel, <<"subject">>, [], filter_cdata(NewTopic)}]},
|
||||
FromJID = user_jid(State),
|
||||
ToJID = channel_to_jid(Channel, State),
|
||||
ejabberd_router:route(FromJID, ToJID, Packet)
|
||||
|
@ -411,10 +417,11 @@ wait_for_cmd({line, #line{command = Unknown, params = Params} = Line}, State) ->
|
|||
Unknown ++ "/" ++ integer_to_list(length(Params))], State),
|
||||
{next_state, wait_for_cmd, State};
|
||||
|
||||
wait_for_cmd({route, From, _To, {xmlelement, "presence", Attrs, Els} = El}, State) ->
|
||||
wait_for_cmd({route, From, _To, {xmlel, <<"presence">>, Attrs, Els} = El}, State) ->
|
||||
?DEBUG("Received a Presence ~p ~p ~p", [From, _To, El]),
|
||||
Type = xml:get_attr_s("type", Attrs),
|
||||
FromRoom = jlib:jid_remove_resource(From),
|
||||
FromNick = From#jid.resource,
|
||||
FromNick = binary_to_list(From#jid.resource),
|
||||
|
||||
Channel = jid_to_channel(From, State),
|
||||
MyNick = State#state.nick,
|
||||
|
@ -422,24 +429,29 @@ wait_for_cmd({route, From, _To, {xmlelement, "presence", Attrs, Els} = El}, Stat
|
|||
|
||||
Joining = ?DICT:find(FromRoom, State#state.joining),
|
||||
Joined = ?DICT:find(FromRoom, State#state.joined),
|
||||
?DEBUG("JoinState ~p ~p ~p", [Joining, Joined, Type]),
|
||||
case {Joining, Joined, Type} of
|
||||
{{ok, BufferedNicks}, _, ""} ->
|
||||
{{ok, BufferedNicks}, _, <<"">>} ->
|
||||
?DEBUG("BufferedNicks ~p", [BufferedNicks]),
|
||||
case BufferedNicks of
|
||||
[] ->
|
||||
%% If this is the first presence, tell the
|
||||
%% client that it's joining.
|
||||
send_command(make_irc_sender(MyNick, FromRoom, State),
|
||||
"JOIN", [Channel], State);
|
||||
?DEBUG("Sending Command ~p ~p", [IRCSender, Channel]),
|
||||
send_command(IRCSender, "JOIN", [Channel], State),
|
||||
?DEBUG("Command Sent", []);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
|
||||
?DEBUG("Getting NewRole", []),
|
||||
NewRole = case find_el("x", ?NS_MUC_USER, Els) of
|
||||
nothing ->
|
||||
"";
|
||||
XMucEl ->
|
||||
xml:get_path_s(XMucEl, [{elem, "item"}, {attr, "role"}])
|
||||
end,
|
||||
?DEBUG("NewRole ~p", [NewRole]),
|
||||
NewBufferedNicks = [{FromNick, NewRole} | BufferedNicks],
|
||||
?DEBUG("~s is present in ~s. we now have ~p.",
|
||||
[FromNick, Channel, NewBufferedNicks]),
|
||||
|
@ -479,14 +491,14 @@ wait_for_cmd({route, From, _To, {xmlelement, "presence", Attrs, Els} = El}, Stat
|
|||
State#state{joining = NewJoining}
|
||||
end,
|
||||
{next_state, wait_for_cmd, NewState};
|
||||
{{ok, _BufferedNicks}, _, "error"} ->
|
||||
{{ok, _BufferedNicks}, _, <<"error">>} ->
|
||||
NewState =
|
||||
case FromNick of
|
||||
MyNick ->
|
||||
%% we couldn't join the room
|
||||
{ReplyCode, ErrorDescription} =
|
||||
case xml:get_subtag(El, "error") of
|
||||
{xmlelement, _, _, _} = ErrorEl ->
|
||||
{xmlel, _, _, _} = ErrorEl ->
|
||||
{ErrorName, ErrorText} = parse_error(ErrorEl),
|
||||
{case ErrorName of
|
||||
"forbidden" -> 'ERR_INVITEONLYCHAN';
|
||||
|
@ -511,7 +523,7 @@ wait_for_cmd({route, From, _To, {xmlelement, "presence", Attrs, Els} = El}, Stat
|
|||
end,
|
||||
{next_state, wait_for_cmd, NewState};
|
||||
%% Presence in a channel we have already joined
|
||||
{_, {ok, _}, ""} ->
|
||||
{_, {ok, _}, <<"">>} ->
|
||||
%% Someone enters
|
||||
send_command(IRCSender, "JOIN", [Channel], State),
|
||||
{next_state, wait_for_cmd, State};
|
||||
|
@ -524,29 +536,37 @@ wait_for_cmd({route, From, _To, {xmlelement, "presence", Attrs, Els} = El}, Stat
|
|||
{next_state, wait_for_cmd, State}
|
||||
end;
|
||||
|
||||
wait_for_cmd({route, From, _To, {xmlelement, "message", Attrs, Els} = El}, State) ->
|
||||
Type = xml:get_attr_s("type", Attrs),
|
||||
wait_for_cmd({route, From, _To, {xmlel, <<"message">>, Attrs, Els} = El}, State) ->
|
||||
?DEBUG("Got a Message! ~p ~p ~p", [From, _To, El]),
|
||||
Type = xml:get_attr_s(<<"type">>, Attrs),
|
||||
case Type of
|
||||
"groupchat" ->
|
||||
<<"groupchat">> ->
|
||||
?DEBUG("It's a groupchat", []),
|
||||
ChannelJID = jlib:jid_remove_resource(From),
|
||||
case ?DICT:find(ChannelJID, State#state.joined) of
|
||||
{ok, #channel{} = ChannelData} ->
|
||||
FromChannel = jid_to_channel(From, State),
|
||||
FromNick = From#jid.resource,
|
||||
Subject = xml:get_path_s(El, [{elem, "subject"}, cdata]),
|
||||
Body = xml:get_path_s(El, [{elem, "body"}, cdata]),
|
||||
XDelay = lists:any(fun({xmlelement, "x", XAttrs, _}) ->
|
||||
xml:get_attr_s("xmlns", XAttrs) == ?NS_DELAY;
|
||||
FromNick = binary_to_list(From#jid.resource),
|
||||
Subject = xml:get_path_s(El, [{elem, <<"subject">>}, cdata]),
|
||||
Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]),
|
||||
?DEBUG("Message Data ~p ~p", [Subject, Body]),
|
||||
XDelay = lists:any(fun({xmlel, <<"x">>, XAttrs, _}) ->
|
||||
xml:get_attr_s(<<"xmlns">>, XAttrs) == ?NS_DELAY;
|
||||
(_) ->
|
||||
false
|
||||
end, Els),
|
||||
?DEBUG("XDelay ~p", [XDelay]),
|
||||
if
|
||||
Subject /= "" ->
|
||||
Subject /= <<"">> ->
|
||||
?DEBUG("Cleaning Subject!", []),
|
||||
CleanSubject = lists:map(fun($\n) ->
|
||||
$\ ;
|
||||
(C) -> C
|
||||
end, Subject),
|
||||
send_text_command(make_irc_sender(From, State),
|
||||
end, binary_to_list(Subject)),
|
||||
?DEBUG("CleanSubject ~p", [CleanSubject]),
|
||||
IRCSender = make_irc_sender(From, State),
|
||||
?DEBUG("IRCSender ~p", [IRCSender]),
|
||||
send_text_command(IRCSender,
|
||||
"TOPIC", [FromChannel, CleanSubject], State),
|
||||
NewChannelData = ChannelData#channel{topic = CleanSubject},
|
||||
NewState = State#state{joined = ?DICT:store(jlib:jid_remove_resource(From), NewChannelData, State#state.joined)},
|
||||
|
@ -554,9 +574,11 @@ wait_for_cmd({route, From, _To, {xmlelement, "message", Attrs, Els} = El}, State
|
|||
not XDelay, FromNick == State#state.nick ->
|
||||
%% there is no message echo in IRC.
|
||||
%% we let the backlog through, though.
|
||||
?DEBUG("Don't care about it", []),
|
||||
{next_state, wait_for_cmd, State};
|
||||
true ->
|
||||
BodyLines = string:tokens(Body, "\n"),
|
||||
?DEBUG("Send it to someone!", []),
|
||||
BodyLines = string:tokens(binary_to_list(Body), "\n"),
|
||||
lists:foreach(
|
||||
fun(Line) ->
|
||||
Line1 =
|
||||
|
@ -576,7 +598,7 @@ wait_for_cmd({route, From, _To, {xmlelement, "message", Attrs, Els} = El}, State
|
|||
[jlib:jid_to_string(ChannelJID)]),
|
||||
{next_state, wait_for_cmd, State}
|
||||
end;
|
||||
"error" ->
|
||||
<<"error">> ->
|
||||
MucHost = State#state.muc_host,
|
||||
ErrorFrom =
|
||||
case From of
|
||||
|
@ -595,7 +617,7 @@ wait_for_cmd({route, From, _To, {xmlelement, "message", Attrs, Els} = El}, State
|
|||
%% I think this should cover all possible combinations of
|
||||
%% XMPP and non-XMPP error messages...
|
||||
ErrorText =
|
||||
error_to_string(xml:get_subtag(El, "error")),
|
||||
error_to_string(xml:get_subtag(El, <<"error">>)),
|
||||
send_text_command("", "NOTICE", [State#state.nick,
|
||||
"Message to "++ErrorFrom++" bounced: "++
|
||||
ErrorText], State),
|
||||
|
@ -604,8 +626,8 @@ wait_for_cmd({route, From, _To, {xmlelement, "message", Attrs, Els} = El}, State
|
|||
ChannelJID = jlib:jid_remove_resource(From),
|
||||
case ?DICT:find(ChannelJID, State#state.joined) of
|
||||
{ok, #channel{}} ->
|
||||
FromNick = From#jid.lresource++jid_to_channel(From, State),
|
||||
Body = xml:get_path_s(El, [{elem, "body"}, cdata]),
|
||||
FromNick = binary_to_list(From#jid.lresource)++jid_to_channel(From, State),
|
||||
Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]),
|
||||
BodyLines = string:tokens(Body, "\n"),
|
||||
lists:foreach(
|
||||
fun(Line) ->
|
||||
|
@ -636,30 +658,36 @@ join_channels(Channels, [], State) ->
|
|||
join_channels([Channel | Channels], [Key | Keys],
|
||||
#state{nick = Nick} = State) ->
|
||||
Packet =
|
||||
{xmlelement, "presence", [],
|
||||
[{xmlelement, "x", [{"xmlns", ?NS_MUC}],
|
||||
{xmlel, <<"presence">>, [],
|
||||
[{xmlel, <<"x">>, [{<<"xmlns">>, ?NS_MUC}],
|
||||
case Key of
|
||||
none ->
|
||||
[];
|
||||
_ ->
|
||||
[{xmlelement, "password", [], filter_cdata(Key)}]
|
||||
[{xmlel, <<"password">>, [], filter_cdata(Key)}]
|
||||
end}]},
|
||||
?DEBUG("joining channel nick=~p channel=~p state=~p", [Nick, Channel, State]),
|
||||
From = user_jid(State),
|
||||
?DEBUG("1 ~p", [From]),
|
||||
To = channel_nick_to_jid(Nick, Channel, State),
|
||||
?DEBUG("2 ~p", [To]),
|
||||
Room = jlib:jid_remove_resource(To),
|
||||
?DEBUG("3 ~p", [Room]),
|
||||
ejabberd_router:route(From, To, Packet),
|
||||
?DEBUG("4", []),
|
||||
NewState = State#state{joining = ?DICT:store(Room, [], State#state.joining)},
|
||||
?DEBUG("5 ~p", [NewState]),
|
||||
join_channels(Channels, Keys, NewState).
|
||||
|
||||
part_channels([], State, _Message) ->
|
||||
State;
|
||||
part_channels([Channel | Channels], State, Message) ->
|
||||
Packet =
|
||||
{xmlelement, "presence",
|
||||
[{"type", "unavailable"}],
|
||||
{xmlel, <<"presence">>,
|
||||
[{<<"type">>, <<"unavailable">>}],
|
||||
case Message of
|
||||
nothing -> [];
|
||||
_ -> [{xmlelement, "status", [],
|
||||
_ -> [{xmlel, <<"status">>, [],
|
||||
[{xmlcdata, Message}]}]
|
||||
end},
|
||||
From = user_jid(State),
|
||||
|
@ -704,7 +732,8 @@ upcase([C|String]) ->
|
|||
send_line(Line, #state{sockmod = SockMod, socket = Socket, encoding = Encoding}) ->
|
||||
?DEBUG("sending ~s", [Line]),
|
||||
gen_tcp = SockMod,
|
||||
EncodedLine = iconv:convert("utf-8", Encoding, Line),
|
||||
%EncodedLine = iconv:convert("utf-8", Encoding, Line),
|
||||
EncodedLine = Line,
|
||||
ok = gen_tcp:send(Socket, [EncodedLine, 13, 10]).
|
||||
|
||||
send_command(Sender, Command, Params, State) ->
|
||||
|
@ -716,9 +745,10 @@ send_text_command(Sender, Command, Params, State) ->
|
|||
send_command(Sender, Command, Params, State, true).
|
||||
|
||||
send_command(Sender, Command, Params, State, AlwaysQuote) ->
|
||||
?DEBUG("SendCommand ~p ~p ~p", [Sender, Command, Params]),
|
||||
Prefix = case Sender of
|
||||
"" ->
|
||||
[$: | State#state.host];
|
||||
[$: | binary_to_list(State#state.host)];
|
||||
_ ->
|
||||
[$: | Sender]
|
||||
end,
|
||||
|
@ -784,7 +814,7 @@ make_param_string([Param | Params], AlwaysQuote) ->
|
|||
" " ++ Param ++ make_param_string(Params, AlwaysQuote)
|
||||
end.
|
||||
|
||||
find_el(Name, NS, [{xmlelement, N, Attrs, _} = El|Els]) ->
|
||||
find_el(Name, NS, [{xmlel, N, Attrs, _} = El|Els]) ->
|
||||
XMLNS = xml:get_attr_s("xmlns", Attrs),
|
||||
case {Name, NS} of
|
||||
{N, XMLNS} ->
|
||||
|
@ -801,7 +831,7 @@ channel_to_jid(Channel, #state{muc_host = MucHost,
|
|||
channels_to_jids = ChannelsToJids}) ->
|
||||
case ?DICT:find(Channel, ChannelsToJids) of
|
||||
{ok, RoomJID} -> RoomJID;
|
||||
_ -> jlib:make_jid(Channel, MucHost, "")
|
||||
_ -> jlib:make_jid(list_to_binary(Channel), MucHost, <<"">>)
|
||||
end.
|
||||
|
||||
channel_nick_to_jid(Nick, [$#|Channel], State) ->
|
||||
|
@ -809,28 +839,28 @@ channel_nick_to_jid(Nick, [$#|Channel], State) ->
|
|||
channel_nick_to_jid(Nick, Channel, #state{muc_host = MucHost,
|
||||
channels_to_jids = ChannelsToJids}) ->
|
||||
case ?DICT:find(Channel, ChannelsToJids) of
|
||||
{ok, RoomJID} -> jlib:jid_replace_resource(RoomJID, Nick);
|
||||
_ -> jlib:make_jid(Channel, MucHost, Nick)
|
||||
{ok, RoomJID} -> jlib:jid_replace_resource(RoomJID, list_to_binary(Nick));
|
||||
_ -> jlib:make_jid(list_to_binary(Channel), MucHost, list_to_binary(Nick))
|
||||
end.
|
||||
|
||||
jid_to_channel(#jid{user = Room} = RoomJID,
|
||||
#state{jids_to_channels = JidsToChannels}) ->
|
||||
case ?DICT:find(jlib:jid_remove_resource(RoomJID), JidsToChannels) of
|
||||
{ok, Channel} -> [$#|Channel];
|
||||
_ -> [$#|Room]
|
||||
{ok, Channel} -> [$#|binary_to_list(Channel)];
|
||||
_ -> [$#|binary_to_list(Room)]
|
||||
end.
|
||||
|
||||
make_irc_sender(Nick, #jid{luser = Room} = RoomJID,
|
||||
#state{jids_to_channels = JidsToChannels}) ->
|
||||
case ?DICT:find(jlib:jid_remove_resource(RoomJID), JidsToChannels) of
|
||||
{ok, Channel} -> Nick++"!"++Nick++"@"++Channel;
|
||||
_ -> Nick++"!"++Nick++"@"++Room
|
||||
{ok, Channel} -> Nick++"!"++Nick++"@"++binary_to_list(Channel);
|
||||
_ -> Nick++"!"++Nick++"@"++binary_to_list(Room)
|
||||
end.
|
||||
make_irc_sender(#jid{lresource = Nick} = JID, State) ->
|
||||
make_irc_sender(Nick, JID, State).
|
||||
make_irc_sender(binary_to_list(Nick), JID, State).
|
||||
|
||||
user_jid(#state{nick = Nick, host = Host}) ->
|
||||
jlib:make_jid(Nick, Host, "irc").
|
||||
jlib:make_jid(list_to_binary(Nick), Host, <<"irc">>).
|
||||
|
||||
filter_cdata(Msg) ->
|
||||
[{xmlcdata, filter_message(Msg)}].
|
||||
|
@ -857,25 +887,25 @@ translate_action(Msg) ->
|
|||
Msg
|
||||
end.
|
||||
|
||||
parse_error({xmlelement, "error", _ErrorAttrs, ErrorEls} = ErrorEl) ->
|
||||
parse_error({xmlel, "error", _ErrorAttrs, ErrorEls} = ErrorEl) ->
|
||||
ErrorTextEl = xml:get_subtag(ErrorEl, "text"),
|
||||
ErrorName =
|
||||
case ErrorEls -- [ErrorTextEl] of
|
||||
[{xmlelement, ErrorReason, _, _}] ->
|
||||
[{xmlel, ErrorReason, _, _}] ->
|
||||
ErrorReason;
|
||||
_ ->
|
||||
"unknown error"
|
||||
end,
|
||||
ErrorText =
|
||||
case ErrorTextEl of
|
||||
{xmlelement, _, _, _} ->
|
||||
{xmlel, _, _, _} ->
|
||||
xml:get_tag_cdata(ErrorTextEl);
|
||||
_ ->
|
||||
nothing
|
||||
end,
|
||||
{ErrorName, ErrorText}.
|
||||
|
||||
error_to_string({xmlelement, "error", _ErrorAttrs, _ErrorEls} = ErrorEl) ->
|
||||
error_to_string({xmlel, "error", _ErrorAttrs, _ErrorEls} = ErrorEl) ->
|
||||
case parse_error(ErrorEl) of
|
||||
{ErrorName, ErrorText} when is_list(ErrorText) ->
|
||||
ErrorName ++ ": " ++ ErrorText;
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
2009-06-15 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/mod_admin_extra.erl: Added command to check the password by
|
||||
providing a MD5 or SHA hash.
|
||||
|
||||
2009-02-16 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/mod_admin_extra.erl: Fix srg_user_add arguments
|
||||
|
||||
2009-02-09 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/mod_admin_extra.erl: Calculation of 'stats registeredusers'
|
||||
works also with non-internal authentication methods (thanks to
|
||||
Francois de Metz)(EJAB-864)
|
||||
|
||||
2009-01-27 Badlop <badlop@process-one.net>
|
||||
|
||||
* README.txt: Explicitely mention that this module does not work
|
||||
with any ejabberd 2.0.x or older
|
||||
|
||||
* src/mod_admin_extra.erl: In the srg_create command, allow to
|
||||
separate group identifiers in the Display argument with escaped
|
||||
newline character. Explain this possibility in the command long
|
||||
description.
|
||||
|
||||
2008-11-10 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/mod_admin_extra.erl: Renamed command send_message to
|
||||
send_message_headline. New command send_message_chat. (thanks to
|
||||
Xyu)
|
||||
|
||||
2008-10-31 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/mod_admin_extra.erl: Deleted the duplicated command
|
||||
delete_older_messages (EJAB-814). Rename the other delete_older_*
|
||||
commands.
|
||||
|
||||
2008-10-12 Badlop <badlop@process-one.net>
|
||||
|
||||
* README.txt: It requires ejabberd trunk SVN 1635 or newer
|
||||
|
||||
* src/mod_admin_extra.erl: Renamed mod_ctlextra to
|
||||
mod_admin_extra, as it now implements ejabberd commands instead of
|
||||
ejabberdctl commands
|
||||
|
||||
2008-04-26 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/mod_ctlextra.erl: New command: ban-account
|
||||
* README.txt: Documented new command
|
||||
|
||||
2008-04-04 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/mod_ctlextra.erl: Added new command
|
||||
rosteritem-purge [options]
|
||||
|
||||
2008-03-19 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/mod_ctlextra.erl: Added two new commands for administration
|
||||
of Shared Roster Groups: "srg-list-groups host" and "srg-get-info
|
||||
group host" (thanks to Taylor Laramie)
|
||||
|
||||
2008-03-09 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/mod_ctlextra.erl: Show the result of each table export
|
||||
|
||||
2008-03-04 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/mod_ctlextra.erl: New command 'stats
|
||||
onlineusersnode'. Removed old unusued code.
|
||||
|
||||
* README.txt: Updated module page
|
||||
|
||||
2008-01-20 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/mod_ctlextra.erl: Allow to define group name with spaces
|
||||
* README.txt: Likewise
|
||||
|
||||
2007-11-14 Badlop <badlop@ono.com>
|
||||
|
||||
* src/mod_ctlextra.erl: Updated to ejabberd SVN.
|
||||
|
||||
2007-09-08 Badlop <badlop@ono.com>
|
||||
|
||||
* src/mod_ctlextra.erl: Bugfix: unregister commands when module
|
||||
stops. Removed all MUC-related commands.
|
||||
|
||||
2007-08-29 Badlop <badlop@ono.com>
|
||||
|
||||
* src/mod_ctlextra.erl: Added command: stats uptime-seconds.
|
||||
|
||||
2007-08-24 Badlop <badlop@ono.com>
|
||||
|
||||
* src/mod_ctlextra.erl: Fixed bug in vcard-set command that
|
||||
forgot second level attributes.
|
||||
|
||||
2007-08-23 Badlop <badlop@ono.com>
|
||||
|
||||
* src/mod_ctlextra.erl: Command add-rosteritem now pushes the
|
||||
roster item to the client. New command rem-rosteritem.
|
||||
|
||||
2007-08-19 Badlop <badlop@ono.com>
|
||||
|
||||
* src/mod_ctlextra.erl: Now vcard-get and vcard-set are more
|
||||
compatible with mod_vcard_odbc and mod_vcard_ldap.
|
||||
|
||||
2007-08-16 Badlop <badlop@ono.com>
|
||||
|
||||
* src/mod_ctlextra.erl: add-rosteritem only adds an item in a
|
||||
roster.
|
||||
|
||||
2007-08-07 Badlop <badlop@ono.com>
|
||||
|
||||
* src/mod_ctlextra.erl: Fixed indentation.
|
||||
|
||||
* ChangeLog: New file to track changes.
|
||||
|
||||
* README.txt: Removed Changelog section.
|
|
@ -1,2 +0,0 @@
|
|||
{'../ejabberd-dev/src/gen_mod', [{outdir, "../ejabberd-dev/ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||
{'src/mod_admin_extra', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
|
@ -1,81 +0,0 @@
|
|||
|
||||
|
||||
mod_admin_extra - Additional ejabberd commands
|
||||
|
||||
Author: Badlop
|
||||
Homepage: http://www.ejabberd.im/mod_admin_extra
|
||||
|
||||
|
||||
CONFIGURATION
|
||||
=============
|
||||
|
||||
Add the module to your ejabberd.yml, on the modules section:
|
||||
modules:
|
||||
mod_admin_extra: {}
|
||||
|
||||
The configurable options are:
|
||||
- module_resource:
|
||||
Indicate the resource that the XMPP stanzas must use in the FROM or TO JIDs.
|
||||
This is only useful in the vcard set and get commands.
|
||||
The default value is "mod_admin_extra".
|
||||
|
||||
In this example configuration, the users vcards can only be modified
|
||||
by executing mod_admin_extra commands.
|
||||
Notice that this needs the patch
|
||||
https://support.process-one.net/browse/EJAB-797
|
||||
acl:
|
||||
adminextraresource:
|
||||
resource: "modadminextraf8x,31ad"
|
||||
access:
|
||||
vcard_set:
|
||||
adminextraresource: allow
|
||||
all: deny
|
||||
modules:
|
||||
mod_admin_extra:
|
||||
module_resource: "modadminextraf8x,31ad"
|
||||
mod_vcard:
|
||||
access_set: vcard_set
|
||||
|
||||
|
||||
USAGE
|
||||
=====
|
||||
|
||||
Now you have several new commands in ejabberdctl.
|
||||
|
||||
Description of some commands:
|
||||
|
||||
- vcard-*
|
||||
Example: ejabberdctl vcard-get joe myjab.net email
|
||||
|
||||
- pushroster*
|
||||
The file used by 'pushroster' and 'pushroster-all' must be placed:
|
||||
- Windows: on the directory were you installed ejabberd:
|
||||
'C:/Program Files/ejabberd'
|
||||
- Other OS: on the same directory where the .beam files are.
|
||||
Example content for the roster file:
|
||||
[{<<"bob">>, <<"example.org">>, <<"workers">>, <<"Bob">>},
|
||||
{<<"mart">>, <<"example.org">>, <<"workers">>, <<"Mart">>},
|
||||
{<<"Rich">>, <<"example.org">>, <<"bosses">>, <<"Rich">>}].
|
||||
|
||||
- srg-create
|
||||
If you want to put a group Name with blankspaces, use the characters
|
||||
"' and '" to define when the Name starts and ends.
|
||||
For example:
|
||||
ejabberdctl srg-create g1 example.org "'Group number 1'" this_is_g1 g1
|
||||
|
||||
- ban-account
|
||||
|
||||
This command kicks all the connected sessions of the account from the
|
||||
server. It also changes his password to another randomly
|
||||
generated, so he can't login anymore unless a server administrator
|
||||
changes him again the password.
|
||||
|
||||
It is possible to define the reason of the ban. The new password
|
||||
also includes the reason and the date and time of the ban.
|
||||
|
||||
For example, if this command is called:
|
||||
ejabberdctl vhost example.org ban-account boby Spammed several MUC rooms
|
||||
then the sessions of the local account which JID is boby@example.org
|
||||
will be kicked, and its password will be set to something like this:
|
||||
BANNED_ACCOUNT--20080425T21:45:07--2176635--Spammed_several_MUC_rooms
|
||||
|
|
@ -1 +0,0 @@
|
|||
erl -pa ../ejabberd-dev/ebin -pa ebin -make
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/sh
|
||||
erl -pa ../ejabberd-dev/ebin -pz ebin -make
|
File diff suppressed because it is too large
Load Diff
|
@ -1,426 +0,0 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%%% File : mod_ecomm_test.erl
|
||||
%%% Author : Badlop <badlop@process-one.net>
|
||||
%%% Purpose : Simple commands for testing
|
||||
%%% Created : 10 Aug 2008 by Badlop <badlop@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2008 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(mod_ecomm_test).
|
||||
-author('badlop@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1,
|
||||
%% Take: test arguments
|
||||
take_integer/1,
|
||||
take_string/1,
|
||||
take_integer_string/2,
|
||||
take_tuple_2integer/1,
|
||||
take_tuple_2string/1,
|
||||
take_list_integer/1,
|
||||
take_list_string/1,
|
||||
%% Echo: test arguments and result
|
||||
echo_integer/1,
|
||||
echo_string/1,
|
||||
echo_integer_string/2,
|
||||
echo_list_integer/1,
|
||||
echo_list_string/1,
|
||||
echo_integer_list_string/2,
|
||||
echo_isatils/4,
|
||||
%% Tell: test result
|
||||
tell_atom/1,
|
||||
tell_rescode/1,
|
||||
tell_restuple/1,
|
||||
tell_tuple_3integer/0,
|
||||
tell_tuple_3string/0,
|
||||
tell_tuple_3atom/0,
|
||||
tell_tuple_3list/0,
|
||||
tell_list_3integer/0,
|
||||
tell_list_3string/0,
|
||||
tell_list_3atom/0,
|
||||
tell_list_3tuple/0,
|
||||
%% Realistic
|
||||
this_crashes/1,
|
||||
this_wrong_args/1,
|
||||
this_wrong_return/0,
|
||||
pow/2, seq/2, substrs/1, splitjid/1, splitjids/1
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
start(_Host, _Opts) ->
|
||||
ejabberd_commands:register_commands(commands()).
|
||||
|
||||
stop(_Host) ->
|
||||
ejabberd_commands:unregister_commands(commands()).
|
||||
|
||||
%%%
|
||||
%%% ejabberd commands
|
||||
%%%
|
||||
|
||||
commands() ->
|
||||
[
|
||||
|
||||
#ejabberd_commands{name = take_integer, tags = [test],
|
||||
desc = "Take Integer in args, give Integer zero",
|
||||
module = ?MODULE, function = take_integer,
|
||||
args = [{thisinteger, integer}],
|
||||
result = {zero, integer}},
|
||||
|
||||
#ejabberd_commands{name = take_string, tags = [test],
|
||||
desc = "Take String, give Integer zero",
|
||||
module = ?MODULE, function = take_string,
|
||||
args = [{thisstring, string}],
|
||||
result = {zero, integer}},
|
||||
|
||||
#ejabberd_commands{name = take_integer_string, tags = [test],
|
||||
desc = "Take integer and string, give Integer zero",
|
||||
module = ?MODULE, function = take_integer_string,
|
||||
args = [{thisinteger, integer}, {thisstring, string}],
|
||||
result = {zero, integer}},
|
||||
|
||||
%% Not supported by ejabberd_ctl
|
||||
#ejabberd_commands{name = take_tuple_2integer, tags = [test],
|
||||
desc = "Take Tuple of two integers, give Integer zero",
|
||||
module = ?MODULE, function = take_tuple_2integer,
|
||||
args = [{thistuple, {tuple, [{thisinteger1, integer}, {thisinteger2, integer}]}}],
|
||||
result = {zero, integer}},
|
||||
%% Not supported by ejabberd_ctl
|
||||
#ejabberd_commands{name = take_tuple_2string, tags = [test],
|
||||
desc = "Take Tuple of two strings, give Integer zero",
|
||||
module = ?MODULE, function = take_tuple_2string,
|
||||
args = [{thistuple, {tuple, [{thisstring1, string}, {thisstring2, string}]}}],
|
||||
result = {zero, integer}},
|
||||
%% Not supported by ejabberd_ctl
|
||||
#ejabberd_commands{name = take_list_integer, tags = [test],
|
||||
desc = "Take List of integers, give Integer zero",
|
||||
module = ?MODULE, function = take_list_integer,
|
||||
args = [{thislist, {list, {thisinteger, integer}}}],
|
||||
result = {zero, integer}},
|
||||
%% Not supported by ejabberd_ctl
|
||||
#ejabberd_commands{name = take_list_string, tags = [test],
|
||||
desc = "Take List of strings, give Integer zero",
|
||||
module = ?MODULE, function = take_list_string,
|
||||
args = [{thislist, {list, {thisstring, string}}}],
|
||||
result = {zero, integer}},
|
||||
|
||||
#ejabberd_commands{name = echo_integer, tags = [test],
|
||||
desc = "Echo Integer",
|
||||
module = ?MODULE, function = echo_integer,
|
||||
args = [{thisinteger, integer}],
|
||||
result = {thatinteger, integer}},
|
||||
#ejabberd_commands{name = echo_string, tags = [test],
|
||||
desc = "Echo String",
|
||||
module = ?MODULE, function = echo_string,
|
||||
args = [{thisstring, string}],
|
||||
result = {thatstring, string}},
|
||||
#ejabberd_commands{name = echo_integer_string, tags = [test],
|
||||
desc = "Echo integer and string, in result as a tuple",
|
||||
module = ?MODULE, function = echo_integer_string,
|
||||
args = [{thisinteger, integer}, {thisstring, string}],
|
||||
result = {thistuple, {tuple, [{thisinteger, integer}, {thisstring, string}]}}},
|
||||
%% Not supported by ejabberd_ctl
|
||||
#ejabberd_commands{name = echo_list_integer, tags = [test],
|
||||
desc = "Echo List of integers",
|
||||
module = ?MODULE, function = echo_list_integer,
|
||||
args = [{thislist, {list, {thisinteger, integer}}}],
|
||||
result = {thatlist, {list, {thatinteger, integer}}}},
|
||||
%% Not supported by ejabberd_ctl
|
||||
#ejabberd_commands{name = echo_list_string, tags = [test],
|
||||
desc = "Echo List of strings",
|
||||
module = ?MODULE, function = echo_list_string,
|
||||
args = [{thislist, {list, {thisstring, string}}}],
|
||||
result = {thatlist, {list, {thatstring, string}}}},
|
||||
%% Not supported by ejabberd_ctl
|
||||
#ejabberd_commands{name = echo_integer_list_string, tags = [test],
|
||||
desc = "Echo an integer and List of strings",
|
||||
module = ?MODULE, function = echo_integer_list_string,
|
||||
args = [{thisinteger, integer}, {thislist, {list, {thisstring, string}}}],
|
||||
result = {thistuple, {tuple, [{thatinteger, integer}, {thatlist, {list, {thatstring, string}}}]}}},
|
||||
%% Not supported by ejabberd_ctl
|
||||
#ejabberd_commands{name = echo_isatils, tags = [test],
|
||||
desc = "Echo integer, string, atom and tuple of integer and list of strings",
|
||||
module = ?MODULE, function = echo_isatils,
|
||||
args = [{thisinteger, integer},
|
||||
{thisstring, string},
|
||||
{thisatom, atom},
|
||||
{thistuple, {tuple, [
|
||||
{listlen, integer},
|
||||
{thislist, {list, {contentstring, string}}}
|
||||
]}}
|
||||
],
|
||||
result = {results, {tuple, [{thatinteger, integer},
|
||||
{thatstring, string},
|
||||
{thatatom, atom},
|
||||
{thattuple, {tuple, [
|
||||
{listlen, integer},
|
||||
{thatlist, {list, {contentstring, string}}}
|
||||
]}}
|
||||
]}}},
|
||||
|
||||
#ejabberd_commands{name = tell_atom, tags = [test],
|
||||
desc = "Tell Atom, give Integer zero",
|
||||
module = ?MODULE, function = tell_atom,
|
||||
args = [{thisinteger, integer}],
|
||||
result = {thisatom, atom}},
|
||||
#ejabberd_commands{name = tell_rescode, tags = [test],
|
||||
desc = "Tell rescode",
|
||||
module = ?MODULE, function = tell_rescode,
|
||||
args = [{thisinteger, integer}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = tell_restuple, tags = [test],
|
||||
desc = "Tell restuple",
|
||||
module = ?MODULE, function = tell_restuple,
|
||||
args = [{thisinteger, integer}],
|
||||
result = {res, restuple}},
|
||||
#ejabberd_commands{name = tell_tuple_3integer, tags = [test],
|
||||
desc = "Tell a tuple with 3 integers",
|
||||
module = ?MODULE, function = tell_tuple_3integer,
|
||||
args = [],
|
||||
result = {thattuple, {tuple, [{first, integer},
|
||||
{second, integer},
|
||||
{third, integer}]}}},
|
||||
#ejabberd_commands{name = tell_tuple_3string, tags = [test],
|
||||
desc = "Tell a tuple with 3 strings",
|
||||
module = ?MODULE, function = tell_tuple_3string,
|
||||
args = [],
|
||||
result = {thattuple, {tuple, [{first, string},
|
||||
{second, string},
|
||||
{third, string}]}}},
|
||||
#ejabberd_commands{name = tell_tuple_3atom, tags = [test],
|
||||
desc = "Tell a tuple with 3 atoms",
|
||||
module = ?MODULE, function = tell_tuple_3atom,
|
||||
args = [],
|
||||
result = {thattuple, {tuple, [{first, atom},
|
||||
{second, atom},
|
||||
{third, atom}]}}},
|
||||
#ejabberd_commands{name = tell_tuple_3list, tags = [test],
|
||||
desc = "Tell a tuple with 3 lists",
|
||||
module = ?MODULE, function = tell_tuple_3list,
|
||||
args = [],
|
||||
result = {thattuple, {tuple,
|
||||
[{first, {list,
|
||||
{thisinteger, integer}}},
|
||||
{second, {list,
|
||||
{thisstring, string}}},
|
||||
{third, {list,
|
||||
{thisatom, atom}}}]}}},
|
||||
|
||||
#ejabberd_commands{name = tell_list_3integer, tags = [test],
|
||||
desc = "Tell a list with 3 integers",
|
||||
module = ?MODULE, function = tell_list_3integer,
|
||||
args = [],
|
||||
result = {thatlist, {list, {thisinteger, integer}}}},
|
||||
#ejabberd_commands{name = tell_list_3string, tags = [test],
|
||||
desc = "Tell a list with 3 strings",
|
||||
module = ?MODULE, function = tell_list_3string,
|
||||
args = [],
|
||||
result = {thatlist, {list, {thisstring, string}}}},
|
||||
#ejabberd_commands{name = tell_list_3atom, tags = [test],
|
||||
desc = "Tell a list with 3 atoms",
|
||||
module = ?MODULE, function = tell_list_3atom,
|
||||
args = [],
|
||||
result = {thatlist, {list, {thisatom, atom}}}},
|
||||
#ejabberd_commands{name = tell_list_3tuple, tags = [test],
|
||||
desc = "Tell a list with 3 tuples",
|
||||
module = ?MODULE, function = tell_list_3tuple,
|
||||
args = [],
|
||||
result = {thatlist, {list, {thistuple,
|
||||
{tuple,
|
||||
[{thisinteger, integer},
|
||||
{thistring, string},
|
||||
{thisatom, atom}]}}}}},
|
||||
|
||||
#ejabberd_commands{name = this_crashes, tags = [test],
|
||||
desc = "This command crashes: test+5",
|
||||
module = ?MODULE, function = this_crashes,
|
||||
args = [{aninteger, integer}],
|
||||
result = {result, integer}},
|
||||
#ejabberd_commands{name = this_wrong_args, tags = [test],
|
||||
desc = "This problematic command defines 2 arguments but function expects 1",
|
||||
module = ?MODULE, function = this_wrong_args,
|
||||
args = [{a, integer}, {b, integer}],
|
||||
result = {result, integer}},
|
||||
#ejabberd_commands{name = this_wrong_return, tags = [test],
|
||||
desc = "This problematic command doesn't give a proper return",
|
||||
module = ?MODULE, function = this_wrong_return,
|
||||
args = [],
|
||||
result = {result, integer}},
|
||||
|
||||
#ejabberd_commands{name = pow, tags = [test],
|
||||
desc = "Return the power of base for exponent",
|
||||
longdesc = "This is an example command. The formula is:\n"
|
||||
" power = base ^ exponent",
|
||||
module = ?MODULE, function = pow,
|
||||
args = [{base, integer}, {exponent, integer}],
|
||||
result = {power, integer}},
|
||||
|
||||
#ejabberd_commands{name = seq, tags = [test],
|
||||
desc = "Return list of integers between two integers",
|
||||
module = ?MODULE, function = seq,
|
||||
args = [{from, integer}, {to, integer}],
|
||||
result = {sequence, {list, {intermediate, integer}}}},
|
||||
|
||||
#ejabberd_commands{name = substrs, tags = [test],
|
||||
desc = "Return list of substrings of length increasing",
|
||||
module = ?MODULE, function = substrs,
|
||||
args = [{word, string}],
|
||||
result = {substrings, {list, {miniword, string}}}},
|
||||
|
||||
#ejabberd_commands{name = splitjid, tags = [test],
|
||||
desc = "Split JID in parts: user, server, resource",
|
||||
module = ?MODULE, function = splitjid,
|
||||
args = [{jid, string}],
|
||||
result = {jidparts, {tuple, [{user, string},
|
||||
{server, string},
|
||||
{resource, string}]}}},
|
||||
|
||||
%% Not supported by ejabberd_ctl because uses 'list' in the arguments
|
||||
#ejabberd_commands{name = splitjids, tags = [test],
|
||||
desc = "Split JIDs in parts: user, server, resource",
|
||||
module = ?MODULE, function = splitjids,
|
||||
args = [{jids, {list, {jid, string}}}],
|
||||
result = {jidsparts,
|
||||
{list, {jidparts,
|
||||
{tuple, [{user, string},
|
||||
{server, string},
|
||||
{resource, string}]}}}}}
|
||||
|
||||
].
|
||||
|
||||
%%%
|
||||
%%% Take
|
||||
%%%
|
||||
|
||||
take_integer(A) when is_integer(A) -> 0.
|
||||
take_string(A) when is_list(A) -> 0.
|
||||
take_integer_string(A, B)
|
||||
when is_integer(A) and is_list(B) ->
|
||||
0.
|
||||
take_tuple_2integer({A, B})
|
||||
when is_integer(A) and is_integer(B) ->
|
||||
0.
|
||||
take_tuple_2string({A, B})
|
||||
when is_list(A) and is_list(B) ->
|
||||
0.
|
||||
take_list_integer(L)
|
||||
when is_list(L) ->
|
||||
true = lists:all(fun(A) -> is_integer(A) end, L),
|
||||
0.
|
||||
take_list_string(L)
|
||||
when is_list(L) ->
|
||||
true = lists:all(fun(A) -> is_list(A) end, L),
|
||||
0.
|
||||
|
||||
%%%
|
||||
%%% Echo
|
||||
%%%
|
||||
|
||||
echo_integer(A) when is_integer(A) -> A.
|
||||
echo_string(A) when is_list(A) -> A.
|
||||
echo_integer_string(A, B) when is_integer(A) and is_list(B) -> {A, B}.
|
||||
echo_list_integer(L)
|
||||
when is_list(L) ->
|
||||
true = lists:all(fun(A) -> is_integer(A) end, L),
|
||||
L.
|
||||
echo_list_string(L)
|
||||
when is_list(L) ->
|
||||
true = lists:all(fun(A) -> is_list(A) end, L),
|
||||
L.
|
||||
echo_integer_list_string(I, L)
|
||||
when is_integer(I) and is_list(L) ->
|
||||
true = lists:all(fun(A) -> is_list(A) end, L),
|
||||
{I, L}.
|
||||
echo_isatils(I, S, A, {II, L})
|
||||
when is_integer(I) and is_list(S) and is_atom(A) and is_integer(II) and is_list(L) ->
|
||||
true = lists:all(fun(SS) -> is_list(SS) end, L),
|
||||
{I, S, A, {I, L}}.
|
||||
|
||||
|
||||
%%%
|
||||
%%% Tell
|
||||
%%%
|
||||
|
||||
tell_atom(0) -> zero;
|
||||
tell_atom(1) -> one;
|
||||
tell_atom(A) when is_integer(A) -> greater_than_one.
|
||||
|
||||
tell_rescode(0) -> ok;
|
||||
tell_rescode(1) -> true;
|
||||
tell_rescode(2) -> error;
|
||||
tell_rescode(3) -> false;
|
||||
tell_rescode(4) -> whatever.
|
||||
|
||||
tell_restuple(0) -> {ok, "All OK"};
|
||||
tell_restuple(1) -> {true, "Successful result"};
|
||||
tell_restuple(2) -> {error, "This is an error message"}.
|
||||
|
||||
tell_tuple_3integer() -> {123, 456, 789}.
|
||||
tell_tuple_3string() -> {"Tell", "me", "a tuple please"}.
|
||||
tell_tuple_3atom() -> {ok, works, perfectly}.
|
||||
tell_tuple_3list() -> {[1, 23, 456], ["Tell", "me"], [all, is, ok]}.
|
||||
|
||||
tell_list_3integer() -> [123, 456, 789].
|
||||
tell_list_3string() -> ["Tell", "me", "a tuple please"].
|
||||
tell_list_3atom() -> [ok, works, perfectly].
|
||||
tell_list_3tuple() ->
|
||||
[{123, "abcdefghijkl", first},
|
||||
{593, "this string", morning},
|
||||
{999, "Sleeping dog", not_seen}].
|
||||
|
||||
|
||||
%%%
|
||||
%%% Realistic
|
||||
%%%
|
||||
|
||||
%% This function will crash for sure
|
||||
this_crashes(Integer) ->
|
||||
test + Integer.
|
||||
|
||||
this_wrong_args(Integer) ->
|
||||
Integer + 1.
|
||||
|
||||
this_wrong_return() ->
|
||||
"this is a string".
|
||||
|
||||
pow(Base, Exponent) ->
|
||||
PowFloat = math:pow(Base, Exponent),
|
||||
round(PowFloat).
|
||||
|
||||
seq(From, To) ->
|
||||
lists:seq(From, To).
|
||||
|
||||
%% For "stick" returns: s st sti stic stick
|
||||
substrs(Word) ->
|
||||
Lengths = lists:seq(1, string:len(Word)),
|
||||
[string:substr(Word, 1, Length) || Length <- Lengths].
|
||||
|
||||
splitjid(String) ->
|
||||
JID = jlib:string_to_jid(String),
|
||||
{JID#jid.user,
|
||||
JID#jid.server,
|
||||
JID#jid.resource}.
|
||||
|
||||
splitjids(Strings) ->
|
||||
[splitjid(String) || String <- Strings].
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{'../ejabberd-dev/src/gen_mod', [{outdir, "../ejabberd-dev/ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||
{'src/mod_archive', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||
{'src/mod_archive_sql', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||
{'src/mod_archive_odbc', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||
{'src/mod_archive_webview', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
|
@ -1,6 +1,13 @@
|
|||
|
||||
|
||||
Please note: those modules do NOT work with ejabberd 13 or newer.
|
||||
***************
|
||||
PLEASE NOTE
|
||||
***************
|
||||
|
||||
Those modules do NOT work
|
||||
with ejabberd 13 or newer.
|
||||
|
||||
***************
|
||||
|
||||
|
||||
mod_archive - Message Archiving (XEP-0136)
|
||||
|
@ -60,7 +67,7 @@ Based in mod_archive, author: Olivier Goffart <ogoffart at kde.org>
|
|||
Based in mod_archive_sql, author: Alexey Shchepin
|
||||
|
||||
For a detailed documentation about this module, please refer to
|
||||
http://www.ndl.kiev.ua/typo/articles/2007/11/14/mod_archive_odbc-release
|
||||
http://endl.ch/content/mod_archive_odbc-release
|
||||
|
||||
|
||||
MOD_ARCHIVE_WEBVIEW
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
erl -pa ../ejabberd-dev/ebin -pa ebin -make
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/sh
|
||||
erl -pa ../ejabberd-dev/ebin -pz ebin -make
|
|
@ -0,0 +1,5 @@
|
|||
author: "Olivier Goffart <ogoffart at kde.org>"
|
||||
category: "archive"
|
||||
summary: "Supports almost all the XEP-0136 version 0.6 except otr"
|
||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
|
||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
@ -0,0 +1,342 @@
|
|||
As a special exception, the authors give permission to link this program
|
||||
with the OpenSSL library and distribute the resulting binary.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
|
@ -0,0 +1,112 @@
|
|||
mod_captcha_rust - Generate CAPTCHAs using Rust library
|
||||
=======================================================
|
||||
|
||||
Requires:
|
||||
- ejabberd 23.xx or higher compiled with Elixir
|
||||
- Erlang/OTP and Elixir to compile the `captcha_nif` Elixir library
|
||||
- Rust compiler (package `rustc` in some package managers)
|
||||
|
||||
|
||||
This small module generates CAPTCHA images suitable for ejabberd's
|
||||
[CAPTCHA](https://docs.ejabberd.im/admin/configuration/basic/#captcha) feature,
|
||||
thanks to the [captcha](https://github.com/feng19/captcha) Elixir library,
|
||||
which internally uses the [captcha](https://github.com/daniel-e/captcha) Rust library.
|
||||
|
||||
See example CAPTCHA images in the
|
||||
[captcha Rust documentation](https://docs.rs/captcha/latest/captcha/).
|
||||
|
||||
|
||||
Get `captcha_nif` library
|
||||
-------------------------
|
||||
|
||||
This module depends on an Elixir library, and there are two ways to get it installed,
|
||||
depending on how you obtained ejabberd:
|
||||
|
||||
### A) ejabberd source code
|
||||
|
||||
If you compile ejabberd from source code, go to the path with your
|
||||
ejabberd source code and apply this small patch:
|
||||
```diff
|
||||
--- a/mix.exs
|
||||
+++ b/mix.exs
|
||||
@@ -99,6 +99,7 @@ defmodule Ejabberd.MixProject do
|
||||
|
||||
defp deps do
|
||||
[{:base64url, "~> 1.0"},
|
||||
+ {:captcha_nif, "~> 0.1", hex: :captcha_nif},
|
||||
{:cache_tab, "~> 1.0"},
|
||||
{:eimp, "~> 1.0"},
|
||||
{:ex_doc, ">= 0.0.0", only: :dev},
|
||||
```
|
||||
|
||||
Then get the new dependencies, and compile as usual:
|
||||
```
|
||||
mix deps.get
|
||||
make
|
||||
```
|
||||
|
||||
Install ejabberd as usual, restart it
|
||||
and now you can proceed to install `mod_captcha_rust`.
|
||||
|
||||
|
||||
### B) ejabberd with binary installer
|
||||
|
||||
If you installed ejabberd using some kind of binary installer,
|
||||
you can download, compile and install the required library manually:
|
||||
|
||||
```
|
||||
git clone https://github.com/feng19/captcha.git
|
||||
cd captcha/
|
||||
mix deps.get
|
||||
mix
|
||||
mix release
|
||||
```
|
||||
|
||||
Copy all the directories from `_build/dev/rel/captcha/lib`
|
||||
to the path where you have ejabberd lib directories installed.
|
||||
|
||||
Now you can restart ejabberd and proceed to install `mod_captcha_rust`.
|
||||
|
||||
|
||||
Basic Configuration
|
||||
-------------------
|
||||
|
||||
The minimal configuration required to get this module working is:
|
||||
|
||||
```yaml
|
||||
captcha_cmd: 'Elixir.ModCaptchaRust'
|
||||
|
||||
captcha_url: http://localhost:5280/captcha
|
||||
|
||||
listen:
|
||||
-
|
||||
port: 5280
|
||||
module: ejabberd_http
|
||||
request_handlers:
|
||||
/captcha: ejabberd_captcha
|
||||
```
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
This module supports those configurable options:
|
||||
|
||||
* `difficulty`
|
||||
|
||||
Sets the CAPTCHA difficulty, it can be `easy`, `medium` or `hard`.
|
||||
Default value: `easy`.
|
||||
|
||||
* `color`
|
||||
|
||||
Sets the CAPTCHA color, as a list of RGB integers from 0 to 255.
|
||||
Default value: `[0, 0, 0]`.
|
||||
|
||||
|
||||
Example of module configuration:
|
||||
|
||||
```yaml
|
||||
modules:
|
||||
'Elixir.ModCaptchaRust':
|
||||
difficulty: hard
|
||||
color: [255, 0, 0]
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
captcha_cmd: 'Elixir.ModCaptchaRust'
|
||||
|
||||
captcha_url: http://localhost:5288/captcha/
|
||||
|
||||
listen:
|
||||
-
|
||||
port: 5288
|
||||
module: ejabberd_http
|
||||
request_handlers:
|
||||
/captcha: ejabberd_captcha
|
||||
|
||||
modules:
|
||||
'Elixir.ModCaptchaRust': {}
|
|
@ -0,0 +1,63 @@
|
|||
defmodule ModCaptchaRust do
|
||||
use Ejabberd.Module
|
||||
|
||||
require Record
|
||||
|
||||
##====================================================================
|
||||
## gen_mod callbacks
|
||||
##====================================================================
|
||||
|
||||
def start(_host, _opts) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def stop(_host) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def depends(_host, _opts) do
|
||||
[]
|
||||
end
|
||||
|
||||
def mod_opt_type(:color) do
|
||||
:econf.list(:econf.int(0, 255))
|
||||
end
|
||||
|
||||
def mod_opt_type(:difficulty) do
|
||||
:econf.enum([:easy, :medium, :hard])
|
||||
end
|
||||
|
||||
def mod_options(_host) do
|
||||
[
|
||||
{:color, [0, 0, 0]},
|
||||
{:difficulty, :easy},
|
||||
]
|
||||
end
|
||||
|
||||
def mod_doc() do
|
||||
%{:desc => 'This is just a demonstration.'}
|
||||
end
|
||||
|
||||
##====================================================================
|
||||
## Captcha Rust
|
||||
##====================================================================
|
||||
|
||||
def create_image(_key) do
|
||||
[r, g, b] = get_opt(:color)
|
||||
color = %{r: r, g: g, b: b}
|
||||
|
||||
{captcha_text, captcha_image} = case get_opt(:difficulty) do
|
||||
:easy -> Captcha.easy(set_color: color)
|
||||
:medium -> Captcha.medium(set_color: color)
|
||||
:hard -> Captcha.hard(set_color: color)
|
||||
end
|
||||
|
||||
{:ok, "image/png", captcha_text, captcha_image}
|
||||
end
|
||||
|
||||
def get_opt(option_name) do
|
||||
host = List.first(:ejabberd_option.hosts())
|
||||
:gen_mod.get_module_opt(host, Elixir.ModCaptchaRust, option_name)
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
author: "Badlop <badlop at process-one.net>"
|
||||
category: "captcha"
|
||||
summary: "Generate CAPTCHAs using Rust library"
|
||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
|
||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
@ -1,2 +0,0 @@
|
|||
{'../ejabberd-dev/src/gen_mod', [{outdir, "../ejabberd-dev/ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||
{'src/mod_cron', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
|
@ -0,0 +1,212 @@
|
|||
mod_cron - Execute scheduled tasks
|
||||
==================================
|
||||
|
||||
* Requires: ejabberd 19.08 or higher
|
||||
* http://www.ejabberd.im/mod_cron
|
||||
* Author: Badlop
|
||||
|
||||
|
||||
This module allows advanced ejabberd administrators to schedule tasks for
|
||||
periodic and automatic execution. This module is a similar concept than the
|
||||
Unix's cron program.
|
||||
|
||||
Each time a scheduled task finishes its execution, a message is printed in the
|
||||
ejabberd log file.
|
||||
|
||||
|
||||
Basic Configuration
|
||||
-------------------
|
||||
|
||||
Add the module to the `modules` section on the configuration file:
|
||||
```yaml
|
||||
modules:
|
||||
mod_cron: {}
|
||||
```
|
||||
|
||||
Then, using the `tasks` option, you can add a list of tasks.
|
||||
For each task there are options to define _when_ to run it,
|
||||
and options to define _what_ to run.
|
||||
|
||||
When
|
||||
----
|
||||
|
||||
Those options determine when a task is ran:
|
||||
|
||||
* `time: integer()`
|
||||
|
||||
* `units: seconds | minutes | hours | days`
|
||||
|
||||
Indicates the time unit to use.
|
||||
|
||||
* `timer_type: interval | fixed`
|
||||
|
||||
Default value is `interval`.
|
||||
Fixed timers occur at a fixed time
|
||||
after the [minute|hour|day] e.g. every hour on the 5th minute (1:05PM, 2:05PM etc).
|
||||
Interval timers occur every interval (starting on an even unit) e.g. every 10 minutes
|
||||
starting at 1PM, 1:10PM, 1:20PM etc.
|
||||
Fixed timers are the equivalent of unix cron's comma syntax e.g. `"2 * * *"`
|
||||
and interval timers are the `/` syntax e.g. `"*/5 * * *"`.
|
||||
|
||||
What
|
||||
----
|
||||
|
||||
You can define a task to run some ejabberd API (either in command or in ctl syntax),
|
||||
or any arbitrary erlang function.
|
||||
|
||||
### Command
|
||||
|
||||
Use the option `command` and provide `arguments` in the correct format:
|
||||
|
||||
```yaml
|
||||
command: delete_old_mam_messages
|
||||
arguments:
|
||||
- "all"
|
||||
- 0
|
||||
```
|
||||
|
||||
This requires a recent ejabberd version that includes
|
||||
[this commit](https://github.com/processone/ejabberd/commit/10481ed895016893ee9dc3fe23cd937fdc46ded6),
|
||||
and `api_permissions` configured to allow mod_cron, for example:
|
||||
|
||||
```yaml
|
||||
api_permissions:
|
||||
"console commands":
|
||||
from:
|
||||
- ejabberd_ctl
|
||||
- mod_cron
|
||||
who: all
|
||||
what: "*"
|
||||
```
|
||||
|
||||
### Ctl
|
||||
|
||||
Use the option `ctl` and provide all `arguments` with quotes:
|
||||
|
||||
```yaml
|
||||
ctl: delete_old_mam_messages
|
||||
arguments:
|
||||
- "all"
|
||||
- "0"
|
||||
```
|
||||
|
||||
### Erlang Function
|
||||
|
||||
Use `module`, `function`, and provide `arguments` in the correct format:
|
||||
|
||||
```yaml
|
||||
module: ejabberd_auth
|
||||
function: try_register
|
||||
arguments:
|
||||
- "user1"
|
||||
- "localhost"
|
||||
- "somepass"
|
||||
```
|
||||
|
||||
Please note the arguments in string format will be converted to binaries.
|
||||
If the function expects strings, you can add the option `args_type: string`:
|
||||
|
||||
```yaml
|
||||
module: mnesia
|
||||
function: backup
|
||||
args_type: string
|
||||
arguments:
|
||||
- "/var/log/ejabberd/mnesia.backup"
|
||||
```
|
||||
|
||||
Example Tasks
|
||||
-------------
|
||||
|
||||
Those example tasks show how to specify arguments in the basic erlang formats:
|
||||
```yaml
|
||||
modules:
|
||||
mod_cron:
|
||||
tasks:
|
||||
- time: 30
|
||||
units: seconds
|
||||
module: erlang
|
||||
function: is_integer
|
||||
arguments:
|
||||
- 123456
|
||||
- time: 31
|
||||
units: seconds
|
||||
module: erlang
|
||||
function: is_float
|
||||
arguments:
|
||||
- 123.456
|
||||
- time: 32
|
||||
units: seconds
|
||||
module: erlang
|
||||
function: is_atom
|
||||
arguments:
|
||||
- 'this_is_atom'
|
||||
- time: 33
|
||||
units: seconds
|
||||
module: erlang
|
||||
function: is_atom
|
||||
arguments:
|
||||
- this_is_atom_too
|
||||
- time: 34
|
||||
units: seconds
|
||||
module: erlang
|
||||
function: is_binary
|
||||
arguments:
|
||||
- "Keep this as a binary"
|
||||
- time: 35
|
||||
units: seconds
|
||||
module: erlang
|
||||
function: is_list
|
||||
args_type: string
|
||||
arguments:
|
||||
- "Convert this as a string"
|
||||
```
|
||||
|
||||
It is even possible to pass an argument that is a list of elements, see:
|
||||
```yaml
|
||||
modules:
|
||||
mod_cron:
|
||||
tasks:
|
||||
- time: 36
|
||||
units: seconds
|
||||
module: io
|
||||
function: format
|
||||
args_type: string
|
||||
arguments:
|
||||
- "log message, integer: ~p, float: ~p, atom: ~p, binary: ~p~n~n"
|
||||
- - 12345678
|
||||
- 123.456
|
||||
- atom_this_is
|
||||
- "this is a binary"
|
||||
```
|
||||
|
||||
If you don't need to provide arguments at all,
|
||||
you can remove `arguments`, or provide it with an empty list:
|
||||
```yaml
|
||||
modules:
|
||||
mod_cron:
|
||||
tasks:
|
||||
- time: 10
|
||||
units: seconds
|
||||
command: connected_users
|
||||
- time: 15
|
||||
units: seconds
|
||||
ctl: delete_expired_pubsub_items
|
||||
- time: 20
|
||||
units: seconds
|
||||
module: mod_pubsub
|
||||
function: delete_expired_items
|
||||
arguments: []
|
||||
```
|
||||
|
||||
ejabberd Commands
|
||||
-----------------
|
||||
|
||||
This module provides two new commands that can be executed using ejabberdctl:
|
||||
* cron_list: list scheduled tasks
|
||||
* cron_del taskid: delete this task from the schedule
|
||||
|
||||
Web Admin
|
||||
---------
|
||||
|
||||
This module provides a page in the Host section of the Web Admin.
|
||||
Currently that page only allows to view the tasks scheduled for that host.
|
|
@ -1,80 +0,0 @@
|
|||
|
||||
mod_cron - Execute scheduled commands
|
||||
|
||||
http://www.ejabberd.im/mod_cron
|
||||
Author: Badlop
|
||||
|
||||
|
||||
This module allows advanced ejabberd administrators to schedule commands for
|
||||
periodic and automatic execution. This module is a similar concept than the
|
||||
*nix's cron program. Obviously, the admin must know in advance which module,
|
||||
function and arguments to use, so this module is not intended for starting
|
||||
administrators.
|
||||
|
||||
Each time a scheduled task finish its execution, a message is printed in the
|
||||
ejabberd log file.
|
||||
|
||||
|
||||
BASIC CONFIGURATION
|
||||
===================
|
||||
|
||||
Add the module to your ejabberd.yml, on the modules section:
|
||||
modules:
|
||||
mod_cron: {}
|
||||
|
||||
|
||||
TASK SYNTAX
|
||||
===========
|
||||
|
||||
Each task is described with five elements:
|
||||
* Time is an integer.
|
||||
* Units indicates the time unit you use. It can be: seconds, minutes, hours, days.
|
||||
* Module and * Function are the exact call you want to schedule.
|
||||
* Arguments is an array. Strings will be converted to binaries.
|
||||
* timer_type is one of 'fixed' or 'interval'. Fixed timers occur at a fixed time
|
||||
after the [minute|hour|day] e.g. every hour on the 5th minute (1:05PM, 2:05PM etc)
|
||||
interval timers occur every interval (starting on an even unit) e.g. every 10 minutes
|
||||
starting at 1PM, 1:10PM, 1:20PM etc.
|
||||
|
||||
Fixed timers are the equivalent of unix cron's comma syntax e.g. "2 * * *" and interval
|
||||
timers are the / syntax e.g. "*/5 * * *".
|
||||
|
||||
Default timer_type is interval.
|
||||
|
||||
EXAMPLE TASKS
|
||||
=============
|
||||
|
||||
Example configuration with some tasks:
|
||||
modules:
|
||||
mod_cron:
|
||||
tasks:
|
||||
- time: 3
|
||||
units: hours
|
||||
module: mnesia
|
||||
function: info
|
||||
arguments: {}
|
||||
timer_type: fixed
|
||||
- time: 10
|
||||
units: seconds
|
||||
module: ejabberd_auth
|
||||
function: try_register
|
||||
arguments:
|
||||
- "user1"
|
||||
- "localhost"
|
||||
- "somepass"
|
||||
timer_type: interval
|
||||
|
||||
|
||||
EJABBERD COMMANDS
|
||||
=================
|
||||
|
||||
This module provides two new commands that can be executed using ejabberdctl:
|
||||
* cron_list: list scheduled tasks
|
||||
* cron_del taskid: delete this task from the schedule
|
||||
|
||||
|
||||
WEB ADMIN
|
||||
=========
|
||||
|
||||
This module provides a page in the Host section of the Web Admin.
|
||||
Currently that page only allows to view the tasks scheduled for that host.
|
|
@ -1 +0,0 @@
|
|||
erl -pa ../ejabberd-dev/ebin -pa ebin -make
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/sh
|
||||
erl -pa ../ejabberd-dev/ebin -pz ebin -make
|
|
@ -0,0 +1,2 @@
|
|||
#modules:
|
||||
# mod_cron: {}
|
|
@ -0,0 +1,5 @@
|
|||
author: "Badlop <badlop at process-one.net>"
|
||||
category: "admin"
|
||||
summary: "Execute scheduled commands"
|
||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
|
||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
@ -11,21 +11,19 @@
|
|||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([
|
||||
cron_list/1, cron_del/1,
|
||||
-export([start/2, stop/1, depends/2, mod_options/1, mod_opt_type/1, mod_doc/0]).
|
||||
-export([cron_list/1, cron_del/1,
|
||||
run_task/3,
|
||||
web_menu_host/3, web_page_host/3,
|
||||
start/2,
|
||||
apply_interval/3,
|
||||
apply_interval1/3,
|
||||
stop/1]).
|
||||
apply_interval1/3]).
|
||||
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_http.hrl").
|
||||
-include("ejabberd_web_admin.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("xml.hrl").
|
||||
-include("translate.hrl").
|
||||
-include_lib("xmpp/include/xmpp.hrl").
|
||||
|
||||
-record(task, {taskid, timerref, host, task}).
|
||||
|
||||
|
@ -37,17 +35,30 @@ start(Host, Opts) ->
|
|||
ejabberd_commands:register_commands(commands()),
|
||||
ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
|
||||
ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, web_page_host, 50),
|
||||
Tasks = gen_mod:get_opt(tasks, Opts, fun(A) -> A end, []),
|
||||
Tasks = gen_mod:get_opt(tasks, Opts),
|
||||
catch ets:new(cron_tasks, [ordered_set, named_table, public, {keypos, 2}]),
|
||||
[add_task(Host, Task) || Task <- Tasks].
|
||||
[add_task(Host, Task) || Task <- Tasks],
|
||||
ok.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_commands:unregister_commands(commands()),
|
||||
ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
|
||||
ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, web_page_host, 50),
|
||||
%% Delete tasks of this host
|
||||
[delete_task(Task) || Task <- get_tasks(Host)].
|
||||
[delete_task(Task) || Task <- get_tasks(Host)],
|
||||
ok.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
mod_opt_type(tasks) ->
|
||||
econf:list(econf:any()).
|
||||
|
||||
mod_options(_Host) ->
|
||||
[{tasks, []}].
|
||||
|
||||
mod_doc() ->
|
||||
#{}.
|
||||
|
||||
%% ---------------------
|
||||
%% Task management
|
||||
|
@ -62,8 +73,7 @@ time_to_ms(IntervalUnit, IntervalNum) ->
|
|||
end.
|
||||
|
||||
time_until_event(IntervalMS) ->
|
||||
{MegaSecs, Secs, MicroSecs} = erlang:now(),
|
||||
NowMS = (MegaSecs*1000000 + Secs)*1000 + round(MicroSecs/1000),
|
||||
NowMS = p1_time_compat:system_time(micro_seconds),
|
||||
MSSinceLastEvent = (NowMS rem IntervalMS),
|
||||
(IntervalMS - MSSinceLastEvent).
|
||||
|
||||
|
@ -129,8 +139,10 @@ update_timer_ref(TaskId, NewTimerRef) ->
|
|||
|
||||
%% Method to add new task
|
||||
add_task(Host, Task) ->
|
||||
[TimeNum, TimeUnit, Mod, Fun, Args, InTimerType] =
|
||||
[proplists:get_value(Key, Task) || Key <- [time, units, module, function, arguments, timer_type]],
|
||||
[TimeNum, TimeUnit, Mod1, Fun1, ArgsType, Args1, InTimerType, Command, Ctl] =
|
||||
[proplists:get_value(Key, Task) || Key <- [time, units, module, function,
|
||||
args_type, arguments, timer_type,
|
||||
command, ctl]],
|
||||
TimerType = case InTimerType of
|
||||
<<"fixed">> ->
|
||||
fixed;
|
||||
|
@ -143,6 +155,10 @@ add_task(Host, Task) ->
|
|||
%% Get new task identifier
|
||||
TaskId = get_new_taskid(),
|
||||
|
||||
Args2 = parse_args_type(ArgsType, Args1),
|
||||
|
||||
{Mod, Fun, Args} = prepare_mfa(Mod1, Fun1, Args2, Command, Ctl),
|
||||
|
||||
TimerRef = case TimerType of
|
||||
interval ->
|
||||
begin_interval_timer(TaskId, TimeUnit, TimeNum, [Mod, Fun, Args]);
|
||||
|
@ -166,6 +182,34 @@ get_new_taskid() ->
|
|||
Id -> Id + 1
|
||||
end.
|
||||
|
||||
parse_args_type(_, undefined) ->
|
||||
[];
|
||||
parse_args_type(string, Args) ->
|
||||
lists:map(fun(Arg) when is_binary(Arg) -> binary_to_list(Arg);
|
||||
(Arg) -> Arg
|
||||
end,
|
||||
Args);
|
||||
parse_args_type(_, Args) ->
|
||||
Args.
|
||||
|
||||
parse_args_ctl(Ctl, Args2) ->
|
||||
[[atom_to_list(Ctl) | Args2]].
|
||||
|
||||
parse_args_command(Command, Args2) ->
|
||||
CI = #{caller_module => ?MODULE},
|
||||
[Command, Args2, CI].
|
||||
|
||||
prepare_mfa(undefined, undefined, Args2, Command, undefined)
|
||||
when Command /= undefined ->
|
||||
{ejabberd_commands, execute_command2,
|
||||
parse_args_command(Command, Args2)};
|
||||
prepare_mfa(undefined, undefined, Args2, undefined, Ctl)
|
||||
when Ctl /= undefined ->
|
||||
{ejabberd_ctl, process,
|
||||
parse_args_ctl(Ctl, parse_args_type(string, Args2))};
|
||||
prepare_mfa(Mod1, Fun1, Args2, undefined, undefined) ->
|
||||
{Mod1, Fun1, Args2}.
|
||||
|
||||
%% Method to run existing task
|
||||
run_task(Mod, Fun, Args) ->
|
||||
case catch apply(Mod, Fun, Args) of
|
||||
|
@ -231,7 +275,7 @@ cron_del(TaskId) ->
|
|||
%% ---------------------
|
||||
|
||||
web_menu_host(Acc, _Host, Lang) ->
|
||||
[{<<"cron">>, ?T(<<"Cron Tasks">>)} | Acc].
|
||||
[{<<"cron">>, translate:translate(Lang, ?T("Cron Tasks"))} | Acc].
|
||||
|
||||
web_page_host(_, Host,
|
||||
#request{path = [<<"cron">>],
|
||||
|
@ -245,9 +289,13 @@ web_page_host(Acc, _, _) -> Acc.
|
|||
make_tasks_table(Tasks, Lang) ->
|
||||
TList = lists:map(
|
||||
fun(T) ->
|
||||
{Time_num, Time_unit, Mod, Fun, Args} = T#task.task,
|
||||
[TimeNum, TimeUnit, Mod, Fun, Args, InTimerType] =
|
||||
[proplists:get_value(Key, T#task.task)
|
||||
|| Key <- [time, units, module, function, arguments, timer_type]],
|
||||
?XE(<<"tr">>,
|
||||
[?XC(<<"td">>, list_to_binary(integer_to_list(Time_num) ++" " ++ atom_to_list(Time_unit))),
|
||||
[?XC(<<"td">>, list_to_binary(integer_to_list(TimeNum)++" "
|
||||
++atom_to_list(TimeUnit)++" "
|
||||
++atom_to_list(InTimerType))),
|
||||
?XC(<<"td">>, list_to_binary(atom_to_list(Mod))),
|
||||
?XC(<<"td">>, list_to_binary(atom_to_list(Fun))),
|
||||
?XC(<<"td">>, list_to_binary(io_lib:format("~p", [Args])))])
|
||||
|
|
|
@ -0,0 +1,342 @@
|
|||
As a special exception, the authors give permission to link this program
|
||||
with the OpenSSL library and distribute the resulting binary.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
|
@ -0,0 +1,41 @@
|
|||
mod_default_contacts - Add roster contact(s) on registration
|
||||
============================================================
|
||||
|
||||
* Author: Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This module allows for specifying one or more contacts that should be
|
||||
added to the user's roster automatically on successful registration (via
|
||||
`mod_register`, or, for example, `ejabberdctl register`). Note that no
|
||||
presence subscription is performed, and the rosters of the contacts aren't
|
||||
modified.
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
In order to use this module, add a configuration snippet such as the
|
||||
following:
|
||||
|
||||
```yaml
|
||||
modules:
|
||||
mod_default_contacts:
|
||||
contacts:
|
||||
-
|
||||
name: "Bob Virding"
|
||||
jid: bob@example.com
|
||||
-
|
||||
name: "Alice Armstrong"
|
||||
jid: alice@example.com
|
||||
```
|
||||
|
||||
The configurable `mod_default_contacts` options are:
|
||||
|
||||
- `contacts` (default: `[]`)
|
||||
|
||||
The list of contact JIDs that should be auto-added to the user's roster
|
||||
on account registration. Each list item must specify the `jid:` and
|
||||
optionally the `name:` of the contact.
|
|
@ -0,0 +1,3 @@
|
|||
#modules:
|
||||
# mod_default_contacts:
|
||||
# contacts: []
|
|
@ -0,0 +1,5 @@
|
|||
author: "Holger Weiss <holger at zedat.fu-berlin.de>"
|
||||
category: "roster"
|
||||
summary: "Auto-add roster contacts on registration"
|
||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
|
||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
@ -0,0 +1,87 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_default_contacts.erl
|
||||
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%% Purpose : Auto-add contacts on registration
|
||||
%%% Created : 14 May 2019 by Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2019-2020 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_default_contacts).
|
||||
-author('holger@zedat.fu-berlin.de').
|
||||
-behavior(gen_mod).
|
||||
|
||||
%% gen_mod callbacks.
|
||||
-export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2, mod_options/1,
|
||||
mod_doc/0]).
|
||||
|
||||
%% ejabberd_hooks callbacks.
|
||||
-export([register_user/2]).
|
||||
|
||||
-include("logger.hrl").
|
||||
-include_lib("xmpp/include/xmpp.hrl").
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_mod callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec start(binary(), gen_mod:opts()) -> ok.
|
||||
start(Host, _Opts) ->
|
||||
ejabberd_hooks:add(register_user, Host, ?MODULE, register_user, 50).
|
||||
|
||||
-spec stop(binary()) -> ok.
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(register_user, Host, ?MODULE, register_user, 50).
|
||||
|
||||
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
ok.
|
||||
|
||||
-spec mod_opt_type(atom()) -> econf:validator().
|
||||
mod_opt_type(contacts) ->
|
||||
econf:list(
|
||||
econf:and_then(
|
||||
econf:options(
|
||||
#{jid => econf:jid(),
|
||||
name => econf:binary()},
|
||||
[{required, [jid]}]),
|
||||
fun(Opts) ->
|
||||
Jid = proplists:get_value(jid, Opts),
|
||||
Name = proplists:get_value(name, Opts, <<>>),
|
||||
#roster_item{jid = Jid, name = Name}
|
||||
end)).
|
||||
|
||||
-spec mod_options(binary()) -> [{atom(), any()}].
|
||||
mod_options(_Host) ->
|
||||
[{contacts, []}].
|
||||
|
||||
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_roster, hard}].
|
||||
|
||||
mod_doc() ->
|
||||
#{}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% ejabberd_hooks callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec register_user(binary(), binary()) -> any().
|
||||
register_user(LUser, LServer) ->
|
||||
?DEBUG("Auto-creating roster entries for ~s@~s", [LUser, LServer]),
|
||||
Items = gen_mod:get_module_opt(LServer, ?MODULE, contacts),
|
||||
mod_roster:set_items(LUser, LServer, #roster_query{items = Items}).
|
|
@ -0,0 +1,342 @@
|
|||
As a special exception, the authors give permission to link this program
|
||||
with the OpenSSL library and distribute the resulting binary.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
|
@ -0,0 +1,39 @@
|
|||
mod_default_rooms - Add MUC bookmark(s) on registration
|
||||
=======================================================
|
||||
|
||||
* Author: Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This module allows for specifying one or more rooms that should be
|
||||
bookmarked automatically on successful user registration (via
|
||||
`mod_register`, or, for example, `ejabberdctl register`).
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
In order to use this module, add a configuration snippet such as the
|
||||
following:
|
||||
|
||||
```yaml
|
||||
modules:
|
||||
mod_default_rooms:
|
||||
rooms:
|
||||
- foo@conference.example.net
|
||||
- bar@conference.example.org
|
||||
```
|
||||
|
||||
The configurable `mod_default_rooms` options are:
|
||||
|
||||
- `rooms` (default: `[]`)
|
||||
|
||||
The list of rooms users that should be auto-bookmarked on account
|
||||
registration.
|
||||
|
||||
- `auto_join` (default: `true`)
|
||||
|
||||
This option specifies whether the auto-join flag should be set for the
|
||||
bookmarks created on registration.
|
|
@ -0,0 +1,3 @@
|
|||
#modules:
|
||||
# mod_default_rooms:
|
||||
# rooms: []
|
|
@ -0,0 +1,5 @@
|
|||
author: "Holger Weiss <holger at zedat.fu-berlin.de>"
|
||||
category: "muc"
|
||||
summary: "Auto-bookmark rooms on registration"
|
||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
|
||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue