Compare commits
1426 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 18f6b02065 | |||
| 33544569fb | |||
| 529ab22a79 | |||
| 9f6afab09c | |||
| 2137c03da1 | |||
| 58fe9d3345 | |||
| deb5e45bff | |||
| bd1df027c6 | |||
| 20290cab01 | |||
| adcf2d5c4e | |||
| 8faafc5b0d | |||
| 370093afde | |||
| 32d3d1626b | |||
| d4d28e038b | |||
| d6a076dae8 | |||
| 2cacf21d51 | |||
| 0874b93e7c | |||
| ba9094a089 | |||
| bfebcebeb7 | |||
| 1c0b99e162 | |||
| f850bcbbc9 | |||
| 7bc879c6f4 | |||
| f7d532f2f7 | |||
| dfbf32ecba | |||
| 17abbf3d82 | |||
| dfbfd90f8c | |||
| 84c1cf8033 | |||
| 5a0cfe7e2b | |||
| 714dce84db | |||
| b807b21a59 | |||
| 4f4c8eb61e | |||
| 3952888f94 | |||
| 9b145385af | |||
| 897b46c31d | |||
| 72299ab078 | |||
| e7ab83b612 | |||
| 07cf6f09b8 | |||
| 252ee6228b | |||
| cf6264f507 | |||
| 4f1ececbd1 | |||
| 70bf5b4eda | |||
| f59a979f7c | |||
| 05e3893f60 | |||
| 7d7621c67c | |||
| 449e56ed52 | |||
| aded966370 | |||
| dac1f328d7 | |||
| 90ea752046 | |||
| e4b3bd5005 | |||
| abcbcd1f2c | |||
| 18fd67b311 | |||
| b116957982 | |||
| a1f20a5bc0 | |||
| e0d14c3b8d | |||
| 1548a18b5e | |||
| 339fa6e41b | |||
| db0962804d | |||
| 677d8b1a29 | |||
| 850218c2df | |||
| 1c89914382 | |||
| fe40a9c5f6 | |||
| bc0ae5a017 | |||
| 91ab7e029b | |||
| c3a88c713b | |||
| fd52f2cb7d | |||
| f91eb52890 | |||
| ca59a7f027 | |||
| 6dea2d2307 | |||
| 650b2802b5 | |||
| 6ddc66db9f | |||
| 792512459d | |||
| 0359e345b0 | |||
| a09c319357 | |||
| 36166aa40e | |||
| a25c9c5df3 | |||
| 6e3a9ac4fd | |||
| f4c74c147b | |||
| 0edda6150b | |||
| bd43505db9 | |||
| a2e1d2030a | |||
| 4c8b034874 | |||
| b47c50145d | |||
| b9f4daca46 | |||
| 606c207e21 | |||
| d9c01e8ed9 | |||
| 3e9c9fc750 | |||
| fbcb9bb154 | |||
| 6242fd2bb8 | |||
| 1c566057f8 | |||
| aa59d8f1f0 | |||
| 6dd35923b0 | |||
| b9bbe19d4c | |||
| 44b2002504 | |||
| faf422202f | |||
| eb884c80d0 | |||
| b83dd9f954 | |||
| fd828c3e9b | |||
| 79648ce853 | |||
| ddc94a8c96 | |||
| aa413d63da | |||
| 548be4cf3d | |||
| 0e5b0b43fa | |||
| 8258f5940b | |||
| c890b17834 | |||
| f0a848ef45 | |||
| 20d66e6736 | |||
| 731c9b86e0 | |||
| 955343f6aa | |||
| 1b73ac5118 | |||
| 9ee8191939 | |||
| f2231a2282 | |||
| 3e24408710 | |||
| 3e67200d64 | |||
| 6374b5b1e7 | |||
| 26ac75bdc9 | |||
| 1d1496a667 | |||
| e34eebb5ad | |||
| 776930fa06 | |||
| 38af7c85a6 | |||
| 99b7a285d7 | |||
| 19e20f75d7 | |||
| b8d6aee1ce | |||
| 48b768b5b0 | |||
| bd604945c9 | |||
| 0a59ebe405 | |||
| 32eaa01929 | |||
| baf9363bdf | |||
| 12aaa0125b | |||
| cb7cd9abb8 | |||
| ca701e1675 | |||
| 9b88fd6646 | |||
| 76ca7ae7f0 | |||
| 1ab223568d | |||
| 043effc3ce | |||
| 4fd1a8ba63 | |||
| 36303fb4be | |||
| 62357b9589 | |||
| 808029b868 | |||
| cfd377b98e | |||
| 6eeb355a22 | |||
| a0e2e943b4 | |||
| 78931d8efa | |||
| 0d2a8cd04f | |||
| 97087eb3b9 | |||
| 6eeef62ce4 | |||
| 0ccff15599 | |||
| 325fb8caef | |||
| 18ef908759 | |||
| 1f1d2bd5f5 | |||
| 0c484369c9 | |||
| 100f2e9a13 | |||
| 510fd8cf73 | |||
| 080922a3de | |||
| cb7d8c8ead | |||
| c47252aea1 | |||
| f96074057c | |||
| dfaeb3bc88 | |||
| c31f59e326 | |||
| 7c2b9eaf97 | |||
| 79f5251d69 | |||
| 917e8640c2 | |||
| c4bc0e7252 | |||
| 1f16e4783c | |||
| 7f3a5066c6 | |||
| 6f3713a67d | |||
| abfb4a2841 | |||
| f672fd0824 | |||
| 60b36beda8 | |||
| c4289095e0 | |||
| 31757116fc | |||
| 2f68733708 | |||
| dccaff0544 | |||
| 184ec38510 | |||
| 2d59efb515 | |||
| 195d22d906 | |||
| ab80513755 | |||
| 8a116411bb | |||
| bd3889b6ec | |||
| 400fb69f15 | |||
| 9da45d40c7 | |||
| 23db206ea1 | |||
| 964b7b6b67 | |||
| 9c5f34794a | |||
| eca7588abf | |||
| a15d583d4d | |||
| e03c453c78 | |||
| 641dc7d695 | |||
| 0a1b0498a6 | |||
| a6858a6ce4 | |||
| f4beeb1706 | |||
| 75298b4c27 | |||
| 011464e6ac | |||
| c53e8012b2 | |||
| fe04d57284 | |||
| 0f3bd782c4 | |||
| 91cf9194d8 | |||
| e2dbad6242 | |||
| 84d4a1619b | |||
| 16fae4d117 | |||
| 76f7548935 | |||
| 2d08dcf11a | |||
| 7af47b9dbe | |||
| 437da38342 | |||
| 801ee586b7 | |||
| 6ff3fda14b | |||
| cfca2b502a | |||
| e1a4ae8264 | |||
| 19826858a4 | |||
| 0c2677bc50 | |||
| d1c1902687 | |||
| a5230c46c2 | |||
| 95ce77f80d | |||
| a93f4f7750 | |||
| ac87749d55 | |||
| 50b747041f | |||
| 36df8c9035 | |||
| 58a5ed9cdc | |||
| 5ea909885e | |||
| 1f2c9b7971 | |||
| c13940e1de | |||
| 8e5297f4c3 | |||
| 3278f019cb | |||
| e2c3925b7d | |||
| 8a0ccfc401 | |||
| c29b2fda99 | |||
| c75b7b2b12 | |||
| c754c91ad1 | |||
| 6952324509 | |||
| 4b5ef8f2ce | |||
| d87fff1a4c | |||
| 3024bb0cbf | |||
| f8dd973373 | |||
| 972440c2ca | |||
| b14899d41a | |||
| d2d8a09b4a | |||
| 1ea09b09a2 | |||
| 071c0a1afe | |||
| 4ed00c3d1f | |||
| c9ff370278 | |||
| 8ab8da82c4 | |||
| 4b6a42f539 | |||
| 8a251ccafe | |||
| 550363cd52 | |||
| 367353100b | |||
| 5113d28bb4 | |||
| c8df607173 | |||
| c8033833f9 | |||
| cf4f0dbe6d | |||
| 8c4884d665 | |||
| 26f6eebaa9 | |||
| ac84267b22 | |||
| 5030f35558 | |||
| d7930d7f82 | |||
| 42ddc297fe | |||
| e82db8cc7f | |||
| 10d43c7cc6 | |||
| d539fd28c1 | |||
| 12e00c57f9 | |||
| 0c95cf7e61 | |||
| d106f741d9 | |||
| 6c96157d1b | |||
| a42a012f94 | |||
| 7e90d6cf92 | |||
| e19ac27803 | |||
| c100cbedd1 | |||
| 4c2e7e38a1 | |||
| fa22b23435 | |||
| c3c23a04f6 | |||
| ae6545989e | |||
| baeb3d076e | |||
| 0ac5684bf0 | |||
| 715cc5ea3b | |||
| d9e2931ed9 | |||
| 1531541331 | |||
| b7e02cc42e | |||
| 3ab90c9d3a | |||
| 1b7948f50a | |||
| fe23dbd76e | |||
| 21d2f4efab | |||
| f66f049ef3 | |||
| 49dd83e731 | |||
| 9cd2d5e0cf | |||
| fab705dc13 | |||
| a84f8bc9ef | |||
| 2477b735bf | |||
| 8db328af60 | |||
| bc2280be70 | |||
| a28aaa1fdd | |||
| afa0f4d403 | |||
| 0e5b930b22 | |||
| 7d8f8a7e99 | |||
| a7924adee9 | |||
| d4ec7a2f01 | |||
| e0fab19345 | |||
| 4646a5dbb8 | |||
| 1567592ac7 | |||
| 3b735c269c | |||
| 5bdef05f12 | |||
| 077626c40a | |||
| c544384c83 | |||
| d56522c8a0 | |||
| b83e77ca21 | |||
| 361400691d | |||
| ba9247c530 | |||
| 91d22109c8 | |||
| cb702a770d | |||
| db059bdfaf | |||
| 2291a6afea | |||
| af7fe0c21e | |||
| 7ce3291603 | |||
| 07e870271e | |||
| a5f8aeb6da | |||
| 1777ecd15a | |||
| d4d45f3a50 | |||
| 9900be3d68 | |||
| b88372c313 | |||
| e4aab7f749 | |||
| 42cbd0c1c4 | |||
| 9318678e08 | |||
| bd992bcb87 | |||
| 01d2bcfd92 | |||
| 0bfda4e628 | |||
| a2f659a2c6 | |||
| 9a04a60c7d | |||
| 7238ab6f05 | |||
| dd954ef1c2 | |||
| f1b8853339 | |||
| 479a2adaae | |||
| dd3c939025 | |||
| 767e7d65ef | |||
| 5feb20afd0 | |||
| 9038bbd3e7 | |||
| 506fbbe7f7 | |||
| 08f1f55e0c | |||
| 5ea9a0ddb7 | |||
| d4e0fab06b | |||
| 217aaf78f9 | |||
| 2ee788e0bf | |||
| f0f4b2719c | |||
| 406a31c8be | |||
| c32332898b | |||
| 4bbf6684b4 | |||
| 9476d8a2c3 | |||
| 6ac46c6171 | |||
| 6ae8b9c4d6 | |||
| 39119192a1 | |||
| 25e4038623 | |||
| f58d03c12e | |||
| 58590cf08d | |||
| cce57310b0 | |||
| 516d369174 | |||
| 7fc1207661 | |||
| 951808658d | |||
| cd9e1621f9 | |||
| 650966ce2c | |||
| 2a99af8cce | |||
| 3ba0061ada | |||
| 705c5b4c1c | |||
| 6ae1f62469 | |||
| 1c00a9713d | |||
| 5e7d8868c0 | |||
| 55dbdf5dba | |||
| 2ff291899d | |||
| 2d375e0429 | |||
| 4669ae0a81 | |||
| c2362db03d | |||
| 1405e9d375 | |||
| 84c4d75735 | |||
| 41aa693896 | |||
| 44b282474a | |||
| 70cfcb1008 | |||
| fa28c8baf6 | |||
| 584fa98564 | |||
| 214c62bf9c | |||
| 962b66b099 | |||
| 417cf11bdd | |||
| b3da1c7d86 | |||
| 839b406903 | |||
| 95e085beb7 | |||
| df63fcd353 | |||
| 77e549e504 | |||
| 20c2bc54cd | |||
| 63ccbe90d3 | |||
| 1f02b5db0f | |||
| 6bde01cd45 | |||
| f36ac2b809 | |||
| e4f19df428 | |||
| 606860e1cc | |||
| 22d490486a | |||
| d2d06403ac | |||
| 6ee8ec49a5 | |||
| 6e878d17e6 | |||
| 4984320f17 | |||
| 09250c0b65 | |||
| 6e8f5a9cd3 | |||
| c3edcd0575 | |||
| 6fb99fbb48 | |||
| 31c3a78453 | |||
| a3f2588df6 | |||
| 41c455fabb | |||
| 8045fec882 | |||
| 414004eae4 | |||
| 8e8a1a3bf5 | |||
| fbd8547d94 | |||
| 6df9a6ab33 | |||
| 2907446e87 | |||
| f60804b060 | |||
| e569087d83 | |||
| 4a7a33168a | |||
| d7fbad6fa1 | |||
| e6c23e0413 | |||
| 35e00ebcf6 | |||
| 1a96944929 | |||
| bc69b96e4c | |||
| 35c9c95e4b | |||
| 47ee65d101 | |||
| 8a856df2f0 | |||
| 6795675108 | |||
| 6867ca43e4 | |||
| ab0905ce56 | |||
| a71b47ea27 | |||
| 2cca00acc0 | |||
| 7c36e304f0 | |||
| 10c16b1cdf | |||
| 8bd37462cb | |||
| 78f1fe9ba5 | |||
| b17c7848fd | |||
| 071aa123e8 | |||
| abdb7f2742 | |||
| bc70e90853 | |||
| 787e078f6a | |||
| 17607ecaa4 | |||
| 9d84caebdb | |||
| 35bcadde7a | |||
| d9e345a4d8 | |||
| 46c667c567 | |||
| db3f8487fc | |||
| 34234b4a46 | |||
| 03454c7f1d | |||
| 1cced15c0d | |||
| 4c8beae585 | |||
| 0a3cfeeed9 | |||
| 933d4a69b8 | |||
| 352afa7902 | |||
| ce18cfb41b | |||
| 4fe8d76b83 | |||
| d804698541 | |||
| 1b1a0d7ed3 | |||
| a0de5bc317 | |||
| a77b6946cf | |||
| cbbb7256ae | |||
| 16bd626fc9 | |||
| 8068fdf4e3 | |||
| b6aa6d6f09 | |||
| 86794b1d23 | |||
| 3e4858f348 | |||
| 7aff5defac | |||
| 6ad93eb609 | |||
| 9f5246cacc | |||
| 855d427182 | |||
| 4d85297c85 | |||
| 7b76fdcde7 | |||
| 6179daaf99 | |||
| 0cc58d7a71 | |||
| e5373de2b9 | |||
| 6ddff15b39 | |||
| 392ea78acf | |||
| 024b688c1d | |||
| 276f890c50 | |||
| ee77257d41 | |||
| e7a8bf6154 | |||
| 4f643ce9b8 | |||
| 8447dfa4b1 | |||
| 7b05ba0e1e | |||
| c8fd680d92 | |||
| 24aefc429c | |||
| 0bf9832187 | |||
| 761ed1b192 | |||
| 05ee83bddc | |||
| f30783a2d2 | |||
| ee41e1eb70 | |||
| 8d218dd2b9 | |||
| 7cbda8badb | |||
| 15a27c0566 | |||
| d9a1eecdca | |||
| 5d749de74d | |||
| dbad3df333 | |||
| 50137aa98d | |||
| f0863e1dfe | |||
| 1713bf635d | |||
| 9bee86178f | |||
| 455d85a278 | |||
| d812975565 | |||
| 9aa9c34a57 | |||
| 34d5d08501 | |||
| 71142e336d | |||
| c7f4f39c03 | |||
| b2d6a25240 | |||
| 4a57f5db74 | |||
| 4cdf7fba4f | |||
| 341e47c2a4 | |||
| 675c24f5eb | |||
| 15abddb7a8 | |||
| 3df45683ff | |||
| ef49822a47 | |||
| 402a8c0d35 | |||
| d24449488c | |||
| b9e57a341d | |||
| 79be929d96 | |||
| 65aed47873 | |||
| 8ac16a0ec1 | |||
| 3250c24ff1 | |||
| 88b6849e69 | |||
| e3b4c99b00 | |||
| 1f5bf8fbc0 | |||
| f044d6ed28 | |||
| 3aa242e2c7 | |||
| 8983cba129 | |||
| bd97c40f92 | |||
| 5a006b977d | |||
| 3658da3ae8 | |||
| 2265f52c34 | |||
| 7b244652f1 | |||
| 325e67f4cb | |||
| 6ce660b9c2 | |||
| 575e07a986 | |||
| 20d1011194 | |||
| 3df5277728 | |||
| ea4f379bd5 | |||
| 51594b6011 | |||
| 58425ba45d | |||
| 7fd3d67ff3 | |||
| 1716b090f9 | |||
| e5e777694d | |||
| 6cf3ae8050 | |||
| 26c572b7e7 | |||
| 311bf4dbb1 | |||
| 1511a5dc82 | |||
| 041d947e83 | |||
| 2007b3d496 | |||
| 09e847b97d | |||
| b9f911554c | |||
| 24323e8b3b | |||
| 1a5eb4f73d | |||
| 3930715712 | |||
| 64bc97d471 | |||
| 4aec8dda48 | |||
| 9192c75e1a | |||
| 512d701ef8 | |||
| ef3dd090c7 | |||
| 3d5fe6a337 | |||
| 0906a68c27 | |||
| 798853abb6 | |||
| 5e3fdbeeed | |||
| 04545f2668 | |||
| 8ce1e790ac | |||
| a232d16ff4 | |||
| f2a37597cf | |||
| 257e4c34a8 | |||
| 66cd86e726 | |||
| 84a1cc943d | |||
| 47d86f5ad1 | |||
| da50ec6215 | |||
| 69c4d5d89b | |||
| 006515020a | |||
| f86318a179 | |||
| b667148982 | |||
| cbf6abc263 | |||
| 355648dc7e | |||
| 3128c26766 | |||
| c311ea1505 | |||
| 2c48515e7c | |||
| 25523371fc | |||
| 3e9e967429 | |||
| c79489fb65 | |||
| 4039f4a14f | |||
| 14232df14c | |||
| 72535ee14d | |||
| 0a77892391 | |||
| c10984f61b | |||
| ce1364b414 | |||
| e780126c9e | |||
| 4667cefb62 | |||
| 6fa0f68239 | |||
| a126a1883e | |||
| 4606f33081 | |||
| 726545ae86 | |||
| 1c585e74bc | |||
| dc474cf9d5 | |||
| e2ba1f4f1d | |||
| 330d6f7b6e | |||
| 68fd22af87 | |||
| f25b2266bd | |||
| 8107b2b7dc | |||
| 3c3cd099de | |||
| 4936c2dc58 | |||
| fc1dd42e5a | |||
| 325660a547 | |||
| d6f63ceec5 | |||
| 8d47a20d63 | |||
| eab6c09e0c | |||
| 9aee12d83b | |||
| 970973cb9a | |||
| 719aff1088 | |||
| ff1622c658 | |||
| 717837172c | |||
| ae70faef94 | |||
| c86a73f894 | |||
| 4b8d7dd19b | |||
| 49894fe065 | |||
| 58d690376c | |||
| c841505a07 | |||
| 31c6d6194f | |||
| d678a21f21 | |||
| 860957d20c | |||
| 9d8de17b4d | |||
| 0d108d5b1e | |||
| 07f457f49f | |||
| 55fef3c3e7 | |||
| a033b06150 | |||
| 4d3708b637 | |||
| fd57ec5872 | |||
| a5c5808911 | |||
| f6c6a7ee31 | |||
| 19dead97a3 | |||
| 2568c616f7 | |||
| dab4adaaee | |||
| fdc6149c08 | |||
| e29c992cbf | |||
| 156c496a27 | |||
| 5e99d3416d | |||
| b6f50972b6 | |||
| ff1fc2b4f0 | |||
| 57aefa240f | |||
| 2fdabe8b5b | |||
| 217d3131c8 | |||
| 51e2896914 | |||
| 054ad379ce | |||
| 2425c51ae4 | |||
| c137715902 | |||
| 670d7c7ebd | |||
| 2c735c91d6 | |||
| eca4d9c963 | |||
| 64976e18b3 | |||
| f303a40973 | |||
| 3909f95ac1 | |||
| ca403c25e7 | |||
| 1b81a2e3d6 | |||
| 342ea2753c | |||
| 73282e0760 | |||
| 11be369987 | |||
| ad7a8fffd5 | |||
| e6b05199a8 | |||
| dabea295aa | |||
| c8e7a4f4ac | |||
| 2a505d1d86 | |||
| 4be5c0c90c | |||
| f9119a3ec6 | |||
| 49fe1399af | |||
| 56e52f0571 | |||
| 7c76c65c8c | |||
| 1efdf914f3 | |||
| ea06dc92bc | |||
| df30831421 | |||
| 6952aae506 | |||
| 0a159a0850 | |||
| 0a89bbaaa1 | |||
| 0ec33b0868 | |||
| fd16591a2a | |||
| b70b742a9e | |||
| f2bd9e01b0 | |||
| 45b12ec886 | |||
| 0192913405 | |||
| cf6d39487a | |||
| 3571ef9767 | |||
| 3e45bdcc2d | |||
| 5ddfccd45e | |||
| 14e95f91d1 | |||
| 2999a11f4b | |||
| 7fb6a4696a | |||
| 2e68058508 | |||
| 3f8a4a5756 | |||
| f6258b2f46 | |||
| cbc78674e5 | |||
| 1e148d4260 | |||
| 46ae4e7340 | |||
| 695ff58c55 | |||
| 268fe029fd | |||
| 3dc88feec9 | |||
| 5098964df0 | |||
| d3f9819c0f | |||
| 06cee5a654 | |||
| 5438504fbf | |||
| 56f525c475 | |||
| 44ac69f8de | |||
| 60932b67b1 | |||
| 208ec6c076 | |||
| 3eaf796cca | |||
| 0c2222745f | |||
| 01a9f981f4 | |||
| d967054a9f | |||
| 73fabfe3a6 | |||
| 5490d7efe8 | |||
| 0e8dd70ed5 | |||
| dd2be928af | |||
| 29cb0ae7ba | |||
| 14b22a4f98 | |||
| c1d2a9305a | |||
| 6b6cb345ba | |||
| 54ff94e3e3 | |||
| f9a8b9ff31 | |||
| 0b03106946 | |||
| 5598d34478 | |||
| 0ad45b1a93 | |||
| d6f3182731 | |||
| 53626d16e3 | |||
| 59c88fcfe7 | |||
| 0cc9d8f8a0 | |||
| 95239baf85 | |||
| 3625681cf4 | |||
| 16be92cc9f | |||
| 720fd7605d | |||
| 9fbef39193 | |||
| fc0209fec9 | |||
| 6bd9bac716 | |||
| 26ace9829a | |||
| 31d6fe3c7d | |||
| b08c061683 | |||
| cd68e0021e | |||
| f4330e9ed3 | |||
| ea96fe3fd1 | |||
| 548be039b3 | |||
| 458b28eeff | |||
| a528c62bba | |||
| bf10d1e956 | |||
| 1262b0e353 | |||
| 9d9fca5e19 | |||
| ff1f4f5a35 | |||
| 164e1520d4 | |||
| 140d938bc3 | |||
| b3eb800be1 | |||
| 94a638da85 | |||
| fef762243c | |||
| 46735f7c62 | |||
| 7dfdd5d654 | |||
| 5346a7df02 | |||
| 46304da5d7 | |||
| 367365f816 | |||
| 360b4916b1 | |||
| 480c98e991 | |||
| 9df5639974 | |||
| 2729285977 | |||
| 08a72c1693 | |||
| c54fb0c9f2 | |||
| d49fe6e60d | |||
| 5413c72ed5 | |||
| b31ca0cd36 | |||
| 0287d50ff4 | |||
| 5475f35f9f | |||
| a7eb5a77eb | |||
| fc55db403e | |||
| 67052929d5 | |||
| 4f0cd053c4 | |||
| 29ea2738cc | |||
| 49688feaf2 | |||
| f145a32b79 | |||
| 35dc60c534 | |||
| 523239d5a7 | |||
| edb5211f5f | |||
| 2159829168 | |||
| 29a11c89b1 | |||
| 46ff889bd3 | |||
| 6140047edf | |||
| 34a490361f | |||
| 534d6a49f9 | |||
| 2594c7d687 | |||
| 38b342e36f | |||
| 19fbe884d7 | |||
| 95aa4653cb | |||
| d950fb96b6 | |||
| ada531bd8d | |||
| caf9c4f65e | |||
| 606e1982a9 | |||
| 660bbaee3d | |||
| 65bc1122b2 | |||
| 7f8c01b150 | |||
| 20060d9cf6 | |||
| 67570668b6 | |||
| 5fc16bc830 | |||
| feded4b0b7 | |||
| 15a6e660b9 | |||
| 1ce4eb06a7 | |||
| 3e9b5d4ed4 | |||
| b8478c50b9 | |||
| e9ee9959a1 | |||
| 2d9f715d65 | |||
| 44c2259a43 | |||
| 17de26d800 | |||
| 154a0d1c96 | |||
| 1626ede210 | |||
| e14c3f4699 | |||
| 9e309d2a38 | |||
| a00b7fd89b | |||
| d036802891 | |||
| 0047e4192b | |||
| 261b7dc0d9 | |||
| b42dd00a53 | |||
| 4a83538cf6 | |||
| a314cdf26f | |||
| 51a55723cf | |||
| 4dac8f437f | |||
| 7bfd87bec6 | |||
| bb26498faf | |||
| 222cabb745 | |||
| c55ee29a24 | |||
| 4cce314c11 | |||
| 8a89b1737f | |||
| e7417c2f8b | |||
| 4ebeb4d6d0 | |||
| eea6a1a544 | |||
| fbf4a7aa35 | |||
| 9b8d897b5f | |||
| f248cfd18d | |||
| 100befc771 | |||
| 4ed01b98de | |||
| 68ae80ed2f | |||
| 0da3b27dfe | |||
| 3dbfd827bb | |||
| 70fd795a1b | |||
| 2e402c27f4 | |||
| 673a7aad55 | |||
| 88d32b1064 | |||
| 5b09c35dea | |||
| 18f183e4e3 | |||
| 15b0841282 | |||
| da6c4f9941 | |||
| 7ed7ca6e1e | |||
| bbe82647e8 | |||
| c34999cc96 | |||
| e43271216f | |||
| c4e80abeac | |||
| 1bcc001615 | |||
| 83e96f155a | |||
| 3681835668 | |||
| d2684bc3e8 | |||
| c4ca19d313 | |||
| a9ab002f48 | |||
| b416ed03c9 | |||
| 2190055a06 | |||
| 7e426b9623 | |||
| bd106bd45f | |||
| 99fe76f8bc | |||
| eedef994a9 | |||
| 732ad47559 | |||
| 83ffe1989a | |||
| e0370d89b4 | |||
| de15168cdb | |||
| ddd3e58f35 | |||
| 50b73664e2 | |||
| 6f080f7fed | |||
| ce7e9129d9 | |||
| f767def249 | |||
| 5583a3f7dc | |||
| b7fd730409 | |||
| 1266bf48e6 | |||
| 459074fd97 | |||
| 3b5a74b34e | |||
| 049b7c5d11 | |||
| 97e2b39b69 | |||
| 48392832ef | |||
| 72b842d8d1 | |||
| 05187325ee | |||
| fa5b06f542 | |||
| bf57f712da | |||
| c27d6fddf4 | |||
| 6bd70aaf09 | |||
| cdd3ab17ae | |||
| 039d5b8367 | |||
| af32a18b4b | |||
| 4094482a9f | |||
| 7f5cce8196 | |||
| ffe2d532bf | |||
| 29607291ae | |||
| 5c00365550 | |||
| 31aa201ee8 | |||
| 3ec3e78baa | |||
| 11c4dba442 | |||
| 270662c07b | |||
| b8f094b080 | |||
| dffe808895 | |||
| b5964c2a5f | |||
| 5f67072e06 | |||
| 08ba5346db | |||
| 5a68b44be1 | |||
| dfc97beaef | |||
| 4c1eefb791 | |||
| 8af1c41a3e | |||
| e3eac4f00d | |||
| 257efddd1b | |||
| 20088d8a92 | |||
| 077b9cd4db | |||
| 7e370d0201 | |||
| 90263cf0e7 | |||
| 18af5f8675 | |||
| 299d6fe72f | |||
| 3d766c1cb5 | |||
| fec3742aaa | |||
| 90abb578e6 | |||
| dff6e28b2d | |||
| 9b370787c2 | |||
| a4b715516e | |||
| 0ed0c45aba | |||
| 5ae96e4065 | |||
| f8780bc6ee | |||
| b2659e4a6f | |||
| e45e486fd2 | |||
| 7c4e7dea4b | |||
| 709a536fb7 | |||
| cabf128bbb | |||
| e1fd7c444e | |||
| fc9ba11afe | |||
| bda111b145 | |||
| eab7a509f9 | |||
| 6acf7fb5ec | |||
| b59ecb83e8 | |||
| aedb847a81 | |||
| ca8eeaac57 | |||
| d7223ea6ef | |||
| 8654991333 | |||
| a26a7463a0 | |||
| 34df1f502c | |||
| 38f5b8f093 | |||
| 0b8a6d92f8 | |||
| 621fab55e2 | |||
| d22c483781 | |||
| 5db572171b | |||
| f954d6829b | |||
| 1dad64a1cc | |||
| 0770e0eee4 | |||
| e740cfcc47 | |||
| 412f65a0cb | |||
| cd6587d9df | |||
| 549fa6e836 | |||
| 409ab48068 | |||
| 7d0de522dd | |||
| b326b7eb74 | |||
| f4c88768da | |||
| 4dd21545a0 | |||
| 7c415f3fdf | |||
| 5a6f837800 | |||
| f80c848692 | |||
| ad0565cf7c | |||
| 46efb20947 | |||
| a93a3370ef | |||
| bc39341633 | |||
| 1ac385f499 | |||
| b789ee630d | |||
| 9aadefa5b0 | |||
| 3834bcc07e | |||
| 7e924341e4 | |||
| f214b8c528 | |||
| a72023c03e | |||
| d4b5e94635 | |||
| 064d1af5de | |||
| 51faa601fa | |||
| d28efabb79 | |||
| ad7bca0e78 | |||
| 787d225f1e | |||
| 06eb54fef8 | |||
| 0e8f56f0b3 | |||
| f826dc2f9f | |||
| 5cc3985b73 | |||
| ea715129e9 | |||
| b3c39d120f | |||
| 965585eb56 | |||
| 020d6a46c5 | |||
| 117b67a31c | |||
| 64f2945892 | |||
| 3a5fb440cc | |||
| b766109f66 | |||
| 67e24366ee | |||
| c5c97a8edf | |||
| fd26056d01 | |||
| 1650a946f2 | |||
| 04fde3620d | |||
| b6208955f3 | |||
| e6324f8a02 | |||
| ce03742e1b | |||
| 6e055f60de | |||
| 74763be4dd | |||
| e4a3cdb486 | |||
| 4659ae7683 | |||
| b0cc0cdcf9 | |||
| 22d87353be | |||
| f6ddd8bc5b | |||
| 2bd54854ec | |||
| b0e749eca5 | |||
| d0f1300a84 | |||
| 70e431787e | |||
| d0fc8fe056 | |||
| a06bffa413 | |||
| 3524172856 | |||
| d1001a99c2 | |||
| bb13c9e8ab | |||
| ec31aef6cb | |||
| 96c1307ebf | |||
| 2f45d7ecc1 | |||
| e321a241b7 | |||
| aab07df37b | |||
| dd49b98580 | |||
| e790ceea60 | |||
| 8530e628b4 | |||
| 8fa76b9015 | |||
| e2fa195e20 | |||
| ee1140483c | |||
| d0273f4630 | |||
| b9caf7e610 | |||
| d6fa5c2903 | |||
| 3e2048ace9 | |||
| 2e511c78af | |||
| 978effade4 | |||
| 579e1972d5 | |||
| 43e12a3702 | |||
| 47ff640292 | |||
| d041c1127d | |||
| 1c7b41f02e | |||
| 52930b77d8 | |||
| 43a98ab69b | |||
| ba2eb35590 | |||
| 55db20d4b7 | |||
| b3328c08a6 | |||
| 562e6c8d39 | |||
| f09509502e | |||
| 1ac9246a49 | |||
| 8090011126 | |||
| c170783a49 | |||
| 38dcae1fc8 | |||
| 91897882d2 | |||
| eaa1644ebd | |||
| f0af10e600 | |||
| a1f0ad3300 | |||
| 967bbe7f03 | |||
| 14bc7cf77f | |||
| 9f80ec4fb6 | |||
| a0d48c63a3 | |||
| b1252f837f | |||
| 91a7110941 | |||
| 01d761a901 | |||
| e710a24860 | |||
| 1f57a380c8 | |||
| df478a8e90 | |||
| 5a5ab955cb | |||
| 85fdf7cb25 | |||
| 707aa4fb8c | |||
| 1944aa097f | |||
| 394245620e | |||
| 682909e0fc | |||
| 11825e7a33 | |||
| 594bc36ce5 | |||
| 7808dc11af | |||
| 35b1e2885e | |||
| 17cbd1614a | |||
| 4d20367a0c | |||
| 5adfcfad7a | |||
| 42036d8f68 | |||
| af2fa5b5f5 | |||
| 74f15f790a | |||
| 4469880c2e | |||
| c0fb556a01 | |||
| 8f43867091 | |||
| 867ce5be32 | |||
| a800ac5a4e | |||
| 620cbc765c | |||
| 6e5a32308a | |||
| e8635b99c8 | |||
| 07e9ad307e | |||
| 23ce0b312d | |||
| dceb817853 | |||
| c443ee4f4a | |||
| e2d8154a80 | |||
| 4618ec1fef | |||
| 8e32cc11cb | |||
| 9e679aa621 | |||
| bc9ba75c2b | |||
| f124bb3d10 | |||
| 60ae1b47c5 | |||
| 7348f4a553 | |||
| 964cb84864 | |||
| 78b90e9300 | |||
| 340562e452 | |||
| 6a785fa536 | |||
| e68a731219 | |||
| 11e5617efc | |||
| cc858469ec | |||
| c2d81c59b6 | |||
| 91b328b7d5 | |||
| 9f110a6352 | |||
| add568169e | |||
| d5d89c2ab3 | |||
| 4709ed58aa | |||
| 10e3f2cf92 | |||
| 02d68146bd | |||
| 8cdebd485a | |||
| f032cf3380 | |||
| ff6891cdd0 | |||
| 9d521c9dd0 | |||
| 9e55a1291a | |||
| 426e75a298 | |||
| cdcf2d380b | |||
| 7181957506 | |||
| 5264d9299d | |||
| f6e2a58772 | |||
| 0083e56c22 | |||
| a910c4baa1 | |||
| 9a71d6b87a | |||
| 7c736a5ea0 | |||
| 6bd76ebfda | |||
| fa18b35df5 | |||
| 2af52abdba | |||
| 42115a8cfb | |||
| 9a66082998 | |||
| 04981b5e44 | |||
| 3bfe585364 | |||
| d5c3006136 | |||
| 416a939352 | |||
| f2bd87b7cf | |||
| 25d1094468 | |||
| 10ab81f4b4 | |||
| dda582db13 | |||
| be2f6e0517 | |||
| 9194c25a4e | |||
| b2d21cb71c | |||
| 385a1d3f0f | |||
| b65caa82fb | |||
| 10ab4e6782 | |||
| 2f8127d343 | |||
| a2340ea8b8 | |||
| 744b827169 | |||
| 8883e4ad8b | |||
| 3a2fb34a61 | |||
| 16a508cdb9 | |||
| 43647c6d50 | |||
| 70dd325aef | |||
| eabd25a8bf | |||
| 7ff346f612 | |||
| 81c70c347f | |||
| e9d62b39ef | |||
| df39f93b80 | |||
| 943b126d0e | |||
| b6a706756e | |||
| 7ed3ebfaa9 | |||
| 11788b2953 | |||
| dfc62ca5be | |||
| 0832ad392d | |||
| 11f74201ab | |||
| 5c9069d677 | |||
| 2d226b39bf | |||
| 35ed848fe8 | |||
| d08b1c62bb | |||
| 38205691f4 | |||
| e37723f4a2 | |||
| 4ae1bda33f | |||
| 7b1547b2bd | |||
| 2be5cb9b50 | |||
| 61a639d5d9 | |||
| bf9377e9a6 | |||
| 2660be962a | |||
| 73a8d9e373 | |||
| ac30a976d4 | |||
| 422d34ddaf | |||
| 80e925d71d | |||
| 1d6570cdec | |||
| 62f22e8976 | |||
| fa86e6aa85 | |||
| 4fcd5f8cbe | |||
| 60308df929 | |||
| bc8fd0607b | |||
| 3a5fb23427 | |||
| 91339fa8f0 | |||
| 7b3efbe4eb | |||
| c3bda4aaee | |||
| 756cd06625 | |||
| 2660bc514f | |||
| a323504a32 | |||
| c68baa953e | |||
| 46e83cce55 | |||
| 1e11525405 | |||
| ce9902da72 | |||
| 55cfcafd92 | |||
| 211966b42e | |||
| 135da533fd | |||
| c7d45af0aa | |||
| 2c3260d07b | |||
| e509da74e8 | |||
| d8dc734d38 | |||
| 7a49880ffc | |||
| 543fb2d7e4 | |||
| fb43bb0109 | |||
| 999c81e79c | |||
| 1b7a36bf64 | |||
| e361835cea | |||
| be140c1a23 | |||
| 970e9e0843 | |||
| e0e7797e99 | |||
| ece37b1dad | |||
| 0b3f830188 | |||
| 6c0f497281 | |||
| 596b2dbe12 | |||
| 3f87768c8c | |||
| d76ca7c65b | |||
| f25316c1e7 | |||
| 6134205383 | |||
| 871d1dfad6 | |||
| 983022438e | |||
| e7b42f9499 | |||
| 429b7bc169 | |||
| cd5bc22bdb | |||
| f1f0bb0431 | |||
| a2bed6709c | |||
| fc911a43ff | |||
| 1f004c56ac | |||
| e3ba72f3dd | |||
| 70d2682c6a | |||
| 19308d34e2 | |||
| 92ce9af092 | |||
| f74fb8ea2d | |||
| 9626c41817 | |||
| 19a1ddfa44 | |||
| 52390e1876 | |||
| d5e2002ffa | |||
| 35d25d3420 | |||
| 2ccf410a75 | |||
| 5e25cbcaa9 | |||
| 152efbb112 | |||
| b6fd0a5ca5 | |||
| 7a3069b68d | |||
| cf8be23af1 | |||
| 5fdcb52306 | |||
| fe82bf3e81 | |||
| 638d789ae6 | |||
| cbf63d06a8 | |||
| e22913d46f | |||
| 44e0a17d80 | |||
| 8f7d9de0b6 | |||
| d27f41a9e8 | |||
| 795263b5d4 | |||
| a8140bca89 | |||
| 9507a0001d | |||
| ff94e586eb | |||
| be8d1902d0 | |||
| 95462d3f25 | |||
| 59201f1def | |||
| 1e345eb0fc | |||
| 110a4b4518 | |||
| 5e9576ffa3 | |||
| cf42ea0bcb | |||
| c4366a54cc | |||
| f383511e0d | |||
| 3c1efb8c4d | |||
| 9bdaa45981 | |||
| 30f54a6bfc | |||
| b503e02b09 | |||
| 8dae53abda | |||
| 8a8a02431f | |||
| 4e77bae206 | |||
| c892a60da5 | |||
| 5bec1eedc1 | |||
| da56452fba | |||
| 98e47e8c73 | |||
| 977a20700a | |||
| 1a2bb06075 | |||
| e4f098a104 | |||
| 740ce75599 | |||
| a664579b58 | |||
| 8267194d5e | |||
| 2304f8cbc8 | |||
| ef531fb208 | |||
| e5879f4349 | |||
| 1cbfc60570 | |||
| edf1ed5c87 | |||
| 85d3b27d71 | |||
| 4c937ab94e | |||
| 0b5720906c | |||
| c88a4650ba | |||
| b1756e8e34 | |||
| 797544333c | |||
| 96f0c001d9 | |||
| ee6aae8211 | |||
| 981a4a9cd8 | |||
| d46b7d5908 | |||
| 7c64509c46 | |||
| cf8c08c980 | |||
| ab9b30c4d4 | |||
| b994e4bab2 | |||
| 9733e6874e | |||
| 8b690c9f9f | |||
| bd884274c0 | |||
| 367610dd38 | |||
| bbf90da6d0 | |||
| b8b2bde8d0 | |||
| 2af65d4f42 | |||
| 1b13449194 | |||
| 2498bdaad9 | |||
| 076132a5d4 | |||
| 2c8cc8ae2d | |||
| 2f65188edb | |||
| 1649dec58e | |||
| f0ee4f999d | |||
| 37fc28ef49 | |||
| 39b35cf54b | |||
| ef6ce3c7c0 | |||
| 5fd627b85b | |||
| 273c076ef6 | |||
| dcc00bca6d | |||
| 2510d71ae6 | |||
| f634e4dc0d | |||
| e13908c26c | |||
| ab1610021d | |||
| 9b3926baf1 | |||
| 28c082551d | |||
| 48e066999b | |||
| ea510f9d48 | |||
| 9e5721a5d9 | |||
| 2d363b8a5c | |||
| f40476e12f | |||
| f7a9e5bac0 | |||
| 30739179ee | |||
| 86df7de48f | |||
| efec28ada7 | |||
| 1247a98cf2 | |||
| 618af5e07a | |||
| b2bd9759c8 | |||
| 694baba127 | |||
| 4cef493b91 | |||
| 320c266276 | |||
| 4cf68c2f9f | |||
| 295da9cf02 | |||
| 04aec272db | |||
| 7c377783fa | |||
| 40442b9c99 | |||
| d2eb00d814 | |||
| 9ae1282854 | |||
| 07454d8f1a | |||
| 78ce676e81 | |||
| 0f9102e4d1 | |||
| 5cb8f4826c | |||
| ee339aebb8 | |||
| 53e804575f | |||
| 0f4ee703c6 | |||
| 735b34e7b7 | |||
| a65273690d | |||
| 76245f0fcf | |||
| 993ffe9096 | |||
| 8283a9faf3 | |||
| 22a5bce9de | |||
| 868182d1c8 | |||
| f4f3d06120 | |||
| 2ef35b89ca | |||
| de433ef4c1 | |||
| ac99c7fc7d | |||
| 0316caafd3 | |||
| c3a5b5b0fe | |||
| 4bac690f90 | |||
| 9466b1a21f | |||
| 6e3ac3a5a0 | |||
| 56e9971435 | |||
| a021731c09 | |||
| 04d688c1ba | |||
| def7b0ff90 | |||
| aa22e20e0a | |||
| 08a4392fa9 | |||
| 0eb8aafc0d | |||
| 1309f26f06 | |||
| def8f81f20 | |||
| 866e5c2d82 | |||
| edad54d961 | |||
| 0ae7f15ce7 | |||
| 4b5632a260 | |||
| 7f6cdc6cfe | |||
| 8104eac23a | |||
| 116966ce44 | |||
| 8ff5599d3a | |||
| 2fa599c40f | |||
| 0beb1c8116 | |||
| ac204f0849 | |||
| 4e096f1590 | |||
| ba419eae59 | |||
| 24a7d86577 | |||
| 65981598ed | |||
| fff45aca21 | |||
| fb6c0d4771 | |||
| b6ff597740 | |||
| 45b01994e2 | |||
| cdc1698c8b | |||
| a34feb9b1a | |||
| cca5d7fcbe | |||
| c8e580987d | |||
| 58c491ade3 | |||
| f361633f36 | |||
| 864d2056a7 | |||
| 2d2fcc8210 | |||
| 983e51d5fb | |||
| 1b030db9f2 | |||
| 38976270b4 | |||
| 25e6bddf29 | |||
| aa685c7794 | |||
| b106f894e4 | |||
| 20c27ea9d9 | |||
| 04eb1c6641 | |||
| b3bf437dd1 | |||
| 7672515e76 | |||
| 635978b137 | |||
| 74d52708b0 | |||
| 1a1f9d1488 | |||
| c2e12d716b | |||
| b23d82bc1e | |||
| c7e9fe1059 | |||
| 86438f1693 | |||
| e0fc360cf8 | |||
| f3aa8a03d3 | |||
| 4562a79b69 | |||
| 264ea1c25f | |||
| e14d70529c | |||
| 24d2fc10f0 | |||
| 29095be0d4 | |||
| 24f20c0dbb |
@@ -0,0 +1,38 @@
|
||||
#
|
||||
# You can add personal rules in your file .git/info/exclude
|
||||
*.swp
|
||||
*~
|
||||
/contrib/extract_translations/extract_translations.beam
|
||||
/doc/*.aux
|
||||
/doc/*.haux
|
||||
/doc/*.html
|
||||
/doc/*.htoc
|
||||
/doc/*.idx
|
||||
/doc/*.ilg
|
||||
/doc/*.ind
|
||||
/doc/*.log
|
||||
/doc/*.out
|
||||
/doc/*.pdf
|
||||
/doc/*.toc
|
||||
/doc/contributed_modules.tex
|
||||
/doc/version.tex
|
||||
/src/*.beam
|
||||
/src/*.so
|
||||
/src/*.so.dSYM
|
||||
/src/*/*.beam
|
||||
/src/*/Makefile
|
||||
/src/Makefile
|
||||
/src/XmppAddr.asn1db
|
||||
/src/XmppAddr.erl
|
||||
/src/XmppAddr.hrl
|
||||
/src/aclocal.m4
|
||||
/src/autom4te.cache
|
||||
/src/config.log
|
||||
/src/config.status
|
||||
/src/ejabberd.init
|
||||
/src/ejabberdctl.example
|
||||
/src/eldap/ELDAPv3.asn1db
|
||||
/src/eldap/ELDAPv3.erl
|
||||
/src/eldap/ELDAPv3.hrl
|
||||
/src/eldap/eldap_filter_yecc.erl
|
||||
/src/epam
|
||||
@@ -2,37 +2,53 @@ ejabberd - High-Performance Enterprise Instant Messaging Server
|
||||
|
||||
Quickstart guide
|
||||
|
||||
0. Dependancies
|
||||
|
||||
To build ejabberd, you need:
|
||||
0. Requirements
|
||||
|
||||
To compile ejabberd you need:
|
||||
- GNU Make
|
||||
- GCC
|
||||
- libexpat 1.95 or higher
|
||||
- Erlang/OTP R9C-2 or higher
|
||||
- OpenSSL 0.9.6 or higher (optional)
|
||||
- Zlib 1.2.3 or higher (optional)
|
||||
- GNU Iconv 1.8 or higher (optional, not needed on systems with GNU libc)
|
||||
- Libexpat 1.95 or higher
|
||||
- Erlang/OTP R10B-9 or higher. Recommended: R12B-5 and R13B04.
|
||||
Avoid R14A and R14B.
|
||||
- OpenSSL 0.9.8 or higher, for STARTTLS, SASL and SSL encryption.
|
||||
- Zlib 1.2.3 or higher, for Stream Compression support
|
||||
(XEP-0138). Optional.
|
||||
- Erlang mysql library. Optional. MySQL authentication/storage.
|
||||
- Erlang pgsql library. Optional. PostgreSQL authentication/storage.
|
||||
- PAM library. Optional. For Pluggable Authentication Modules (PAM).
|
||||
- GNU Iconv 1.8 or higher, for the IRC Transport
|
||||
(mod_irc). Optional. Not needed on systems with GNU Libc.
|
||||
- ImageMagick's Convert program. Optional. For CAPTCHA challenges.
|
||||
- exmpp 0.9.6 or higher. Optional. For import/export XEP-0227 files.
|
||||
|
||||
1. Build and install on *nix systems
|
||||
|
||||
To build ejabberd, please go into the src directory and type the
|
||||
following commands:
|
||||
./configure && make
|
||||
sudo make install
|
||||
1. Compile and install on *nix systems
|
||||
|
||||
To compile ejabberd, go to the directory src/ and execute the commands:
|
||||
./configure
|
||||
make
|
||||
|
||||
To install ejabberd, run this command with system administrator rights
|
||||
(root user):
|
||||
|
||||
sudo make install
|
||||
|
||||
These commands will:
|
||||
- install a startup script into the directory /usr/sbin,
|
||||
- install ejabberd into the directory /var/lib/ejabberd,
|
||||
- install the configuration file into /etc/ejabberd,
|
||||
- create a directory called /var/log/ejabberd to store log files.
|
||||
- Install the configuration files in /etc/ejabberd/
|
||||
- Install ejabberd binary, header and runtime files in /lib/ejabberd/
|
||||
- Install the administration script: /sbin/ejabberdctl
|
||||
- Install ejabberd documentation in /share/doc/ejabberd/
|
||||
- Create a spool directory: /var/lib/ejabberd/
|
||||
- Create a directory for log files: /var/log/ejabberd/
|
||||
|
||||
Note: "sudo make install" means that the "make install" command should
|
||||
be run with system administrator rights (root).
|
||||
|
||||
2. Run
|
||||
2. Start ejabberd
|
||||
|
||||
To run ejabberd, type the following command:
|
||||
erl -pa /var/lib/ejabberd/ebin -sname ejabberd -s ejabberd
|
||||
You can use the ejabberdctl command line administration script to
|
||||
start and stop ejabberd. For example:
|
||||
ejabberdctl start
|
||||
|
||||
For detailled informations, please refer to:
|
||||
http://www.process-one.net/en/projects/ejabberd/docs/guide_en.html
|
||||
|
||||
For detailed information please refer to the
|
||||
ejabberd Installation and Operation Guide
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
% List of ejabberd-modules to add for ejabberd packaging (source archive and installer)
|
||||
%
|
||||
% HTTP-binding:
|
||||
https://svn.process-one.net/ejabberd-modules/http_bind/tags/ejabberd-2.0.0-beta1
|
||||
https://svn.process-one.net/ejabberd-modules/mod_http_fileserver/tags/ejabberd-2.0.0-beta1
|
||||
%https://svn.process-one.net/ejabberd-modules/http_bind/trunk
|
||||
%https://svn.process-one.net/ejabberd-modules/mod_http_fileserver/trunk
|
||||
|
||||
@@ -20,9 +20,14 @@
|
||||
|
||||
start() ->
|
||||
ets:new(translations, [named_table, public]),
|
||||
ets:new(translations_obsolete, [named_table, public]),
|
||||
ets:new(files, [named_table, public]),
|
||||
ets:new(vars, [named_table, public]),
|
||||
case init:get_plain_arguments() of
|
||||
["-srcmsg2po", Dir, File] ->
|
||||
print_po_header(File),
|
||||
Status = process(Dir, File, srcmsg2po),
|
||||
halt(Status);
|
||||
["-unused", Dir, File] ->
|
||||
Status = process(Dir, File, unused),
|
||||
halt(Status);
|
||||
@@ -50,7 +55,11 @@ process(Dir, File, Used) ->
|
||||
unused ->
|
||||
ets:foldl(fun({Key, _}, _) ->
|
||||
io:format("~p~n", [Key])
|
||||
end, ok, translations);
|
||||
end, ok, translations);
|
||||
srcmsg2po ->
|
||||
ets:foldl(fun({Key, Trans}, _) ->
|
||||
print_translation_obsolete(Key, Trans)
|
||||
end, ok, translations_obsolete);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
@@ -59,7 +68,7 @@ process(Dir, File, Used) ->
|
||||
|
||||
parse_file(Dir, File, Used) ->
|
||||
ets:delete_all_objects(vars),
|
||||
case epp:parse_file(File, [Dir, filename:dirname(File)], []) of
|
||||
case epp:parse_file(File, [Dir, filename:dirname(File) | code:get_path()], []) of
|
||||
{ok, Forms} ->
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
@@ -71,49 +80,57 @@ parse_file(Dir, File, Used) ->
|
||||
|
||||
parse_form(Dir, File, Form, Used) ->
|
||||
case Form of
|
||||
%%{undefined, Something} ->
|
||||
%% io:format("Undefined: ~p~n", [Something]);
|
||||
{call,
|
||||
_,
|
||||
{remote, _, {atom, _, translate}, {atom, _, translate}},
|
||||
[_, {string, _, Str}]
|
||||
[_, {string, Line, Str}]
|
||||
} ->
|
||||
process_string(Dir, File, Str, Used);
|
||||
process_string(Dir, File, Line, Str, Used);
|
||||
{call,
|
||||
_,
|
||||
{remote, _, {atom, _, translate}, {atom, _, translate}},
|
||||
[_, {var, _, Name}]
|
||||
} ->
|
||||
case ets:lookup(vars, Name) of
|
||||
[{_Name, Value}] ->
|
||||
process_string(Dir, File, Value, Used);
|
||||
[{_Name, Value, Line}] ->
|
||||
process_string(Dir, File, Line, Value, Used);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
{match,
|
||||
_,
|
||||
{var, _, Name},
|
||||
{string, _, Value}
|
||||
{string, Line, Value}
|
||||
} ->
|
||||
ets:insert(vars, {Name, Value});
|
||||
ets:insert(vars, {Name, Value, Line});
|
||||
L when is_list(L) ->
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
parse_form(Dir, File, F, Used)
|
||||
parse_form(Dir, File, F, Used)
|
||||
end, L);
|
||||
T when is_tuple(T) ->
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
parse_form(Dir, File, F, Used)
|
||||
parse_form(Dir, File, F, Used)
|
||||
end, tuple_to_list(T));
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
process_string(_Dir, File, Str, Used) ->
|
||||
process_string(_Dir, _File, _Line, "", _Used) ->
|
||||
ok;
|
||||
|
||||
process_string(_Dir, File, Line, Str, Used) ->
|
||||
case {ets:lookup(translations, Str), Used} of
|
||||
{[{_Key, _Trans}], unused} ->
|
||||
ets:delete(translations, Str);
|
||||
{[{_Key, _Trans}], used} ->
|
||||
ok;
|
||||
{[{_Key, Trans}], srcmsg2po} ->
|
||||
ets:delete(translations_obsolete, Str),
|
||||
print_translation(File, Line, Str, Trans);
|
||||
{_, used} ->
|
||||
case ets:lookup(files, File) of
|
||||
[{_}] ->
|
||||
@@ -127,6 +144,15 @@ process_string(_Dir, File, Str, Used) ->
|
||||
_ -> io:format("{~p, \"\"}.~n", [Str])
|
||||
end,
|
||||
ets:insert(translations, {Str, ""});
|
||||
{_, srcmsg2po} ->
|
||||
case ets:lookup(files, File) of
|
||||
[{_}] ->
|
||||
ok;
|
||||
_ ->
|
||||
ets:insert(files, {File})
|
||||
end,
|
||||
ets:insert(translations, {Str, ""}),
|
||||
print_translation(File, Line, Str, "");
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
@@ -140,7 +166,8 @@ load_file(File) ->
|
||||
"" ->
|
||||
ok;
|
||||
_ ->
|
||||
ets:insert(translations, {Orig, Trans})
|
||||
ets:insert(translations, {Orig, Trans}),
|
||||
ets:insert(translations_obsolete, {Orig, Trans})
|
||||
end
|
||||
end, Terms);
|
||||
Err ->
|
||||
@@ -191,3 +218,77 @@ print_usage() ->
|
||||
" extract_translations . ./msgs/ru.msg~n"
|
||||
).
|
||||
|
||||
|
||||
%%%
|
||||
%%% Gettext
|
||||
%%%
|
||||
|
||||
print_po_header(File) ->
|
||||
MsgProps = get_msg_header_props(File),
|
||||
{Language, [LastT | AddT]} = prepare_props(MsgProps),
|
||||
application:load(ejabberd),
|
||||
{ok, Version} = application:get_key(ejabberd, vsn),
|
||||
print_po_header(Version, Language, LastT, AddT).
|
||||
|
||||
get_msg_header_props(File) ->
|
||||
{ok, F} = file:open(File, [read]),
|
||||
Lines = get_msg_header_props(F, []),
|
||||
file:close(F),
|
||||
Lines.
|
||||
|
||||
get_msg_header_props(F, Lines) ->
|
||||
String = io:get_line(F, ""),
|
||||
case io_lib:fread("% ", String) of
|
||||
{ok, [], RemString} ->
|
||||
case io_lib:fread("~s", RemString) of
|
||||
{ok, [Key], Value} when Value /= "\n" ->
|
||||
%% The first character in Value is a blankspace:
|
||||
%% And the last characters are 'slash n'
|
||||
ValueClean = string:substr(Value, 2, string:len(Value)-2),
|
||||
get_msg_header_props(F, Lines ++ [{Key, ValueClean}]);
|
||||
_ ->
|
||||
get_msg_header_props(F, Lines)
|
||||
end;
|
||||
_ ->
|
||||
Lines
|
||||
end.
|
||||
|
||||
prepare_props(MsgProps) ->
|
||||
Language = proplists:get_value("Language:", MsgProps),
|
||||
Authors = proplists:get_all_values("Author:", MsgProps),
|
||||
{Language, Authors}.
|
||||
|
||||
print_po_header(Version, Language, LastTranslator, AdditionalTranslatorsList) ->
|
||||
AdditionalTranslatorsString = build_additional_translators(AdditionalTranslatorsList),
|
||||
HeaderString =
|
||||
"msgid \"\"\n"
|
||||
"msgstr \"\"\n"
|
||||
"\"Project-Id-Version: " ++ Version ++ "\\n\"\n"
|
||||
++ "\"X-Language: " ++ Language ++ "\\n\"\n"
|
||||
"\"Last-Translator: " ++ LastTranslator ++ "\\n\"\n"
|
||||
++ AdditionalTranslatorsString ++
|
||||
"\"MIME-Version: 1.0\\n\"\n"
|
||||
"\"Content-Type: text/plain; charset=UTF-8\\n\"\n"
|
||||
"\"Content-Transfer-Encoding: 8bit\\n\"\n",
|
||||
io:format("~s~n", [HeaderString]).
|
||||
|
||||
build_additional_translators(List) ->
|
||||
lists:foldl(
|
||||
fun(T, Str) ->
|
||||
Str ++ "\"X-Additional-Translator: " ++ T ++ "\\n\"\n"
|
||||
end,
|
||||
"",
|
||||
List).
|
||||
|
||||
print_translation(File, Line, Str, StrT) ->
|
||||
{ok, StrQ, _} = regexp:gsub(Str, "\"", "\\\""),
|
||||
{ok, StrTQ, _} = regexp:gsub(StrT, "\"", "\\\""),
|
||||
io:format("#: ~s:~p~nmsgid \"~s\"~nmsgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).
|
||||
|
||||
print_translation_obsolete(Str, StrT) ->
|
||||
File = "unknown.erl",
|
||||
Line = 1,
|
||||
{ok, StrQ, _} = regexp:gsub(Str, "\"", "\\\""),
|
||||
{ok, StrTQ, _} = regexp:gsub(StrT, "\"", "\\\""),
|
||||
io:format("#: ~s:~p~n#~~ msgid \"~s\"~n#~~ msgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).
|
||||
|
||||
|
||||
@@ -3,16 +3,24 @@
|
||||
# Frontend for ejabberd's extract_translations.erl
|
||||
# by Badlop
|
||||
|
||||
# How to create template files for a new language:
|
||||
# NEWLANG=zh
|
||||
# cp msgs/ejabberd.pot msgs/$NEWLANG.po
|
||||
# echo \{\"\",\"\"\}. > msgs/$NEWLANG.msg
|
||||
# ../../extract_translations/prepare-translation.sh -updateall
|
||||
|
||||
prepare_dirs ()
|
||||
{
|
||||
# Where is Erlang binary
|
||||
ERL=`which erl`
|
||||
|
||||
EJA_DIR=`pwd`/../..
|
||||
EJA_SRC_DIR=$EJA_DIR/src/
|
||||
EJA_MSGS_DIR=$EJA_SRC_DIR/msgs/
|
||||
EXTRACT_DIR=$EJA_DIR/contrib/extract_translations/
|
||||
EXTRACT_ERL=extract_translations.erl
|
||||
EXTRACT_BEAM=extract_translations.beam
|
||||
SRC_DIR=$EJA_DIR/src
|
||||
EXTRACT_ERL=$EXTRACT_DIR/extract_translations.erl
|
||||
EXTRACT_BEAM=$EXTRACT_DIR/extract_translations.beam
|
||||
|
||||
SRC_DIR=$RUN_DIR/src
|
||||
MSGS_DIR=$SRC_DIR/msgs
|
||||
|
||||
if !([[ -n $EJA_DIR ]])
|
||||
@@ -22,9 +30,7 @@ prepare_dirs ()
|
||||
|
||||
if !([[ -x $EXTRACT_BEAM ]])
|
||||
then
|
||||
echo -n "Compiling extract_translations.erl: "
|
||||
sh -c "cd $EXTRACT_DIR; $ERL -compile $EXTRACT_ERL"
|
||||
echo "ok"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -76,14 +82,14 @@ extract_lang ()
|
||||
extract_lang_all ()
|
||||
{
|
||||
cd $MSGS_DIR
|
||||
for i in *.msg; do
|
||||
for i in $( ls *.msg ) ; do
|
||||
extract_lang $i;
|
||||
done
|
||||
|
||||
echo -e "File\tMissing\tLanguage\t\tLast translator"
|
||||
echo -e "----\t-------\t--------\t\t---------------"
|
||||
cd $MSGS_DIR
|
||||
for i in *.msg; do
|
||||
for i in $( ls *.msg ) ; do
|
||||
MISSING=`cat $i.translate | grep "\", \"\"}." | wc -l`
|
||||
LANGUAGE=`grep "Language:" $i.translate | sed 's/% Language: //g'`
|
||||
LASTAUTH=`grep "Author:" $i.translate | head -n 1 | sed 's/% Author: //g'`
|
||||
@@ -91,7 +97,7 @@ extract_lang_all ()
|
||||
done
|
||||
|
||||
cd $MSGS_DIR
|
||||
REVISION=`svn info | grep "^Rev" | head -1 | awk '{print $2}'`
|
||||
REVISION=`git describe --always`
|
||||
zip $HOME/ejabberd-langs-$REVISION.zip *.translate;
|
||||
|
||||
rm *.translate
|
||||
@@ -140,6 +146,130 @@ find_unused_full ()
|
||||
cd ..
|
||||
}
|
||||
|
||||
extract_lang_srcmsg2po ()
|
||||
{
|
||||
LANG=$1
|
||||
LANG_CODE=$LANG.$PROJECT
|
||||
MSGS_PATH=$MSGS_DIR/$LANG_CODE.msg
|
||||
PO_PATH=$MSGS_DIR/$LANG_CODE.po
|
||||
|
||||
echo $MSGS_PATH
|
||||
|
||||
$ERL -pa $EXTRACT_DIR -pa $SRC_DIR -pa $EJA_SRC_DIR -pa /lib/ejabberd/include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$PO_PATH.1
|
||||
sed -e 's/ \[\]$/ \"\"/g;' $PO_PATH.1 > $PO_PATH.2
|
||||
msguniq --sort-by-file $PO_PATH.2 --output-file=$PO_PATH
|
||||
|
||||
rm $PO_PATH.*
|
||||
}
|
||||
|
||||
extract_lang_src2pot ()
|
||||
{
|
||||
LANG_CODE=$PROJECT
|
||||
MSGS_PATH=$MSGS_DIR/$LANG_CODE.msg
|
||||
POT_PATH=$MSGS_DIR/$LANG_CODE.pot
|
||||
|
||||
echo -n "" >$MSGS_PATH
|
||||
echo "% Language: Language Name" >>$MSGS_PATH
|
||||
echo "% Author: Translator name and contact method" >>$MSGS_PATH
|
||||
echo "" >>$MSGS_PATH
|
||||
|
||||
cd $SRC_DIR
|
||||
$ERL -pa $EXTRACT_DIR -pa $SRC_DIR -pa $EJA_SRC_DIR -pa /lib/ejabberd/include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$POT_PATH.1
|
||||
sed -e 's/ \[\]$/ \"\"/g;' $POT_PATH.1 > $POT_PATH.2
|
||||
|
||||
#msguniq --sort-by-file $POT_PATH.2 $EJA_MSGS_DIR --output-file=$POT_PATH
|
||||
msguniq --sort-by-file $POT_PATH.2 --output-file=$POT_PATH
|
||||
|
||||
rm $POT_PATH.*
|
||||
rm $MSGS_PATH
|
||||
|
||||
# If the project is a specific module, not the main ejabberd
|
||||
if [[ $PROJECT != ejabberd ]] ; then
|
||||
# Remove from project.pot the strings that are already present in the general ejabberd
|
||||
EJABBERD_MSG_FILE=$EJA_MSGS_DIR/es.po # This is just some file with translated strings
|
||||
POT_PATH_TEMP=$POT_PATH.temp
|
||||
msgattrib --set-obsolete --only-file=$EJABBERD_MSG_FILE -o $POT_PATH_TEMP $POT_PATH
|
||||
mv $POT_PATH_TEMP $POT_PATH
|
||||
fi
|
||||
}
|
||||
|
||||
extract_lang_popot2po ()
|
||||
{
|
||||
LANG_CODE=$1
|
||||
PO_PATH=$MSGS_DIR/$LANG_CODE.po
|
||||
POT_PATH=$MSGS_DIR/$PROJECT.pot
|
||||
|
||||
msgmerge $PO_PATH $POT_PATH >$PO_PATH.translate 2>/dev/null
|
||||
mv $PO_PATH.translate $PO_PATH
|
||||
}
|
||||
|
||||
extract_lang_po2msg ()
|
||||
{
|
||||
LANG_CODE=$1
|
||||
PO_PATH=$LANG_CODE.po
|
||||
MS_PATH=$PO_PATH.ms
|
||||
MSGID_PATH=$PO_PATH.msgid
|
||||
MSGSTR_PATH=$PO_PATH.msgstr
|
||||
MSGS_PATH=$LANG_CODE.msg
|
||||
|
||||
cd $MSGS_DIR
|
||||
|
||||
# Check PO has correct ~
|
||||
# Let's convert to C format so we can use msgfmt
|
||||
PO_TEMP=$LANG_CODE.po.temp
|
||||
cat $PO_PATH | sed 's/%/perc/g' | sed 's/~/%/g' | sed 's/#:.*/#, c-format/g' >$PO_TEMP
|
||||
msgfmt $PO_TEMP --check-format
|
||||
result=$?
|
||||
rm $PO_TEMP
|
||||
if [ $result -ne 0 ] ; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msgattrib $PO_PATH --translated --no-fuzzy --no-obsolete --no-location --no-wrap | grep "^msg" | tail --lines=+3 >$MS_PATH
|
||||
grep "^msgid" $PO_PATH.ms | sed 's/^msgid //g' >$MSGID_PATH
|
||||
grep "^msgstr" $PO_PATH.ms | sed 's/^msgstr //g' >$MSGSTR_PATH
|
||||
paste $MSGID_PATH $MSGSTR_PATH --delimiter=, | awk '{print "{" $0 "}."}' | sort -g >$MSGS_PATH
|
||||
|
||||
rm $MS_PATH
|
||||
rm $MSGID_PATH
|
||||
rm $MSGSTR_PATH
|
||||
}
|
||||
|
||||
extract_lang_updateall ()
|
||||
{
|
||||
echo "Generating POT"
|
||||
extract_lang_src2pot
|
||||
|
||||
cd $MSGS_DIR
|
||||
echo ""
|
||||
echo -e "File Missing Language Last translator"
|
||||
echo -e "---- ------- -------- ---------------"
|
||||
for i in $( ls *.msg ) ; do
|
||||
LANG_CODE=${i%.msg}
|
||||
echo -n $LANG_CODE | awk '{printf "%-6s", $1 }'
|
||||
|
||||
# Convert old MSG file to PO
|
||||
PO=$LANG_CODE.po
|
||||
[ -f $PO ] || extract_lang_srcmsg2po $LANG_CODE
|
||||
|
||||
extract_lang_popot2po $LANG_CODE
|
||||
extract_lang_po2msg $LANG_CODE
|
||||
|
||||
MISSING=`msgfmt --statistics $PO 2>&1 | awk '{printf "%5s", $4 }'`
|
||||
echo -n " $MISSING"
|
||||
|
||||
LANGUAGE=`grep "Language:" $PO | sed 's/\"X-Language: //g' | sed 's/\\\\n\"//g' | awk '{printf "%-12s", $1}'`
|
||||
echo -n " $LANGUAGE"
|
||||
|
||||
LASTAUTH=`grep "Last-Translator" $PO | sed 's/\"Last-Translator: //g' | sed 's/\\\\n\"//g'`
|
||||
echo " $LASTAUTH"
|
||||
done
|
||||
echo ""
|
||||
rm messages.mo
|
||||
|
||||
cd ..
|
||||
}
|
||||
|
||||
translation_instructions ()
|
||||
{
|
||||
echo ""
|
||||
@@ -158,34 +288,76 @@ translation_instructions ()
|
||||
echo " $MSGS_PATH"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
-help)
|
||||
echo "Options:"
|
||||
echo " -langall"
|
||||
echo " -lang LANGUAGE_FILE"
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " ./prepare-translation.sh -lang es.msg"
|
||||
exit 0
|
||||
EJA_DIR=`pwd`/..
|
||||
RUN_DIR=`pwd`/..
|
||||
PROJECT=ejabberd
|
||||
|
||||
while [ $# -ne 0 ] ; do
|
||||
PARAM=$1
|
||||
shift
|
||||
case $PARAM in
|
||||
--) break ;;
|
||||
-project)
|
||||
PROJECT=$1
|
||||
shift
|
||||
;;
|
||||
-ejadir)
|
||||
EJA_DIR=$1
|
||||
shift
|
||||
;;
|
||||
-rundir)
|
||||
RUN_DIR=$1
|
||||
shift
|
||||
;;
|
||||
-lang)
|
||||
LANGU=$2
|
||||
LANGU=$1
|
||||
prepare_dirs
|
||||
extract_lang $LANGU
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-langall)
|
||||
prepare_dirs
|
||||
extract_lang_all
|
||||
;;
|
||||
-srcmsg2po)
|
||||
LANG_CODE=$1
|
||||
prepare_dirs
|
||||
extract_lang_srcmsg2po $LANG_CODE
|
||||
shift
|
||||
;;
|
||||
-popot2po)
|
||||
LANG_CODE=$1
|
||||
prepare_dirs
|
||||
extract_lang_popot2po $LANG_CODE
|
||||
shift
|
||||
;;
|
||||
-src2pot)
|
||||
prepare_dirs
|
||||
extract_lang_src2pot
|
||||
;;
|
||||
-po2msg)
|
||||
LANG_CODE=$1
|
||||
prepare_dirs
|
||||
extract_lang_po2msg $LANG_CODE
|
||||
shift
|
||||
;;
|
||||
-updateall)
|
||||
prepare_dirs
|
||||
extract_lang_updateall
|
||||
;;
|
||||
*)
|
||||
echo "unknown option: '$1 $2'"
|
||||
shift
|
||||
shift
|
||||
echo "Options:"
|
||||
echo " -langall"
|
||||
echo " -lang LANGUAGE_FILE"
|
||||
echo " -srcmsg2po LANGUAGE Construct .msg file using source code to PO file"
|
||||
echo " -src2pot Generate template POT file from source code"
|
||||
echo " -popot2po LANGUAGE Update PO file with template POT file"
|
||||
echo " -po2msg LANGUAGE Export PO file to MSG file"
|
||||
echo " -updateall Generate POT and update all PO"
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " ./prepare-translation.sh -lang es.msg"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "End."
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
# $Id$
|
||||
|
||||
SHELL = /bin/bash
|
||||
|
||||
CONTRIBUTED_MODULES = ""
|
||||
ifeq ($(shell ls mod_http_bind.tex),mod_http_bind.tex)
|
||||
CONTRIBUTED_MODULES += "\\r\\n\\setboolean{modhttpbind}{true}"
|
||||
endif
|
||||
ifeq ($(shell ls mod_http_fileserver.tex),mod_http_fileserver.tex)
|
||||
CONTRIBUTED_MODULES += "\\r\\n\\setboolean{modhttpfileserver}{true}"
|
||||
endif
|
||||
#ifeq ($(shell ls mod_http_bind.tex),mod_http_bind.tex)
|
||||
# CONTRIBUTED_MODULES += "\\n\\setboolean{modhttpbind}{true}"
|
||||
#endif
|
||||
|
||||
|
||||
all: release pdf html
|
||||
@@ -14,14 +13,14 @@ all: release pdf html
|
||||
release:
|
||||
@echo "Notes for the releaser:"
|
||||
@echo "* Do not forget to add a link to the release notes in guide.tex"
|
||||
@echo "* Do not forget to update the version number in src/ejabberd.hrl!"
|
||||
@echo "* Do not forget to update the version number in src/ejabberd.app!"
|
||||
@echo "* Do not forget to update the features in introduction.tex (including \new{} and \improved{} tags)."
|
||||
@echo "Press any key to continue"
|
||||
@read foo
|
||||
##@read foo
|
||||
@echo "% ejabberd version (automatically generated)." > version.tex
|
||||
@echo "\\\newcommand{\\\version}{"`cat ../src/ejabberd.hrl | grep VERSION | sed s/-define\(VERSION,\ \"//g | sed s/\"\).//g`"}" >> version.tex
|
||||
@echo "\newcommand{\version}{"`sed '/vsn/!d;s/\(.*\)"\(.*\)"\(.*\)/\2/' ../src/ejabberd.app`"}" >> version.tex
|
||||
@echo -n "% Contributed modules (automatically generated)." > contributed_modules.tex
|
||||
@echo "$(CONTRIBUTED_MODULES)" >> contributed_modules.tex
|
||||
@echo -e "$(CONTRIBUTED_MODULES)" >> contributed_modules.tex
|
||||
|
||||
html: guide.html dev.html features.html
|
||||
|
||||
@@ -30,7 +29,6 @@ pdf: guide.pdf features.pdf
|
||||
clean:
|
||||
rm -f *.aux
|
||||
rm -f *.haux
|
||||
rm -f *.html
|
||||
rm -f *.htoc
|
||||
rm -f *.idx
|
||||
rm -f *.ilg
|
||||
@@ -39,15 +37,19 @@ clean:
|
||||
rm -f *.out
|
||||
rm -f *.pdf
|
||||
rm -f *.toc
|
||||
[ ! -f contributed_modules.tex ] || rm contributed_modules.tex
|
||||
|
||||
distclean: clean
|
||||
rm -f *.html
|
||||
|
||||
guide.html: guide.tex
|
||||
hevea -fix -noiso -pedantic guide.tex
|
||||
hevea -fix -pedantic guide.tex
|
||||
|
||||
dev.html: dev.tex
|
||||
hevea -fix -noiso -pedantic dev.tex
|
||||
hevea -fix -pedantic dev.tex
|
||||
|
||||
features.html: features.tex
|
||||
hevea -fix -noiso -pedantic features.tex
|
||||
hevea -fix -pedantic features.tex
|
||||
|
||||
guide.pdf: guide.tex
|
||||
pdflatex guide.tex
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
APPNAME = ejabberd
|
||||
VSN = SVN
|
||||
VSN = $(shell sed '/vsn/!d;s/\(.*\)"\(.*\)"\(.*\)/\2/' ../../src/ejabberd.app)
|
||||
|
||||
DOCDIR=.
|
||||
SRCDIR=../../src
|
||||
@@ -11,6 +11,7 @@ all: docs
|
||||
clean:
|
||||
rm -f *.html
|
||||
rm edoc-info
|
||||
rm erlang.png
|
||||
|
||||
docs:
|
||||
erl -noshell -run edoc_run application \
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@author Mickael Remond <mickael.remond@process-one.net>
|
||||
[http://www.process-one.net/]
|
||||
@copyright 2007 Process-one
|
||||
@copyright 2007 ProcessOne
|
||||
@version {@vsn}, {@date} {@time}
|
||||
@title ejabberd Development API Documentation
|
||||
|
||||
|
||||
@@ -71,10 +71,22 @@ pre, tt, code {
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-left: 1em;
|
||||
margin:1ex 2ex;
|
||||
border:1px dashed lightgrey;
|
||||
background-color:#f9f9f9;
|
||||
padding:0.5ex;
|
||||
}
|
||||
|
||||
pre em {
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dt {
|
||||
margin:0ex 2ex;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin:0ex 0ex 1ex 4ex;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Ejabberd 2.0.0-beta1 Developers Guide
|
||||
<TITLE>Ejabberd 2.1.7 Developers Guide
|
||||
</TITLE>
|
||||
|
||||
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
|
||||
@@ -46,10 +46,10 @@ TD P{margin:0px;}
|
||||
</STYLE>
|
||||
</HEAD>
|
||||
<BODY >
|
||||
<!--HEVEA command line is: /usr/bin/hevea -fix -noiso -pedantic dev.tex -->
|
||||
<!--HEVEA command line is: /usr/bin/hevea -fix -pedantic dev.tex -->
|
||||
<!--CUT DEF section 1 --><P><A NAME="titlepage"></A>
|
||||
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.0.0-beta1 Developers Guide</H1><H3 CLASS="titlerest">Alexey Shchepin<BR>
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.7 Developers Guide</H1><H3 CLASS="titlerest">Alexey Shchepin<BR>
|
||||
<A HREF="mailto:alexey@sevcom.net"><TT>mailto:alexey@sevcom.net</TT></A><BR>
|
||||
<A HREF="xmpp:aleksey@jabber.ru"><TT>xmpp:aleksey@jabber.ru</TT></A></H3></TD></TR>
|
||||
</TABLE><DIV CLASS="center">
|
||||
@@ -84,7 +84,7 @@ Kevin Smith, Current maintainer of the Psi project</I></BLOCKQUOTE><!--TOC secti
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc15">8.2  Services</A>
|
||||
</LI></UL>
|
||||
</LI></UL><P>Introduction
|
||||
<A NAME="intro"></A></P><P><TT>ejabberd</TT> is a free and open source instant messaging server written in <A HREF="http://www.erlang.org/">Erlang</A>.</P><P><TT>ejabberd</TT> is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</P><P><TT>ejabberd</TT> is designed to be a rock-solid and feature rich XMPP server.</P><P><TT>ejabberd</TT> is suitable for small deployments, whether they need to be scalable or not, as well as extremely big deployments.</P><!--TOC section Key Features-->
|
||||
<A NAME="intro"></A></P><P><TT>ejabberd</TT> is a free and open source instant messaging server written in <A HREF="http://www.erlang.org/">Erlang/OTP</A>.</P><P><TT>ejabberd</TT> is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</P><P><TT>ejabberd</TT> is designed to be a rock-solid and feature rich XMPP server.</P><P><TT>ejabberd</TT> is suitable for small deployments, whether they need to be scalable or not, as well as extremely big deployments.</P><!--TOC section Key Features-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc1">1</A>  Key Features</H2><!--SEC END --><P>
|
||||
<A NAME="keyfeatures"></A>
|
||||
</P><P><TT>ejabberd</TT> is:
|
||||
@@ -92,13 +92,13 @@ Kevin Smith, Current maintainer of the Psi project</I></BLOCKQUOTE><!--TOC secti
|
||||
Cross-platform: <TT>ejabberd</TT> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.</LI><LI CLASS="li-itemize">Distributed: You can run <TT>ejabberd</TT> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.</LI><LI CLASS="li-itemize">Fault-tolerant: You can deploy an <TT>ejabberd</TT> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced ‘on the fly’.</LI><LI CLASS="li-itemize">Administrator Friendly: <TT>ejabberd</TT> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Comprehensive documentation.
|
||||
</LI><LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </LI><LI CLASS="li-itemize">Web interface for administration tasks.
|
||||
</LI><LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </LI><LI CLASS="li-itemize">Web Administration.
|
||||
</LI><LI CLASS="li-itemize">Shared Roster Groups.
|
||||
</LI><LI CLASS="li-itemize">Command line administration tool. </LI><LI CLASS="li-itemize">Can integrate with existing authentication mechanisms.
|
||||
</LI><LI CLASS="li-itemize">Capability to send announce messages.
|
||||
</LI></UL></LI><LI CLASS="li-itemize">Internationalized: <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Translated in 17 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
|
||||
Translated to 25 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
|
||||
</LI></UL></LI><LI CLASS="li-itemize">Open Standards: <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Fully XMPP compliant.
|
||||
@@ -118,35 +118,35 @@ Load only the modules you want.
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
SASL and STARTTLS for c2s and s2s connections.
|
||||
</LI><LI CLASS="li-itemize">STARTTLS and Dialback s2s connections.
|
||||
</LI><LI CLASS="li-itemize">Web interface accessible via HTTPS secure access.
|
||||
</LI><LI CLASS="li-itemize">Web Admin accessible via HTTPS secure access.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Databases
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Native MySQL support.
|
||||
Internal database for fast deployment (Mnesia).
|
||||
</LI><LI CLASS="li-itemize">Native MySQL support.
|
||||
</LI><LI CLASS="li-itemize">Native PostgreSQL support.
|
||||
</LI><LI CLASS="li-itemize">Mnesia.
|
||||
</LI><LI CLASS="li-itemize">ODBC data storage support.
|
||||
</LI><LI CLASS="li-itemize">Microsoft SQL Server support. </LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Authentication
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
PAM, LDAP and ODBC. </LI><LI CLASS="li-itemize">External Authentication script.
|
||||
</LI><LI CLASS="li-itemize">Internal Authentication.
|
||||
Internal Authentication.
|
||||
</LI><LI CLASS="li-itemize">PAM, LDAP and ODBC. </LI><LI CLASS="li-itemize">External Authentication script.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Others
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Compressing XML streams with Stream Compression (<A HREF="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</A>).
|
||||
</LI><LI CLASS="li-itemize">Interface with networks such as AIM, ICQ and MSN.
|
||||
Support for virtual hosting.
|
||||
</LI><LI CLASS="li-itemize">Compressing XML streams with Stream Compression (<A HREF="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</A>).
|
||||
</LI><LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</A>).
|
||||
</LI><LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component.
|
||||
</LI><LI CLASS="li-itemize">Support for virtual hosting.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> service.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with support for clustering and HTML logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</A>.
|
||||
</LI><LI CLASS="li-itemize">Support for web clients: <A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> and <A HREF="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</A> services.
|
||||
</LI><LI CLASS="li-itemize">IRC transport.
|
||||
</LI><LI CLASS="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
|
||||
</LI></UL>
|
||||
</LI></UL><!--TOC section How it Works-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc3">3</A>  How it Works</H2><!--SEC END --><P>
|
||||
<A NAME="howitworks"></A></P><P>A Jabber domain is served by one or more <TT>ejabberd</TT> nodes. These nodes can
|
||||
<A NAME="howitworks"></A></P><P>A XMPP domain is served by one or more <TT>ejabberd</TT> nodes. These nodes can
|
||||
be run on different machines that are connected via a network. They all must
|
||||
have the ability to connect to port 4369 of all another nodes, and must have
|
||||
the same magic cookie (see Erlang/OTP documentation, in other words the file
|
||||
@@ -159,7 +159,7 @@ router;
|
||||
</LI><LI CLASS="li-itemize">session manager;
|
||||
</LI><LI CLASS="li-itemize">S2S manager;
|
||||
</LI></UL><!--TOC subsection Router-->
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc4">3.1</A>  Router</H3><!--SEC END --><P>This module is the main router of Jabber packets on each node. It routes
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc4">3.1</A>  Router</H3><!--SEC END --><P>This module is the main router of XMPP packets on each node. It routes
|
||||
them based on their destinations domains. It has two tables: local and global
|
||||
routes. First, domain of packet destination searched in local table, and if it
|
||||
found, then the packet is routed to appropriate process. If no, then it
|
||||
@@ -170,10 +170,10 @@ manager.</P><!--TOC subsection Local Router-->
|
||||
name. If destination JID has a non-empty user part, then it routed to the
|
||||
session manager, else it is processed depending on it’s content.</P><!--TOC subsection Session Manager-->
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc6">3.3</A>  Session Manager</H3><!--SEC END --><P>This module routes packets to local users. It searches for what user resource
|
||||
packet must be sended via presence table. If this resource is connected to
|
||||
packet must be sent via presence table. If this resource is connected to
|
||||
this node, it is routed to C2S process, if it connected via another node, then
|
||||
the packet is sent to session manager on that node.</P><!--TOC subsection S2S Manager-->
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc7">3.4</A>  S2S Manager</H3><!--SEC END --><P>This module routes packets to other Jabber servers. First, it checks if an
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc7">3.4</A>  S2S Manager</H3><!--SEC END --><P>This module routes packets to other XMPP servers. First, it checks if an
|
||||
open S2S connection from the domain of the packet source to the domain of
|
||||
packet destination already exists. If it is open on another node, then it
|
||||
routes the packet to S2S manager on that node, if it is open on this node, then
|
||||
@@ -194,6 +194,9 @@ operation are as follows:
|
||||
auth:User:Server:Password (check if a username/password pair is correct)
|
||||
</LI><LI CLASS="li-itemize">isuser:User:Server (check if it’s a valid user)
|
||||
</LI><LI CLASS="li-itemize">setpass:User:Server:Password (set user’s password)
|
||||
</LI><LI CLASS="li-itemize">tryregister:User:Server:Password (try to register an account)
|
||||
</LI><LI CLASS="li-itemize">removeuser:User:Server (remove this account)
|
||||
</LI><LI CLASS="li-itemize">removeuser3:User:Server:Password (remove this account if the password is correct)
|
||||
</LI></UL>
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">write to stdout: AABB
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
\newcommand{\ns}[1]{\texttt{#1}}
|
||||
\newcommand{\ejabberd}{\texttt{ejabberd}}
|
||||
\newcommand{\Jabber}{Jabber}
|
||||
\newcommand{\XMPP}{XMPP}
|
||||
|
||||
%% Modules
|
||||
\newcommand{\module}[1]{\texttt{#1}}
|
||||
@@ -100,7 +101,7 @@
|
||||
\label{howitworks}
|
||||
|
||||
|
||||
A \Jabber{} domain is served by one or more \ejabberd{} nodes. These nodes can
|
||||
A \XMPP{} domain is served by one or more \ejabberd{} nodes. These nodes can
|
||||
be run on different machines that are connected via a network. They all must
|
||||
have the ability to connect to port 4369 of all another nodes, and must have
|
||||
the same magic cookie (see Erlang/OTP documentation, in other words the file
|
||||
@@ -121,7 +122,7 @@ Each \ejabberd{} node have following modules:
|
||||
|
||||
\subsection{Router}
|
||||
|
||||
This module is the main router of \Jabber{} packets on each node. It routes
|
||||
This module is the main router of \XMPP{} packets on each node. It routes
|
||||
them based on their destinations domains. It has two tables: local and global
|
||||
routes. First, domain of packet destination searched in local table, and if it
|
||||
found, then the packet is routed to appropriate process. If no, then it
|
||||
@@ -140,14 +141,14 @@ session manager, else it is processed depending on it's content.
|
||||
\subsection{Session Manager}
|
||||
|
||||
This module routes packets to local users. It searches for what user resource
|
||||
packet must be sended via presence table. If this resource is connected to
|
||||
packet must be sent via presence table. If this resource is connected to
|
||||
this node, it is routed to C2S process, if it connected via another node, then
|
||||
the packet is sent to session manager on that node.
|
||||
|
||||
|
||||
\subsection{S2S Manager}
|
||||
|
||||
This module routes packets to other \Jabber{} servers. First, it checks if an
|
||||
This module routes packets to other \XMPP{} servers. First, it checks if an
|
||||
open S2S connection from the domain of the packet source to the domain of
|
||||
packet destination already exists. If it is open on another node, then it
|
||||
routes the packet to S2S manager on that node, if it is open on this node, then
|
||||
@@ -175,6 +176,9 @@ That script is supposed to do theses actions, in an infinite loop:
|
||||
\item auth:User:Server:Password (check if a username/password pair is correct)
|
||||
\item isuser:User:Server (check if it's a valid user)
|
||||
\item setpass:User:Server:Password (set user's password)
|
||||
\item tryregister:User:Server:Password (try to register an account)
|
||||
\item removeuser:User:Server (remove this account)
|
||||
\item removeuser3:User:Server:Password (remove this account if the password is correct)
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\item write to stdout: AABB
|
||||
|
||||
|
Before Width: | Height: | Size: 9.1 KiB |
@@ -2,7 +2,7 @@
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Ejabberd 2.0.0-beta1 Feature Sheet
|
||||
<TITLE>Ejabberd 2.1.7 Feature Sheet
|
||||
</TITLE>
|
||||
|
||||
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
|
||||
@@ -47,10 +47,10 @@ SPAN{width:20%; float:right; text-align:left; margin-left:auto;}
|
||||
</STYLE>
|
||||
</HEAD>
|
||||
<BODY >
|
||||
<!--HEVEA command line is: /usr/bin/hevea -fix -noiso -pedantic features.tex -->
|
||||
<!--HEVEA command line is: /usr/bin/hevea -fix -pedantic features.tex -->
|
||||
<!--CUT DEF section 1 --><P><A NAME="titlepage"></A>
|
||||
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.0.0-beta1 Feature Sheet</H1><H3 CLASS="titlerest">Sander Devrieze<BR>
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.7 Feature Sheet</H1><H3 CLASS="titlerest">Sander Devrieze<BR>
|
||||
<A HREF="mailto:s.devrieze@pandora.be"><TT>mailto:s.devrieze@pandora.be</TT></A><BR>
|
||||
<A HREF="xmpp:sander@devrieze.dyndns.org"><TT>xmpp:sander@devrieze.dyndns.org</TT></A></H3></TD></TR>
|
||||
</TABLE><DIV CLASS="center">
|
||||
@@ -61,7 +61,7 @@ SPAN{width:20%; float:right; text-align:left; margin-left:auto;}
|
||||
</DIV><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>I can thoroughly recommend ejabberd for ease of setup –
|
||||
Kevin Smith, Current maintainer of the Psi project</I></FONT></BLOCKQUOTE><P>Introduction
|
||||
<A NAME="intro"></A></P><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>I just tried out ejabberd and was impressed both by ejabberd itself and the language it is written in, Erlang. —
|
||||
Joeri</I></FONT></BLOCKQUOTE><P><TT>ejabberd</TT> is a <B><FONT SIZE=4><FONT COLOR="#001376">free and open source</FONT></FONT></B> instant messaging server written in <A HREF="http://www.erlang.org/">Erlang</A>.</P><P><TT>ejabberd</TT> is <B><FONT SIZE=4><FONT COLOR="#001376">cross-platform</FONT></FONT></B>, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</P><P><TT>ejabberd</TT> is designed to be a <B><FONT SIZE=4><FONT COLOR="#001376">rock-solid and feature rich</FONT></FONT></B> XMPP server.</P><P><TT>ejabberd</TT> is suitable for small deployments, whether they need to be <B><FONT SIZE=4><FONT COLOR="#001376">scalable</FONT></FONT></B> or not, as well as extremely big deployments.</P><!--TOC section Key Features-->
|
||||
Joeri</I></FONT></BLOCKQUOTE><P><TT>ejabberd</TT> is a <B><FONT SIZE=4><FONT COLOR="#001376">free and open source</FONT></FONT></B> instant messaging server written in <A HREF="http://www.erlang.org/">Erlang/OTP</A>.</P><P><TT>ejabberd</TT> is <B><FONT SIZE=4><FONT COLOR="#001376">cross-platform</FONT></FONT></B>, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</P><P><TT>ejabberd</TT> is designed to be a <B><FONT SIZE=4><FONT COLOR="#001376">rock-solid and feature rich</FONT></FONT></B> XMPP server.</P><P><TT>ejabberd</TT> is suitable for small deployments, whether they need to be <B><FONT SIZE=4><FONT COLOR="#001376">scalable</FONT></FONT></B> or not, as well as extremely big deployments.</P><!--TOC section Key Features-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc1"></A>Key Features</H2><!--SEC END --><P>
|
||||
<A NAME="keyfeatures"></A>
|
||||
</P><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>Erlang seems to be tailor-made for writing stable, robust servers. —
|
||||
@@ -70,13 +70,13 @@ Peter Saint-André, Executive Director of the Jabber Software Foundation</I>
|
||||
<B><FONT SIZE=4><FONT COLOR="#001376">Cross-platform:</FONT></FONT></B> <TT>ejabberd</TT> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.</LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Distributed:</FONT></FONT></B> You can run <TT>ejabberd</TT> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.</LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Fault-tolerant:</FONT></FONT></B> You can deploy an <TT>ejabberd</TT> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced ‘on the fly’.</LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Administrator Friendly:</FONT></FONT></B> <TT>ejabberd</TT> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Comprehensive documentation.
|
||||
</LI><LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </LI><LI CLASS="li-itemize">Web interface for administration tasks.
|
||||
</LI><LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </LI><LI CLASS="li-itemize">Web Administration.
|
||||
</LI><LI CLASS="li-itemize">Shared Roster Groups.
|
||||
</LI><LI CLASS="li-itemize">Command line administration tool. </LI><LI CLASS="li-itemize">Can integrate with existing authentication mechanisms.
|
||||
</LI><LI CLASS="li-itemize">Capability to send announce messages.
|
||||
</LI></UL></LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Internationalized:</FONT></FONT></B> <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Translated in 17 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
|
||||
Translated to 25 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
|
||||
</LI></UL></LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Open Standards:</FONT></FONT></B> <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Fully XMPP compliant.
|
||||
@@ -97,31 +97,31 @@ Load only the modules you want.
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
SASL and STARTTLS for c2s and s2s connections.
|
||||
</LI><LI CLASS="li-itemize">STARTTLS and Dialback s2s connections.
|
||||
</LI><LI CLASS="li-itemize">Web interface accessible via HTTPS secure access.
|
||||
</LI><LI CLASS="li-itemize">Web Admin accessible via HTTPS secure access.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Databases
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Native MySQL support.
|
||||
Internal database for fast deployment (Mnesia).
|
||||
</LI><LI CLASS="li-itemize">Native MySQL support.
|
||||
</LI><LI CLASS="li-itemize">Native PostgreSQL support.
|
||||
</LI><LI CLASS="li-itemize">Mnesia.
|
||||
</LI><LI CLASS="li-itemize">ODBC data storage support.
|
||||
</LI><LI CLASS="li-itemize">Microsoft SQL Server support. </LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Authentication
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
PAM, LDAP and ODBC. </LI><LI CLASS="li-itemize">External Authentication script.
|
||||
</LI><LI CLASS="li-itemize">Internal Authentication.
|
||||
Internal Authentication.
|
||||
</LI><LI CLASS="li-itemize">PAM, LDAP and ODBC. </LI><LI CLASS="li-itemize">External Authentication script.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Others
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Compressing XML streams with Stream Compression (<A HREF="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</A>).
|
||||
</LI><LI CLASS="li-itemize">Interface with networks such as AIM, ICQ and MSN.
|
||||
Support for virtual hosting.
|
||||
</LI><LI CLASS="li-itemize">Compressing XML streams with Stream Compression (<A HREF="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</A>).
|
||||
</LI><LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</A>).
|
||||
</LI><LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component.
|
||||
</LI><LI CLASS="li-itemize">Support for virtual hosting.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> service.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with support for clustering and HTML logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</A>.
|
||||
</LI><LI CLASS="li-itemize">Support for web clients: <A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> and <A HREF="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</A> services.
|
||||
</LI><LI CLASS="li-itemize">IRC transport.
|
||||
</LI><LI CLASS="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
|
||||
</LI></UL>
|
||||
</LI></UL><!--CUT END -->
|
||||
<!--HTMLFOOT-->
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
%% Fancy header
|
||||
\fancyhf{}
|
||||
\pagestyle{fancy}
|
||||
\rhead{\textcolor{ejblue}{The Expandable Jabber Daemon.}}
|
||||
\rhead{\textcolor{ejblue}{The Expandable Jabber/XMPP Daemon.}}
|
||||
\renewcommand{\headrule}{{\color{ejblue}%
|
||||
\hrule width\headwidth height\headrulewidth \vskip-\headrulewidth}}
|
||||
\lhead{\setlength{\unitlength}{-6mm}
|
||||
@@ -133,4 +133,4 @@
|
||||
% "What I find interesting is that *no* XMPP servers truly provide clustering. This includes all the commercial
|
||||
% servers. The one partial exception appears to be ejabberd, which can cluster certain data such as sessions,
|
||||
% but not all services such as MUC."
|
||||
% * try it today: links to migration tutorials
|
||||
% * try it today: links to migration tutorials
|
||||
|
||||
@@ -8,7 +8,7 @@ Joeri}
|
||||
|
||||
%ejabberd is a free and open source instant messaging server written in Erlang. ejabberd is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication (Jabber/XMPP).
|
||||
|
||||
\ejabberd{} is a \marking{free and open source} instant messaging server written in \footahref{http://www.erlang.org/}{Erlang}.
|
||||
\ejabberd{} is a \marking{free and open source} instant messaging server written in \footahref{http://www.erlang.org/}{Erlang/OTP}.
|
||||
|
||||
\ejabberd{} is \marking{cross-platform}, distributed, fault-tolerant, and based on open standards to achieve real-time communication.
|
||||
|
||||
@@ -59,7 +59,7 @@ Peter Saint-Andr\'e, Executive Director of the Jabber Software Foundation}
|
||||
\begin{itemize}
|
||||
\item Comprehensive documentation.
|
||||
\item Straightforward installers for Linux, Mac OS X, and Windows. %%\improved{}
|
||||
\item Web interface for administration tasks.
|
||||
\item Web Administration.
|
||||
\item Shared Roster Groups.
|
||||
\item Command line administration tool. %%\improved{}
|
||||
\item Can integrate with existing authentication mechanisms.
|
||||
@@ -68,7 +68,7 @@ Peter Saint-Andr\'e, Executive Director of the Jabber Software Foundation}
|
||||
|
||||
\item \marking{Internationalized:} \ejabberd{} leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
|
||||
\begin{itemize}
|
||||
\item Translated in 17 languages. %%\improved{}
|
||||
\item Translated to 25 languages. %%\improved{}
|
||||
\item Support for \footahref{http://www.ietf.org/rfc/rfc3490.txt}{IDNA}.
|
||||
\end{itemize}
|
||||
|
||||
@@ -101,7 +101,7 @@ Moreover, \ejabberd{} comes with a wide range of other state-of-the-art features
|
||||
\begin{itemize}
|
||||
\item SASL and STARTTLS for c2s and s2s connections.
|
||||
\item STARTTLS and Dialback s2s connections.
|
||||
\item Web interface accessible via HTTPS secure access.
|
||||
\item Web Admin accessible via HTTPS secure access.
|
||||
\end{itemize}
|
||||
\item Databases
|
||||
\begin{itemize}
|
||||
@@ -125,7 +125,7 @@ Moreover, \ejabberd{} comes with a wide range of other state-of-the-art features
|
||||
\item IPv6 support both for c2s and s2s connections.
|
||||
\item \txepref{0045}{Multi-User Chat} module with support for clustering and HTML logging. %%\improved{}
|
||||
\item Users Directory based on users vCards.
|
||||
\item \txepref{0060}{Publish-Subscribe} component with support for \txepref{00163}{Personal Eventing via Pubsub}.
|
||||
\item \txepref{0060}{Publish-Subscribe} component with support for \txepref{0163}{Personal Eventing via Pubsub}.
|
||||
\item Support for web clients: \txepref{0025}{HTTP Polling} and \txepref{0206}{HTTP Binding (BOSH)} services.
|
||||
\item IRC transport.
|
||||
\item Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
|
||||
|
||||
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 21 KiB |
@@ -1,17 +1,18 @@
|
||||
Release Notes
|
||||
ejabberd 2.0.0 beta 1
|
||||
24 december 2007
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.0.0
|
||||
21 February 2008
|
||||
|
||||
ejabberd 2.0.0 is a major new version for ejabberd adding plenty of
|
||||
new features, performance and scalability improvements and
|
||||
architectural changes.
|
||||
|
||||
ejabberd 2.0.0 includes near that 200 improvements over ejabberd
|
||||
ejabberd 2.0.0 includes more than 200 improvements over ejabberd
|
||||
1.1.x. A complete list of changes can be retrieved from:
|
||||
http://redir.process-one.net/ejabberd-2.0.0
|
||||
|
||||
The new code can be downloaded from ejabberd download page:
|
||||
http://www.process-one.net/en/projects/ejabberd/
|
||||
The new code can be downloaded from ejabberd downloads page:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
Recent changes include:
|
||||
@@ -61,21 +62,21 @@
|
||||
- Support for LDAP servers pool.
|
||||
- Simplified use of virtual hosting with LDAP with domain substitution
|
||||
in config.
|
||||
- Ability to match on several userid attibutes.
|
||||
- Ability to match on several userid attributes.
|
||||
|
||||
|
||||
* Multi-user chat
|
||||
* Multi-User Chat
|
||||
|
||||
- Clustering and load balancing support.
|
||||
- Ability to define default room configuration in ejabberd config file.
|
||||
- Many anti abuse features have been added:
|
||||
. New ACL to limit the creation of persistent room to autorized users.
|
||||
. New ACL to limit the creation of persistent room to authorized users.
|
||||
. Ability to define the maximum number of users per room.
|
||||
. Limitation of the rate of message and presence packets.
|
||||
. Limitation of the maximum number of room a user can join at the same time.
|
||||
|
||||
|
||||
* File transfer
|
||||
* File Transfer
|
||||
|
||||
- XEP-0065 - Proxy65 file transfer proxy. The proxy can run in
|
||||
cluster mode.
|
||||
@@ -87,14 +88,14 @@
|
||||
- External Authentication protocol is now fully documented.
|
||||
|
||||
|
||||
* Web client support
|
||||
* Web Client Support
|
||||
|
||||
- XEP-0124 - BOSH support: BOSH (Bidirectional-streams Over
|
||||
Synchronous HTTP) was formerly known as "HTTP binding". It provides
|
||||
an efficient alternative to HTTP polling for scalable Web based chat
|
||||
solutions.
|
||||
- HTTP module can now serve static documents (with
|
||||
mod_http_fileserver). It is needed for high-performance Web2.0 chat
|
||||
mod_http_fileserver). It is needed for high-performance Web 2.0 chat
|
||||
/ IM application. System administrators can now avoid using a proxy
|
||||
(like Apache) that handles much less simultaneous than ejabberd HTTP
|
||||
module.
|
||||
@@ -102,23 +103,24 @@
|
||||
(bandwidth, packet size).
|
||||
|
||||
|
||||
* System administration
|
||||
* System Administration
|
||||
|
||||
- XEP-0133 - Service administration support. System administrators can
|
||||
now performs lot of ejabberd related admin tasks from their XMPP
|
||||
now perform lot of ejabberd related admin tasks from their XMPP
|
||||
client, through adhoc commands.
|
||||
- Dynamic log levels: Improved logging with more log levels. You can
|
||||
now change the loglevel at run time. No performance penality is
|
||||
involved when
|
||||
- Better command-line tool, with more options available.
|
||||
now change the loglevel at run time. No performance penalty is
|
||||
involved when less verbose levels are used.
|
||||
- The ejabberdctl command-line administration script now can start
|
||||
and stop ejabberd. It also includes other useful options.
|
||||
|
||||
|
||||
* Localization
|
||||
|
||||
- ejabberd is now available in 22 languages: Catalan, Chinese, Czech,
|
||||
Dutch, French, Galicia, German, Italian, Japanese, Polish,
|
||||
Portuguese, Portuguese (Brazil), Russian, Slovak, Spanish, Swedish,
|
||||
Thai, Turkish, Ukrainian, Vietnamese, Wallon.
|
||||
- ejabberd is now translated to 24 languages: Catalan, Chinese, Czech,
|
||||
Dutch, English, Esperanto, French, Galician, German, Italian, Japanese,
|
||||
Norwegian, Polish, Portuguese, Portuguese (Brazil), Russian, Slovak,
|
||||
Spanish, Swedish, Thai, Turkish, Ukrainian, Vietnamese, Walloon.
|
||||
|
||||
|
||||
* Build and Installer
|
||||
@@ -126,11 +128,11 @@
|
||||
- Many launch script improvements.
|
||||
- New translations. The binary installer is now available in Chinese,
|
||||
Dutch, English, French, German, Spanish, Russian.
|
||||
- Makefile now implements uninstall command
|
||||
- Full MacOSX compliance in Makefile
|
||||
- Makefile now implements uninstall command.
|
||||
- Full MacOSX compliance in Makefile.
|
||||
- Configure script is clever at finding libraries in unusual places.
|
||||
|
||||
|
||||
|
||||
|
||||
* Development API
|
||||
|
||||
- Several hooks have been added for module developers (most notably
|
||||
@@ -141,13 +143,66 @@
|
||||
|
||||
* Bugfixes
|
||||
|
||||
- ejabberd 2.0 also fixes numerous small bugs :) Read the full
|
||||
- ejabberd 2.0.0 also fixes numerous small bugs :) Read the full
|
||||
changelog for details.
|
||||
|
||||
|
||||
Bugs report
|
||||
|
||||
Important Note:
|
||||
|
||||
- Since this release, ejabberd requires Erlang R10B-5 or higher.
|
||||
R11B-5 is the recommended version. R12 is not yet officially
|
||||
supported, and is not recommended for production servers.
|
||||
|
||||
|
||||
|
||||
Upgrading From ejabberd 1.x:
|
||||
|
||||
- If you upgrade from a version older than 1.1.4, please check the
|
||||
Release Notes of the intermediate versions for additional
|
||||
information about database or configuration changes.
|
||||
|
||||
- The database schemas didn't change since ejabberd 1.1.4. Of course,
|
||||
you are encouraged to make a database backup of your SQL database,
|
||||
or your Mnesia spool directory before upgrading ejabberd.
|
||||
|
||||
- The ejabberdctl command line administration script is improved in
|
||||
ejabberd 2.0.0, and now it can start and stop ejabberd. If you
|
||||
already wrote your own start script for ejabberd 1.x, you can
|
||||
continue using it, or try ejabberdctl. For your convenience, the
|
||||
ejabberd Guide describes all the ejabberd and Erlang options used by
|
||||
ejabberdctl.
|
||||
|
||||
- The example ejabberd.cfg file has been reorganized, but its format
|
||||
and syntax rules are the same. So, you can continue using your
|
||||
ejabberd.cfg file from 1.x if you want. The most important changes
|
||||
are described now.
|
||||
|
||||
- The 'ssl' option is no longer available in the listening ports. For
|
||||
legacy SSL encryption use the option 'tls'. For STARTTLS encryption
|
||||
as defined in RFC 3920 XMPP-CORE use the option 'starttls'. Check
|
||||
the ejabberd Guide for more information about configuring listening
|
||||
ports.
|
||||
|
||||
- The options 'welcome_message' and 'registration_watchers' are now
|
||||
options of the module mod_register. Check in the ejabberd Guide how
|
||||
to configure that module.
|
||||
|
||||
- To enable PEP support in mod_pubsub, you need to enable it in the
|
||||
mod_pubsub configuration, and also enable the new module
|
||||
mod_caps. Check the section about mod_pubsub in the ejabberd Guide.
|
||||
|
||||
- Other new features and improvements also require changes in the
|
||||
ejabberd.cfg, like mod_http_bind, mod_http_fileserver, mod_proxy65,
|
||||
loglevel, pam_service, and watchdog_admins. Search for those words
|
||||
in the ejabberd Guide and the example ejabberd.cfg.
|
||||
|
||||
|
||||
|
||||
Bug Reports
|
||||
|
||||
You can officially report bugs on Process-one support site:
|
||||
https://support.process-one.net/
|
||||
|
||||
END
|
||||
|
||||
END
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.0.1
|
||||
20 May 2008
|
||||
|
||||
ejabberd 2.0.1 is a bugfix release for ejabberd 2.0.x branch.
|
||||
|
||||
ejabberd 2.0.1 includes 10 improvements and 32 bugfixes.
|
||||
A complete list of changes can be retrieved from:
|
||||
http://redir.process-one.net/ejabberd-2.0.1
|
||||
|
||||
The new code can be downloaded from ejabberd download page:
|
||||
http://www.process-one.net/en/projects/ejabberd/
|
||||
|
||||
|
||||
Recent changes include:
|
||||
|
||||
- Erlang R12 support.
|
||||
- Better LDAP handling.
|
||||
- PubSub bugfixes.
|
||||
- Documentation improvements.
|
||||
- inband registration limitation per IP.
|
||||
- s2s improvements.
|
||||
|
||||
Bugs report
|
||||
|
||||
You can officially report bugs on Process-one support site:
|
||||
http://support.process-one.net/
|
||||
|
||||
END
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.0.2
|
||||
28 August 2008
|
||||
|
||||
ejabberd 2.0.2 is the second bug fix release for ejabberd 2.0.x branch.
|
||||
|
||||
ejabberd 2.0.2 includes many bugfixes and a few improvements.
|
||||
A complete list of changes can be retrieved from:
|
||||
http://redir.process-one.net/ejabberd-2.0.2
|
||||
|
||||
The new code can be downloaded from ejabberd download page:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
Recent changes include:
|
||||
|
||||
- Anti-abuse feature: client blacklist support by IP.
|
||||
- Guide: new section Securing ejabberd; improved usability.
|
||||
- LDAP filter optimisation: ability to filter user in ejabberd and not LDAP.
|
||||
- MUC improvements: room options to restrict visitors; broadcast reasons.
|
||||
- Privacy rules: fix MySQL storage.
|
||||
- Pub/Sub and PEP: many improvements in implementation and protocol compliance.
|
||||
- Proxy65: send valid SOCKS5 reply (removed support for Psi < 0.10).
|
||||
- Web server embedded: better support for HTTPS.
|
||||
- Binary installers: SMP on Windows; don't remove config when uninstalling.
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
|
||||
END
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.0.3
|
||||
14 January 2009
|
||||
|
||||
ejabberd 2.0.3 is the third bugfix release for ejabberd 2.0.x branch.
|
||||
|
||||
ejabberd 2.0.3 includes several bugfixes and a few improvements.
|
||||
A complete list of changes can be retrieved from:
|
||||
http://redir.process-one.net/ejabberd-2.0.3
|
||||
|
||||
The new code can be downloaded from ejabberd download page:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
Recent changes include:
|
||||
|
||||
- Do not ask certificate for client (c2s)
|
||||
- Check digest-uri in SASL digest authentication
|
||||
- Use send timeout to avoid locking on gen_tcp:send
|
||||
- Fix ejabberd reconnection to database
|
||||
- HTTP-Bind: handle wrong order of packets
|
||||
- MUC: Improve traffic regulation management
|
||||
- PubSub: Several bugfixes and improvements for best coverage of XEP-0060 v1.12
|
||||
- Shared Roster Groups: push immediately membership changes
|
||||
- Rotate also sasl.log on "reopen-log" command
|
||||
- Binary Windows installer: better detect "Error running Post Install Script"
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
|
||||
END
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.0.4
|
||||
|
||||
ejabberd 2.0.4 is the fourth bugfix release for ejabberd 2.0.x branch.
|
||||
|
||||
ejabberd 2.0.4 includes several bugfixes.
|
||||
A detailed list of changes can be retrieved from:
|
||||
http://redir.process-one.net/ejabberd-2.0.4
|
||||
|
||||
The new code can be downloaded from ejabberd download page:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The changes are:
|
||||
|
||||
- Ensure ID attribute in roster push is unique
|
||||
- Authentication: Fix Anonymous auth when enabled with broken ODBC
|
||||
- Authentication: Unquote correctly backslash in DIGEST-MD5 SASL responses
|
||||
- Authentication: Cancel presence subscriptions on account deletion
|
||||
- LDAP: Close a connection on tcp_error
|
||||
- LDAP: Implemented queue for pending queries
|
||||
- LDAP: On failure of LDAP connection, waiting is done on pending queue
|
||||
- MUC: Owner of a password protected room must also provide the password
|
||||
- MUC: Prevent XSS in MUC logs by linkifying only a few known protocols
|
||||
- Privacy rules: Items are now processed in the specified order
|
||||
- Privacy rules: Fix to correctly block subscription requests
|
||||
- Proxy65: If ip option is not defined, take an IP address of a local hostname
|
||||
- PubSub: Add roster subscription handling; send PEP events to all resources
|
||||
- PubSub: Allow node creation without configure item
|
||||
- PubSub: Requesting items on a node which exists, but empty returns an error
|
||||
- PEP: Fix sending notifications to other domains and s2s
|
||||
- S2S: Fix problem with encrypted connection to Gtalk and recent Openfire
|
||||
- S2S: Workaround to get DNS SRV lookup to work on Windows machine
|
||||
- Shared Roster Groups: Fix to not resend authorization request
|
||||
- WebAdmin: Fix encryption problem for ejabberd_http after timeout
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
|
||||
END
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.0.5
|
||||
|
||||
ejabberd 2.0.5 is the fifth bugfix release in ejabberd 2.0.x branch.
|
||||
|
||||
ejabberd 2.0.5 includes three bugfixes.
|
||||
More details of those fixes can be retrieved from:
|
||||
http://redir.process-one.net/ejabberd-2.0.5
|
||||
|
||||
The new code can be downloaded from ejabberd download page:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The changes are:
|
||||
|
||||
- Fix two problems introduced in ejabberd 2.0.4: subscription request
|
||||
produced many authorization requests with some clients and
|
||||
transports; and subscription requests were not stored for later
|
||||
delivery when receiver was offline.
|
||||
|
||||
- Fix warning in expat_erl.c about implicit declaration of x_fix_buff
|
||||
|
||||
- HTTP-Bind (BOSH): Fix a missing stream:error in the returned
|
||||
remote-stream-error stanza
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
|
||||
END
|
||||
@@ -0,0 +1,281 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.0
|
||||
|
||||
ejabberd 2.1.0 is a major new version for ejabberd adding many
|
||||
new features, performance and scalability improvements.
|
||||
|
||||
ejabberd 2.1.0 includes many new features, improvements and bug fixes.
|
||||
A complete list of changes can be retrieved from:
|
||||
http://redir.process-one.net/ejabberd-2.1.0
|
||||
|
||||
The new code can be downloaded from ejabberd download page:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
New features and improvements:
|
||||
|
||||
* Anti-abuse
|
||||
- Captcha support (XEP-0158). The example script uses ImageMagick.
|
||||
- New option: registration_timeout to limit registrations by time
|
||||
- Use send timeout to avoid locking on gen_tcp:send
|
||||
- mod_ip_blacklist: client blacklist support by IP
|
||||
|
||||
* API
|
||||
- ejabberd_http provides Host, Port, Headers and Protocol in HTTP requests
|
||||
- Export function to create MUC room
|
||||
- New events: s2s_send_packet and s2s_receive_packet
|
||||
- New event: webadmin_user_parse_query when POST in web admin user page
|
||||
- Support distributed hooks over the cluster
|
||||
|
||||
* Authentification
|
||||
- Extauth responses: log strange responses and add timeout
|
||||
|
||||
* Binary Installer
|
||||
- Includes exmpp library to support import/export XML files
|
||||
|
||||
* Caps
|
||||
- Remove useless caps tables entries
|
||||
- mod_caps must handle correctly external contacts with several resources
|
||||
- Complain if mod_caps disabled and mod_pubsub has PEP plugin enabled
|
||||
|
||||
* Clustering and Architecture
|
||||
|
||||
* Configuration
|
||||
- Added option access_max_user_messages for mod_offline
|
||||
- Added option backlog for ejabberd_listener to increase TCP backlog
|
||||
- Added option define_macro and use_macro
|
||||
- Added option include_config_file to include additional configuration files
|
||||
- Added option max_fsm_queue
|
||||
- Added option outgoing_s2s_options to define IP address families and timeout
|
||||
- Added option registration_timeout to ejabberd.cfg.example
|
||||
- Added option s2s_dns_options to define DNS timeout and retries
|
||||
- Added option ERL_OPTIONS to ejabberdctl.cfg
|
||||
- Added option FIREWALL_WINDOW to ejabberdctl.cfg
|
||||
- Added option EJABBERD_PID_PATH to ejabberdctl.cfg
|
||||
- Deleted option user_max_messages of mod_offline
|
||||
- Check certfiles are readable on server start and listener start
|
||||
- Config file management mix file reading and sanity check
|
||||
- Include example PAM configuration file: ejabberd.pam
|
||||
- New ejabberd listener: ejabberd_stun
|
||||
- Support to bind the same port to multiple interfaces
|
||||
- New syntax to specify the IP address and IPv6 in listeners
|
||||
configuration. The old options {ip,{1,2,3,4}} and inet6 are
|
||||
supported even if they aren't documented.
|
||||
- New syntax to specify the network protocol: tcp or udp
|
||||
- Report error at startup if a listener module isn't available
|
||||
- Only listen in a port when actually ready to serve requests
|
||||
- In default config, only local accounts can create rooms and PubSub nodes
|
||||
|
||||
* Core architecture
|
||||
- More verbose error reporting for xml:element_to_string
|
||||
- Deliver messages when first presence is Invisible
|
||||
- Better log message when config file is not found
|
||||
- Include original timestamp on delayed presences
|
||||
|
||||
* Crypto
|
||||
- Do not ask certificate for client (c2s)
|
||||
- SSL code remove from ejabberd in favor of TLS
|
||||
- Support Zlib compression after STARTTLS encryption
|
||||
- tls v1 client hello
|
||||
|
||||
* Documentation
|
||||
- Document possible default MUC room options
|
||||
- Document service_check_from in the Guide
|
||||
- Document s2s_default_policy and s2s_host in the Guide
|
||||
- new command and guide instructions to change node name in a Mnesia database
|
||||
|
||||
* ejabberd commands
|
||||
- ejabberd commands: separate command definition and calling interface
|
||||
- access_commands restricts who can execute what commands and arguments
|
||||
- ejabberdctl script now displays help and categorization of commands
|
||||
|
||||
* HTTP Binding and HTTP Polling
|
||||
- HTTP-Bind: module optimization and clean-up
|
||||
- HTTP-Bind: allow configuration of max_inactivity timeout
|
||||
- HTTP-Poll: turn session timeout into a config file parameter
|
||||
|
||||
* Jingle
|
||||
- STUN server that facilitates the client-to-client negotiation process
|
||||
|
||||
* LDAP
|
||||
- Faster reconnection to LDAP servers
|
||||
- LDAP filter optimisation: Add ability to filter user in ejabberd and not LDAP
|
||||
- LDAP differentiates failed auth and unavailable auth service
|
||||
- Improve LDAP logging
|
||||
- LDAPS support using TLS.
|
||||
|
||||
* Localization
|
||||
- Use Gettext PO for translators, export to ejabberd MSG
|
||||
- Support translation files for additional projects
|
||||
- Most translations are updated to latest code
|
||||
- New translation to Greek language
|
||||
|
||||
* Multi-User Chat (MUC)
|
||||
- Allow admins to send messages to rooms
|
||||
- Allow to store room description
|
||||
- Captcha support in MUC: the admin of a room can configure it to
|
||||
require participants to fill a captcha to join the room.
|
||||
- Limit number of characters in Room ID, Name and Description
|
||||
- Prevent unvoiced occupants from changing nick
|
||||
- Support Result Set Management (XEP-0059) for listing rooms
|
||||
- Support for decline of invitation to MUC room
|
||||
- mod_muc_log options: plaintext format; filename with only room name
|
||||
|
||||
* Performance
|
||||
- Run roster_get_jid_info only if privacy list has subscription or group item
|
||||
- Significant PubSub performance improvements
|
||||
|
||||
* Publish-Subscribe
|
||||
- Add nodetree filtering/authorization
|
||||
- Add subscription option support for collection nodes
|
||||
- Allow Multiple Subscriptions
|
||||
- Check option of the nodetree instead of checking configuration
|
||||
- Implement whitelist authorize and roster access model
|
||||
- Implicit item deletion is not notified when deleting node
|
||||
- Make PubSub x-data configuration form handles list value
|
||||
- Make default node name convention XEP-compatible, document usage of hierarchy
|
||||
- Node names are used verbatim, without separating by slash, unless a
|
||||
node plugin uses its own separator
|
||||
- Send authorization update event (XEP-0060, 8.6)
|
||||
- Support of collection node subscription options
|
||||
- Support ODBC storage. Experimental, needs more testing.
|
||||
|
||||
* Relational databases:
|
||||
- Added MSSQL 2000 and 2005
|
||||
- Privacy rules storage in MySQL
|
||||
- Implement reliable ODBC transaction nesting
|
||||
|
||||
* Source Package
|
||||
- Default installation directories changed. Please see the upgrade notes below.
|
||||
- Allow more environment variable overrides in ejabberdctl
|
||||
- ChangeLog is not edited manually anymore; it's generated automatically.
|
||||
- Install the ejabberd Guide
|
||||
- Install the ejabberd include files
|
||||
- New option for the 'configure' script: --enable-user which installs
|
||||
ejabberd granting permission to manage it to a regular system user;
|
||||
no need to use root account to.
|
||||
- Only try to install epam if pam was enabled in configure script
|
||||
- Spool, config and log dirs: owner writes, group reads, others do nothing.
|
||||
- Provides an example ejabberd.init file
|
||||
|
||||
* S2S
|
||||
- Option to define s2s outgoing behaviour: IPv4, IPv6 and timeout
|
||||
- DNS timeout and retries, configurable with s2s_dns_options.
|
||||
|
||||
* Shared rosters
|
||||
- When a member is added/removed to group, send roster upgrade to group members
|
||||
|
||||
* Users management
|
||||
- When account is deleted, cancel presence subscription for all roster items
|
||||
|
||||
* XEP Support
|
||||
- Added XEP-0059 Result Set Management (for listing rooms)
|
||||
- Added XEP-0082 Date Time
|
||||
- Added XEP-0085 Chat State Notifications
|
||||
- Added XEP-0157 Contact Addresses for XMPP Services
|
||||
- Added XEP-0158 CAPTCHA Forms (in MUC rooms)
|
||||
- Added STUN server, for XEP-0176: Jingle ICE-UDP Transport Method
|
||||
- Added XEP-0199 XMPP Ping
|
||||
- Added XEP-0202 Entity Time
|
||||
- Added XEP-0203 Delayed Delivery
|
||||
- Added XEP-0227 Portable Import/Export Format for XMPP-IM Servers
|
||||
- Added XEP-0237 Roster Versioning
|
||||
|
||||
* Web Admin
|
||||
- Display the connection method of user sessions
|
||||
- Cross link of ejabberd users in the list of users and rosters
|
||||
- Improved the browsing menu: don't disappear when browsing a host or node
|
||||
- Include Last-Modified HTTP header in responses to allow caching
|
||||
- Make some Input areas multiline: options of listening ports and modules
|
||||
- Support PUT and DELETE methods in ejabberd_http
|
||||
- WebAdmin serves Guide and links to related sections
|
||||
|
||||
* Web plugins
|
||||
- mod_http_fileserver: new option directory_indices, and improve logging
|
||||
|
||||
|
||||
Important Notes:
|
||||
|
||||
- ejabberd 2.1.0 requires Erlang R10B-9 or higher.
|
||||
R12B-5 is the recommended version. Support for R13B is experimental.
|
||||
|
||||
|
||||
Upgrading From ejabberd 1.x.x:
|
||||
|
||||
- Check the Release Notes of the intermediate versions for additional
|
||||
information about database or configuration changes.
|
||||
|
||||
|
||||
Upgrading From ejabberd 2.0.x:
|
||||
|
||||
- The database schemas have three changes since ejabberd 2.0.x.
|
||||
Check the database creation SQL files and update your database.
|
||||
1) New table roster_version to support roster versioning.
|
||||
2) Six new tables for optional pubsub ODBC storage.
|
||||
3) Some tables in the MySQL database have a new created_at column.
|
||||
|
||||
- As usual, it is recommended to backup the Mnesia spool directory and
|
||||
your SQL database (if used) before upgrading ejabberd.
|
||||
|
||||
- Between ejabberd 2.0.0 and 2.0.5, mod_pubsub used "default" as the
|
||||
default node plugin. But in 2.1.0 this is renamed to "hometree".
|
||||
You have to edit your ejabberd config file and replace those names.
|
||||
If you used ejabberd 2.0.5 or older, the database will be updated
|
||||
automatically. But if you were using ejabberd from SVN, you must
|
||||
manually run ejabberdctl with the command: rename_default_nodeplugin.
|
||||
Running this command on already updated database does nothing.
|
||||
|
||||
- The listener options 'ip' and 'inet6' are not documented anymore
|
||||
but they are supported and you can continue using them.
|
||||
There is a new syntax to define IP address and IP version.
|
||||
As usual, check the ejabberd Guide for more information.
|
||||
|
||||
- The log file sasl.log is now called erlang.log
|
||||
|
||||
- ejabberdctl commands now have _ characters instead of -.
|
||||
For backwards compatibility, it is still supported -.
|
||||
|
||||
- mod_offline has a new option: access_max_user_messages.
|
||||
The old option user_max_messages is no longer supported.
|
||||
|
||||
- If you upgrade from ejabberd trunk SVN, you must execute this:
|
||||
$ ejabberdctl rename_default_nodeplugin
|
||||
|
||||
- Default installation directories changed a bit:
|
||||
* The Mnesia spool files that were previously stored in
|
||||
/var/lib/ejabberd/db/NODENAME/*
|
||||
are now stored in
|
||||
/var/lib/ejabberd/*
|
||||
* The directories
|
||||
/var/lib/ejabberd/ebin
|
||||
/var/lib/ejabberd/priv
|
||||
and their content is now installed as
|
||||
/lib/ejabberd/ebin
|
||||
/lib/ejabberd/priv
|
||||
* There is a new directory with Erlang header files:
|
||||
/lib/ejabberd/include
|
||||
* There is a new directory for ejabberd documentation,
|
||||
which includes the Admin Guide and the release notes::
|
||||
/share/doc/ejabberd
|
||||
|
||||
- How to upgrade from previous version to ejabberd 2.1.0:
|
||||
1. Stop the old instance of ejabberd.
|
||||
2. Run 'make install' of new ejabberd 2.1.0 to create the new directories.
|
||||
3. Copy the content of your old directory:
|
||||
/var/lib/ejabberd/db/NODENAME/
|
||||
to the new location:
|
||||
/var/lib/ejabberd/
|
||||
so you will have the files like this:
|
||||
/var/lib/ejabberd/acl.DCD ...
|
||||
4. You can backup the content of those directories and delete them:
|
||||
/var/lib/ejabberd/ebin
|
||||
/var/lib/ejabberd/priv
|
||||
/var/lib/ejabberd/db
|
||||
5. Now try to start your new ejabberd 2.1.0.
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.1
|
||||
|
||||
ejabberd 2.1.1 is the first bugfix release in ejabberd 2.1.x branch.
|
||||
|
||||
ejabberd 2.1.1 includes several important bugfixes.
|
||||
More details of those fixes can be retrieved from:
|
||||
http://redir.process-one.net/ejabberd-2.1.1
|
||||
|
||||
The new code can be downloaded from ejabberd download page:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The changes are:
|
||||
|
||||
* Core
|
||||
- Call ejabberd_router:route/3 instead of sending a message
|
||||
- Can't connect if starttls_required and zlib are set
|
||||
- Routes vCard request to the occupant full JID, but should to bare JID
|
||||
- S2S: fix allow_host/2 on subdomains. added hook s2s_allow_host
|
||||
|
||||
* MUC
|
||||
- Support converting one-to-one chat to MUC
|
||||
- Add support for serving a Unique Room Name
|
||||
|
||||
* Publish Subscribe
|
||||
- Receive same last published PEP items at reconnect if several resources online
|
||||
- Typo in mod_pubsub_odbc breaks Service Discovery and more
|
||||
|
||||
* Web
|
||||
- Fix memory and port leak when TLS is enabled in HTTP
|
||||
- WebAdmin doesn't report correct last activity with postgresql backend
|
||||
- Option to define custom HTTP headers in mod_http_fileserver
|
||||
- Show informative webpage when browsing the HTTP-Poll page
|
||||
|
||||
* Other
|
||||
- Change captcha.sh to not depend on bash
|
||||
- Generate main XML file also when exporting only a vhost
|
||||
- Fix last newline in ejabberdctl result
|
||||
- Guide: fix -setcookie, mod_pubsub_odbc host, content_types
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.2
|
||||
|
||||
ejabberd 2.1.2 is the second bugfix release in ejabberd 2.1.x branch.
|
||||
|
||||
ejabberd 2.1.2 includes several bugfixes.
|
||||
More details of those fixes can be retrieved from:
|
||||
http://redir.process-one.net/ejabberd-2.1.2
|
||||
|
||||
The new code can be downloaded from ejabberd download page:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The major changes are:
|
||||
|
||||
* Core
|
||||
- Close sessions that were half connected
|
||||
- Fix SASL PLAIN authentication message for RFC4616 compliance
|
||||
- Fix support for old Erlang/OTP R10 and R11
|
||||
- Return proper error (not 'conflict') when register is forbidden by ACL
|
||||
- When ejabberd stops, send stream close to clients
|
||||
|
||||
* ejabberdctl
|
||||
- Check for EGID in ejabberdctl command
|
||||
- Command to stop ejabberd informing users, with grace period
|
||||
- If there's a problem in config file, display config lines and stop node
|
||||
|
||||
* MUC
|
||||
- Kick occupants with reason when room is stopped due to MUC shutdown
|
||||
- Write in room log when a room is created, destroyed, started, stopped
|
||||
|
||||
* PubSub and PEP
|
||||
- Don't call gen_server on internal event (improves performance and scalability)
|
||||
- Fix duplicate SHIM header in Pubsub message
|
||||
- Notification messages of Pubsub node config change contained a SHIM header
|
||||
- SubID SHIM header missing in Pubsub message with multiple
|
||||
subscriptions on the same node
|
||||
- PEP: last published item not sent from unavailable users when the
|
||||
subscription is implicit (XEP-0115)
|
||||
- pep_mapping not working due to Node type mismatch
|
||||
|
||||
* WebAdmin
|
||||
- If big offline message queue, show only subset on WebAdmin
|
||||
- Support in user list page of WebAdmin when mod_offline is disabled
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -0,0 +1,91 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.3
|
||||
|
||||
ejabberd 2.1.3 is the third release in ejabberd 2.1.x branch.
|
||||
|
||||
ejabberd 2.1.3 includes many bugfixes, and some improvements.
|
||||
More details of those fixes can be retrieved from:
|
||||
http://redir.process-one.net/ejabberd-2.1.3
|
||||
|
||||
The new code can be downloaded from ejabberd download page:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
This is the full list of changes:
|
||||
|
||||
* Client connections
|
||||
- Avoid 'invalid' value in iq record
|
||||
- Avoid resending stream:error stanzas on terminate (EJAB-1180)
|
||||
- Close also legacy sessions that were half connected (EJAB-1165)
|
||||
- iq_query_info/1 now returns 'invalid' if XMLNS is invalid
|
||||
- New ejabberd_c2s option support: max_fsm_queue
|
||||
- Rewrite mnesia counter functions to use dirty_update_counter (EJAB-1177)
|
||||
- Run user_receive_packet also when sending offline messages (EJAB-1193)
|
||||
- Use p1_fsm behaviour in c2s FSM (EJAB-1173)
|
||||
|
||||
* Clustering
|
||||
- Fix cluster race condition in route read
|
||||
- New command to set master Mnesia node
|
||||
- Use mnesia:async_dirty when cleaning table from failed node
|
||||
|
||||
* Documentation
|
||||
- Add quotes in documentation of some erl arguments (EJAB-1191)
|
||||
- Add option access_from (EJAB-1187)
|
||||
- Add option max_fsm_queue (EJAB-1185)
|
||||
- Fix documentation installation, no need for executable permission (EJAB-1170)
|
||||
- Fix typo in EJABBERD_BIN_PATH (EJAB-891)
|
||||
- Fix typos in example config comments (EJAB-1192)
|
||||
|
||||
* ejabberdctl
|
||||
- Support concurrent connections with bound connection names
|
||||
- Add support for Jot in ctl and TTY in debug
|
||||
- Support help command names with old - characters
|
||||
- Fix to really use the variable ERL_PROCESSES
|
||||
|
||||
* Erlang compatibility
|
||||
- Don't call queue:filter/2 to keep compatibility with older Erlang versions
|
||||
- Use alternative of file:read_line/1 to not require R13B02
|
||||
|
||||
* HTTP
|
||||
- Add new debugging hook to the http receiving process
|
||||
- Allow a request_handler to serve a file in root of HTTP
|
||||
|
||||
* HTTP-Bind (BOSH)
|
||||
- Cross-domain HTTP-Bind support (EJAB-1168)
|
||||
- Hibernate http-bind process after handling a request
|
||||
- Reduce verbosity of HTTP Binding log messages
|
||||
|
||||
* LDAP
|
||||
- Document ldap_dn_filter, fetch only needed attributes in search (EJAB-1204)
|
||||
- Use "%u" pattern as default for ldap_uids (EJAB-1203)
|
||||
|
||||
* Localization
|
||||
- Fix German translation (EJAB-1195)
|
||||
- Fix Russian translation
|
||||
|
||||
* ODBC
|
||||
- Fix MSSQL support, which was broken (EJAB-1201)
|
||||
- Improved SQL reconnect behaviour
|
||||
|
||||
* Pubsub, PEP and Caps
|
||||
- Add extended stanza addressing 'replyto' on PEP (EJAB-1198)
|
||||
- Add pubsub#purge_offline (EJAB-1186)
|
||||
- Fix pubsub#title option (EJAB-1190)
|
||||
- Fix remove_user for node subscriptions (EJAB-1172)
|
||||
- Optimizations in mod_caps
|
||||
|
||||
* Other
|
||||
- mod_register: Add new acl access_from, default is to deny
|
||||
- mod_sic: new module for the experimental XEP-0279 Server IP Check (EJAB-1205)
|
||||
- PIEFXIS: Catch errors when exporting to PIEFXIS file (EJAB-1178)
|
||||
- Proxy65: new option "hostname" (EJAB-838)
|
||||
- Roster: Fix resending authorization problem
|
||||
- Shared Roster Groups: get contacts nickname from vcard (EJAB-114)
|
||||
- S2S: Improved s2s connections clean up (EJAB-1202)
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -0,0 +1,80 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.4
|
||||
|
||||
ejabberd 2.1.4 is the fourth release in ejabberd 2.1.x branch,
|
||||
and includes many small bugfixes and improvements.
|
||||
|
||||
Read more details about the changes in:
|
||||
http://redir.process-one.net/ejabberd-2.1.4
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
This is the full list of changes:
|
||||
|
||||
* Authentication
|
||||
- Extauth: Optionally cache extauth users in mnesia (EJAB-641)
|
||||
- LDAP: Allow inband password change (EJAB-199)
|
||||
- LDAP: Extensible match support (EJAB-722)
|
||||
- LDAP: New option ldap_tls_verify is added (EJAB-1229)
|
||||
- PAM: New option pam_userinfotype to provide username or JID (EJAB-652)
|
||||
|
||||
* HTTP
|
||||
- Add xml default content type
|
||||
- Don't show HTTP request in logs, because reveals password (EJAB-1231)
|
||||
- Move HTTP session timeout log from warning level to info
|
||||
- New Access rule webadmin_view for read-only
|
||||
|
||||
* HTTP-Bind (BOSH)
|
||||
- Change max inactivity from 30 to 120 seconds
|
||||
- Export functions to facilitate prebinding methods
|
||||
- Use dirty_delete when removing the session
|
||||
- Remove an unneeded delay of 100 milliseconds
|
||||
|
||||
* Pubsub, PEP and Caps
|
||||
- Enforce pubsub#presence_based_delivery (EJAB-1221)
|
||||
- Enforce pubsub#show_values subscription option (EJAB-1096)
|
||||
- Fix error code when unsubscribing from a non-existent node
|
||||
- Fix to send node notifications (EJAB-1225)
|
||||
- Full support for XEP-0115 v1.5 (EJAB-1223)(EJAB-1189)
|
||||
- Make last_item_cache feature to be cluster aware
|
||||
- Prevent orphaned pubsub node (EJAB-1233)
|
||||
- Send created node notifications
|
||||
|
||||
* Other
|
||||
- Bounce messages when closing c2s session
|
||||
- Bugfixes when handling Service Discovery to contacts (EJAB-1207)
|
||||
- Compilation of ejabberd_debug.erl is now optional
|
||||
- Don't send error stanza as reply to error stanza (EJAB-930)
|
||||
- Don't store blocked messages in offline queue
|
||||
- Reduce verbosity of log when captcha_cmd is checked but not configured
|
||||
- Use a standard method to get a random seed (EJAB-1229)
|
||||
- Commands: new update_list and update to update modified modules (EJAB-1237)
|
||||
- Localization: Updated most translations
|
||||
- MUC: Refactor code to reduce calls to get_affiliation and get_role
|
||||
- ODBC: Add created_at column also to PostgreSQL schema
|
||||
- Vcard: Automatic vcard avatar addition in presence
|
||||
|
||||
|
||||
Upgrading From previous ejabberd releases:
|
||||
|
||||
- If you use PostgreSQL, maybe you want to add the column created_at
|
||||
to several tables. This is only a suggestion; ejabberd doesn't use
|
||||
that column. Add it to your existing database executing those SQL
|
||||
statements:
|
||||
|
||||
ALTER TABLE users ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
ALTER TABLE rosterusers ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
ALTER TABLE spool ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
ALTER TABLE vcard ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
ALTER TABLE privacy_list ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
ALTER TABLE privacy_storage ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.5
|
||||
|
||||
ejabberd 2.1.5 is the fifth release in ejabberd 2.1.x branch,
|
||||
and includes several minor bugfixes and a few improvements.
|
||||
|
||||
Read more details about the changes in:
|
||||
http://redir.process-one.net/ejabberd-2.1.4
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
This is the full list of changes:
|
||||
|
||||
* Authentication
|
||||
- Extauth: Support parallel script running (EJAB-1280)
|
||||
- mod_register: Return Registered element when account exists
|
||||
|
||||
* ejabberdctl
|
||||
- Fix print of command result that contains ~
|
||||
- Fix problem when FIREWALL_WINDOW options for erl kernel were used
|
||||
- Fix typo in update_list command (EJAB-1237)
|
||||
- Some systems delete the lock dir; in such case don't use flock at all
|
||||
- The command Update now returns meaningful message and exit-status (EJAB-1237)
|
||||
|
||||
* HTTP-Bind (BOSH)
|
||||
- Don't say v1.2 in the Bind HTTP page
|
||||
- New optional BOSH connection attribute process-delay (EJAB-1257)
|
||||
|
||||
* MUC
|
||||
- Document the mod_muc option captcha_protected
|
||||
- Now admins are able to see private rooms in disco (EJAB-1269)
|
||||
- Show some more room options in the log file
|
||||
|
||||
* ODBC
|
||||
- Correct handling of SQL boolean types (EJAB-1275)
|
||||
- Discard too old queued requests (the caller has already got a timeout)
|
||||
- Fixes wrong SQL escaping when --enable-full-xml is set
|
||||
- Use ets insead of asking supervisor in ejabberd_odbc_sup:get_pids/1
|
||||
|
||||
* Pubsub, PEP and Caps
|
||||
- Enforce disco features results (EJAB-1033, EJAB-1228, EJAB-1238)
|
||||
- Support all the hash functions required by Caps XEP-0115
|
||||
|
||||
* Requirements
|
||||
- Fixed support for Erlang R12; which doesn't support: true andalso ok
|
||||
- Support OTP R14A by using public_key library instead of old ssl (EJAB-953)
|
||||
- Requirement of OpenSSL increased from 0.9.6 to 0.9.8
|
||||
- OpenSSL is now required, not optional
|
||||
|
||||
* Other
|
||||
- Don't ask for client certificate when using tls (EJAB-1267)
|
||||
- Fix typo in --enable-transient_supervisors
|
||||
- Fix privacy check when serving local Last (EJAB-1271)
|
||||
- Inform client that SSL session caching is disabled
|
||||
- New configure option: --enable-nif
|
||||
- Use driver allocator in C files for reflecting memory in erlang:memory(system)
|
||||
- Debug: New p1_prof compiled with: make debugtools=true
|
||||
- Debug: Added functions to collect stats about queues, memory, reductions etc
|
||||
- HTTP: Log error if request has ambiguous Host header (EJAB-1261)
|
||||
- Logs: When logging s2s out connection attempt or success, log if TLS is used
|
||||
- Shared Rosters: When account is deleted, delete also member of stored rosters
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -0,0 +1,67 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.6
|
||||
|
||||
ejabberd 2.1.6 is the sixth release in ejabberd 2.1.x branch,
|
||||
and includes a lot of bugfixes and improvements.
|
||||
|
||||
Read more details about the changes in:
|
||||
http://redir.process-one.net/ejabberd-2.1.6
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
Some of the changes are:
|
||||
|
||||
* Account register
|
||||
- mod_register: New ip_access option restricts which IPs can register (EJAB-915)
|
||||
- mod_register: Default configuration allows registrations only from localhost
|
||||
- mod_register: New password_strength for entropy check (EJAB-1326)
|
||||
- mod_register: New captcha_protected option to require CAPTCHA (EJAB-1262)
|
||||
- mod_register_web: New module, with CAPTCHA support (EJAB-471)
|
||||
|
||||
* BOSH
|
||||
- Don't loop when there is nothing after a stream start (EJAB-1358)
|
||||
- Fix http-bind supervisor to support multiple vhosts (EJAB-1321)
|
||||
- Support to restart http-bind (EJAB-1318)
|
||||
- Support for X-Forwarded-For HTTP header (EJAB-1356)
|
||||
|
||||
* Erlang/OTP compatibility
|
||||
- R11: Fix detection of Erlang R11 and older (EJAB-1287)
|
||||
- R12B5: Fix compatibility in ejabberd_http_bind.erl (EJAB-1343)
|
||||
- R14A: Make xml.c correctly compile (EJAB-1288)
|
||||
- R14A, R14B: Disapprove the use of R14A and R14B due to the rwlock bug
|
||||
- R14B: Use pg2 from this version in systems with older ones (EJAB-1349)
|
||||
|
||||
* Listeners
|
||||
- Bind listener ports early and start accepting connections later
|
||||
- Fix a leak of ejabberd_receiver processes
|
||||
- Speed up ejabberd_s2s:is_service/2, allow_host/2 (EJAB-1319)
|
||||
- S2S: New option to require encryption (EJAB-495)
|
||||
- S2S: New option to reject connection if untrusted certificate (EJAB-464)
|
||||
- S2S: Include From attribute in the stream header of outgoing S2S connections
|
||||
- S2S: Fix domain_certfile tlsopts modifications for S2S connections (EJAB-1086)
|
||||
|
||||
* Pubsub/PEP/Caps
|
||||
- Fix pubsub cross domain eventing (EJAB-1340)
|
||||
- Use one_queue IQ discipline by default
|
||||
- Implement lifetime for broken hashes
|
||||
- New CAPS processing
|
||||
|
||||
* ODBC
|
||||
- Increase maximum restart strategy to handle some SQL timeouts
|
||||
- Support PostgreSQL 9.0 (EJAB-1359)
|
||||
- Use MEDIUMTEXT type for vcard avatars in MySQL schema (EJAB-1252)
|
||||
|
||||
* Miscellanea:
|
||||
- mod_shared_roster_ldap: New Shared Roster Groups using LDAP information
|
||||
- mod_privacy: Fix to allow block by group and subscription again
|
||||
- Support timezone West of UTC (EJAB-1301)
|
||||
- Support to change loglevel per module at runtime (EJAB-225)
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -0,0 +1,97 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.7
|
||||
|
||||
ejabberd 2.1.7 is the eighth release in ejabberd 2.1.x branch,
|
||||
and includes a lot of bugfixes and improvements.
|
||||
|
||||
Read more details about the changes in:
|
||||
http://redir.process-one.net/ejabberd-2.1.7
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The changes are:
|
||||
|
||||
* BOSH and Web
|
||||
- Clarify error message when BOSH query is sent to non-running module
|
||||
- Keep the order of stanzas when BOSH sends several (EJAB-1374)
|
||||
- Show configuration for HTTPS http_bind
|
||||
- Support as read-only HTTP method not only GET, also HEAD
|
||||
- The responses to HEAD must have empty Body
|
||||
|
||||
* CAPTCHA
|
||||
- If the port number isn't listener, then specify the protocol (EJAB-1418)
|
||||
- New CAPTCHA limit
|
||||
- New CAPTCHA whitelist support
|
||||
- Only check system at startup if option is enabled
|
||||
- Provide HTTPS URL in CAPTCHA form when listener has 'tls' option (EJAB-1406)
|
||||
- Show captcha_limit option in the example config
|
||||
- Support more captcha_host value formats (EJAB-1418)
|
||||
- Throw error when captcha fails at server start, not later at runtime
|
||||
- captcha_host must have the port number to get protocol (EJAB-1418)
|
||||
|
||||
* Core ejabberd
|
||||
- Disable all entity expansions (EJAB-1451)
|
||||
- Do not accept XML with undefined prefixes (EJAB-680)
|
||||
- Make jlib:ip_to_list safe to use
|
||||
- Make sure 'closed' event is correctly processed on every state
|
||||
- New route_iq/5 accepting Timeout (EJAB-1398)
|
||||
- Take into consideration internal queue length when sorting processes queues
|
||||
- Use route instead of send_element to go through standard workflow
|
||||
|
||||
* Erlang/OTP compatibility
|
||||
- Remove Type and Spec, backport list comprehensions, so R12B-5 can compile
|
||||
- Tweak pg2_backport.erl to work with Erlang older than R13A (EJAB-1349)
|
||||
|
||||
* ODBC
|
||||
- Don't let presence-in privacy rule block a presence subscription (EJAB-255)
|
||||
- Escape user input in mod_privacy_odbc (EJAB-1442)
|
||||
- Try to improve support for roster_version in MSSQL (EJAB-1437)
|
||||
|
||||
* Pubsub/PEP/Caps
|
||||
- Apply filtered notification to PEP last items (EJAB-1456)
|
||||
- Fix empty pubsub payload check
|
||||
- Owner can delete any items from its own node (EJAB-1445)
|
||||
- Pubsub node maxitem forced to 0 if non persistent node (EJAB-1434)
|
||||
- Reorganize the push_item function, and handle version not_found (EJAB-1420)
|
||||
|
||||
* Scripts
|
||||
- ejabberd.init: Several fixes and improvements
|
||||
- ejabberdctl: Escape output from ctlexec() to erl script (EJAB-1399)
|
||||
- ejabberdctl: Fix bashism and mimic master branch (EJAB-1404)
|
||||
- ejabberdctl: Fix space between INET_DIST_INTERFACE (EJAB-1416)
|
||||
- ejabberdctl: New DIST_USE_INTERFACE restricts IP of erlang listen (EJAB-1404)
|
||||
- ejabberdctl: New ERL_EPMD_ADDRESS that works since Erlang/OTP R14B03
|
||||
- extauth: Fix delayed response of timeout was reused for next login (EJAB-1385)
|
||||
- extauth: Forward old messages to newly spawned extauth process (EJAB-1385)
|
||||
- extauth: If script crashes, ejabberd should restart it (EJAB-1428)
|
||||
|
||||
* XEP support
|
||||
- mod_blocking: New XEP-0191 Simple Communications Blocking (EJAB-695)
|
||||
- No need to inform that XEP-0237 is optional; clarified in XEP version 1.2
|
||||
|
||||
* Miscellanea:
|
||||
- If a module start fails during server start, stop erlang (EJAB-1446)
|
||||
- New Indonesian translation (EJAB-1407)
|
||||
- LDAP: Note that ejabberd works with CGP LDAP server
|
||||
- S2S: Handle Tigase's unexpected version=1.0 (EJAB-1379)
|
||||
- mod_irc: Send presence unavailable to the departing occupant (EJAB-1417)
|
||||
- mod_last: Allow user to query his own Last activity
|
||||
- mod_muc: Do not decrease MUC admin's role/affiliation
|
||||
- mod_muc: Send jid attribute when occupant is banned (EJAB-1432)
|
||||
- mod_offline: Change c2s state before offline messages resending
|
||||
- mod_ping: Use iqdisc no_queue by default (EJAB-1435)
|
||||
- mod_pres_counter: Prevent subscription flood (EJAB-1388)
|
||||
- mod_register Access now also controls account unregistrations
|
||||
- mod_register: Clarify more the expected content of welcome_message option
|
||||
- mod_shared_roster: Fix support for anonymous accounts in @all@ (EJAB-1264)
|
||||
- mod_shared_roster: New @online@ directive (EJAB-1391)
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.8
|
||||
|
||||
ejabberd 2.1.8 is the ninth release in ejabberd 2.1.x branch,
|
||||
and includes a PubSub regression bugfix.
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The change is:
|
||||
|
||||
- Fix issue on PubSub preventing publication of items (EJAB-1457)
|
||||
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -1,2 +1,2 @@
|
||||
% ejabberd version (automatically generated).
|
||||
\newcommand{\version}{2.0.0-beta1}
|
||||
\newcommand{\version}{2.1.7}
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 13 KiB |
@@ -42,6 +42,22 @@ while(1)
|
||||
# password is null. Return 1 if the user $user\@$domain exitst.
|
||||
$result = 1;
|
||||
},last SWITCH;
|
||||
|
||||
$op eq 'tryregister' and do
|
||||
{
|
||||
$result = 1;
|
||||
},last SWITCH;
|
||||
|
||||
$op eq 'removeuser' and do
|
||||
{
|
||||
# password is null. Return 1 if the user $user\@$domain exitst.
|
||||
$result = 1;
|
||||
},last SWITCH;
|
||||
|
||||
$op eq 'removeuser3' and do
|
||||
{
|
||||
$result = 1;
|
||||
},last SWITCH;
|
||||
};
|
||||
my $out = pack "nn",2,$result ? 1 : 0;
|
||||
syswrite STDOUT,$out;
|
||||
|
||||
@@ -13,63 +13,144 @@ EXPAT_LIBS = @EXPAT_LIBS@
|
||||
ERLANG_LIBS = @ERLANG_LIBS@
|
||||
|
||||
ASN_FLAGS = -bber_bin +der +compact_bit_string +optimize +noobj
|
||||
# make debug=true to compile Erlang module with debug informations.
|
||||
ifdef debug
|
||||
ERLC_FLAGS+=+debug_info
|
||||
|
||||
INSTALLUSER=@INSTALLUSER@
|
||||
# if no user was enabled, don't set privileges or ownership
|
||||
ifeq ($(INSTALLUSER),)
|
||||
O_USER=
|
||||
G_USER=
|
||||
CHOWN_COMMAND=echo
|
||||
CHOWN_OUTPUT=/dev/null
|
||||
INIT_USER=root
|
||||
else
|
||||
O_USER=-o $(INSTALLUSER)
|
||||
G_USER=-g $(INSTALLUSER)
|
||||
CHOWN_COMMAND=chown
|
||||
CHOWN_OUTPUT=&1
|
||||
INIT_USER=$(INSTALLUSER)
|
||||
endif
|
||||
|
||||
ifdef ejabberd_debug
|
||||
ERLC_FLAGS+=-Dejabberd_debug
|
||||
EFLAGS += @ERLANG_SSLVER@ -pa .
|
||||
ERLANG_CFLAGS += @ERLANG_SSLVER@
|
||||
|
||||
# make debug=true to compile Erlang module with debug informations.
|
||||
ifdef debug
|
||||
EFLAGS+=+debug_info +export_all
|
||||
endif
|
||||
|
||||
DEBUGTOOLS = p1_prof.erl
|
||||
ifdef debugtools
|
||||
SOURCES+=$(DEBUGTOOLS)
|
||||
endif
|
||||
|
||||
ifeq (@hipe@, true)
|
||||
ERLC_FLAGS+=+native
|
||||
EFLAGS+=+native
|
||||
endif
|
||||
|
||||
ifeq (@roster_gateway_workaround@, true)
|
||||
ERLC_FLAGS+=-DROSTER_GATEWAY_WORKAROUND
|
||||
EFLAGS+=-DROSTER_GATEWAY_WORKAROUND
|
||||
endif
|
||||
|
||||
ifeq (@full_xml@, true)
|
||||
ERLC_FLAGS+=-DFULL_XML_SUPPORT
|
||||
EFLAGS+=-DFULL_XML_SUPPORT
|
||||
endif
|
||||
|
||||
ifeq (@nif@, true)
|
||||
EFLAGS+=-DNIF
|
||||
ERLSHLIBS=xml.so
|
||||
endif
|
||||
|
||||
ifeq (@transient_supervisors@, false)
|
||||
ERLC_FLAGS+=-DNO_TRANSIENT_SUPERVISORS
|
||||
EFLAGS+=-DNO_TRANSIENT_SUPERVISORS
|
||||
endif
|
||||
|
||||
ifeq (@md2@, true)
|
||||
EFLAGS+=-DHAVE_MD2
|
||||
ERLANG_CFLAGS += -DHAVE_MD2
|
||||
endif
|
||||
|
||||
INSTALL_EPAM=
|
||||
ifeq (@pam@, pam)
|
||||
INSTALL_EPAM=install -m 750 $(O_USER) epam $(PBINDIR)
|
||||
endif
|
||||
|
||||
prefix = @prefix@
|
||||
exec_prefix = @exec_prefix@
|
||||
|
||||
SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @mod_proxy65@ @eldap@ @pam@ @web@ stringprep @tls@ @odbc@ @ejabberd_zlib@
|
||||
ERLSHLIBS = expat_erl.so
|
||||
SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @mod_proxy65@ @eldap@ @pam@ @web@ stringprep stun @tls@ @odbc@ @ejabberd_zlib@
|
||||
ERLSHLIBS += expat_erl.so
|
||||
ERLBEHAVS = cyrsasl.erl gen_mod.erl p1_fsm.erl
|
||||
SOURCES = $(ERLBEHAVS) $(wildcard *.erl)
|
||||
SOURCES_ALL = $(wildcard *.erl)
|
||||
SOURCES_MISC = $(ERLBEHAVS) $(DEBUGTOOLS)
|
||||
SOURCES += $(filter-out $(SOURCES_MISC),$(SOURCES_ALL))
|
||||
ERLBEHAVBEAMS = $(ERLBEHAVS:.erl=.beam)
|
||||
BEAMS = $(SOURCES:.erl=.beam)
|
||||
|
||||
DESTDIR =
|
||||
DESTDIR =
|
||||
|
||||
EJABBERDDIR = $(DESTDIR)@prefix@/var/lib/ejabberd
|
||||
# /etc/ejabberd/
|
||||
ETCDIR = $(DESTDIR)@sysconfdir@/ejabberd
|
||||
|
||||
# /sbin/
|
||||
SBINDIR = $(DESTDIR)@sbindir@
|
||||
|
||||
# /lib/ejabberd/
|
||||
EJABBERDDIR = $(DESTDIR)@libdir@/ejabberd
|
||||
|
||||
# /share/doc/ejabberd
|
||||
PACKAGE_TARNAME = @PACKAGE_TARNAME@
|
||||
datarootdir = @datarootdir@
|
||||
DOCDIR = $(DESTDIR)@docdir@
|
||||
|
||||
# /usr/lib/ejabberd/ebin/
|
||||
BEAMDIR = $(EJABBERDDIR)/ebin
|
||||
PRIVDIR = $(EJABBERDDIR)/priv
|
||||
SODIR = $(PRIVDIR)/lib
|
||||
MSGSDIR = $(PRIVDIR)/msgs
|
||||
LOGDIR = $(DESTDIR)@prefix@/var/log/ejabberd
|
||||
ETCDIR = $(DESTDIR)@prefix@/etc/ejabberd
|
||||
SBINDIR = $(DESTDIR)@prefix@/sbin
|
||||
|
||||
ifeq ($(shell uname),Darwin)
|
||||
DYNAMIC_LIB_CFLAGS = -fPIC -bundle -flat_namespace -undefined suppress
|
||||
else
|
||||
# /usr/lib/ejabberd/include/
|
||||
INCLUDEDIR = $(EJABBERDDIR)/include
|
||||
|
||||
# /usr/lib/ejabberd/priv/
|
||||
PRIVDIR = $(EJABBERDDIR)/priv
|
||||
|
||||
# /usr/lib/ejabberd/priv/bin
|
||||
PBINDIR = $(PRIVDIR)/bin
|
||||
|
||||
# /usr/lib/ejabberd/priv/lib
|
||||
SODIR = $(PRIVDIR)/lib
|
||||
|
||||
# /usr/lib/ejabberd/priv/msgs
|
||||
MSGSDIR = $(PRIVDIR)/msgs
|
||||
|
||||
# /var/lib/ejabberd/
|
||||
SPOOLDIR = $(DESTDIR)@localstatedir@/lib/ejabberd
|
||||
|
||||
# /var/lock/ejabberdctl
|
||||
CTLLOCKDIR = $(DESTDIR)@localstatedir@/lock/ejabberdctl
|
||||
|
||||
# /var/lib/ejabberd/.erlang.cookie
|
||||
COOKIEFILE = $(SPOOLDIR)/.erlang.cookie
|
||||
|
||||
# /var/log/ejabberd/
|
||||
LOGDIR = $(DESTDIR)@localstatedir@/log/ejabberd
|
||||
|
||||
# Assume Linux-style dynamic library flags
|
||||
DYNAMIC_LIB_CFLAGS = -fpic -shared
|
||||
ifeq ($(shell uname),Darwin)
|
||||
DYNAMIC_LIB_CFLAGS = -fPIC -bundle -flat_namespace -undefined suppress
|
||||
endif
|
||||
ifeq ($(shell uname),SunOs)
|
||||
DYNAMIC_LIB_CFLAGS = -KPIC -G -z text
|
||||
endif
|
||||
|
||||
all: $(ERLSHLIBS) compile-beam all-recursive
|
||||
|
||||
compile-beam: XmppAddr.hrl $(BEAMS)
|
||||
compile-beam: XmppAddr.hrl $(ERLBEHAVBEAMS) $(BEAMS)
|
||||
|
||||
$(BEAMS): $(ERLBEHAVBEAMS)
|
||||
|
||||
all-recursive: $(ERLBEHAVBEAMS)
|
||||
|
||||
%.beam: %.erl
|
||||
@ERLC@ -W $(ERLC_FLAGS) $<
|
||||
@ERLC@ -W $(EFLAGS) $<
|
||||
|
||||
|
||||
all-recursive install-recursive uninstall-recursive \
|
||||
@@ -84,58 +165,147 @@ mostlyclean-recursive maintainer-clean-recursive:
|
||||
|
||||
%.hrl: %.asn1
|
||||
@ERLC@ $(ASN_FLAGS) $<
|
||||
@ERLC@ -W $(ERLC_FLAGS) $*.erl
|
||||
@ERLC@ -W $(EFLAGS) $*.erl
|
||||
|
||||
$(ERLSHLIBS): %.so: %.c
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) \
|
||||
$(subst ../,,$(subst .so,.c,$@)) \
|
||||
$(EXPAT_LIBS) $(EXPAT_CFLAGS) \
|
||||
$(ERLANG_LIBS) $(ERLANG_CFLAGS) \
|
||||
-o $@ $(DYNAMIC_LIB_CFLAGS)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) \
|
||||
$(subst ../,,$(subst .so,.c,$@)) \
|
||||
$(EXPAT_LIBS) \
|
||||
$(EXPAT_CFLAGS) \
|
||||
$(ERLANG_LIBS) \
|
||||
$(ERLANG_CFLAGS) \
|
||||
-o $@ \
|
||||
$(DYNAMIC_LIB_CFLAGS)
|
||||
|
||||
translations:
|
||||
../contrib/extract_translations/prepare-translation.sh -updateall
|
||||
|
||||
install: all
|
||||
#
|
||||
# Configuration files
|
||||
install -d -m 750 $(G_USER) $(ETCDIR)
|
||||
[ -f $(ETCDIR)/ejabberd.cfg ] \
|
||||
&& install -b -m 640 $(G_USER) ejabberd.cfg.example $(ETCDIR)/ejabberd.cfg-new \
|
||||
|| install -b -m 640 $(G_USER) ejabberd.cfg.example $(ETCDIR)/ejabberd.cfg
|
||||
sed -e "s*@rootdir@*@prefix@*" \
|
||||
-e "s*@installuser@*@INSTALLUSER@*" \
|
||||
-e "s*@LIBDIR@*@libdir@*" \
|
||||
-e "s*@SYSCONFDIR@*@sysconfdir@*" \
|
||||
-e "s*@LOCALSTATEDIR@*@localstatedir@*" \
|
||||
-e "s*@DOCDIR@*@docdir@*" \
|
||||
-e "s*@erl@*@ERL@*" ejabberdctl.template \
|
||||
> ejabberdctl.example
|
||||
[ -f $(ETCDIR)/ejabberdctl.cfg ] \
|
||||
&& install -b -m 640 $(G_USER) ejabberdctl.cfg.example $(ETCDIR)/ejabberdctl.cfg-new \
|
||||
|| install -b -m 640 $(G_USER) ejabberdctl.cfg.example $(ETCDIR)/ejabberdctl.cfg
|
||||
install -b -m 644 $(G_USER) inetrc $(ETCDIR)/inetrc
|
||||
#
|
||||
# Administration script
|
||||
[ -d $(SBINDIR) ] || install -d -m 755 $(SBINDIR)
|
||||
install -m 550 $(G_USER) ejabberdctl.example $(SBINDIR)/ejabberdctl
|
||||
#
|
||||
# Init script
|
||||
sed -e "s*@ctlscriptpath@*$(SBINDIR)*" \
|
||||
-e "s*@installuser@*$(INIT_USER)*" ejabberd.init.template \
|
||||
> ejabberd.init
|
||||
chmod 755 ejabberd.init
|
||||
#
|
||||
# Binary Erlang files
|
||||
install -d $(BEAMDIR)
|
||||
install -m 644 *.app $(BEAMDIR)
|
||||
install -m 644 *.beam $(BEAMDIR)
|
||||
rm -f $(BEAMDIR)/configure.beam
|
||||
install -m 644 *.app $(BEAMDIR)
|
||||
#
|
||||
# ejabberd header files
|
||||
install -d $(INCLUDEDIR)
|
||||
install -m 644 *.hrl $(INCLUDEDIR)
|
||||
install -d $(INCLUDEDIR)/eldap/
|
||||
install -m 644 eldap/*.hrl $(INCLUDEDIR)/eldap/
|
||||
install -d $(INCLUDEDIR)/mod_muc/
|
||||
install -m 644 mod_muc/*.hrl $(INCLUDEDIR)/mod_muc/
|
||||
install -d $(INCLUDEDIR)/mod_proxy65/
|
||||
install -m 644 mod_proxy65/*.hrl $(INCLUDEDIR)/mod_proxy65/
|
||||
install -d $(INCLUDEDIR)/mod_pubsub/
|
||||
install -m 644 mod_pubsub/*.hrl $(INCLUDEDIR)/mod_pubsub/
|
||||
install -d $(INCLUDEDIR)/web/
|
||||
install -m 644 web/*.hrl $(INCLUDEDIR)/web/
|
||||
#
|
||||
# Binary C programs
|
||||
install -d $(PBINDIR)
|
||||
install -m 750 $(O_USER) ../tools/captcha.sh $(PBINDIR)
|
||||
$(INSTALL_EPAM)
|
||||
#
|
||||
# Binary system libraries
|
||||
install -d $(SODIR)
|
||||
install -m 644 *.so $(SODIR)
|
||||
-install -m 750 epam $(SODIR)
|
||||
#
|
||||
# Translated strings
|
||||
install -d $(MSGSDIR)
|
||||
install -m 644 msgs/*.msg $(MSGSDIR)
|
||||
install -d $(ETCDIR)
|
||||
[ -f $(ETCDIR)/ejabberd.cfg ] && install -b -m 644 ejabberd.cfg.example $(ETCDIR)/ejabberd.cfg-new || install -b -m 644 ejabberd.cfg.example $(ETCDIR)/ejabberd.cfg
|
||||
sed -e "s*@rootdir@*@prefix@*" ejabberdctl.template > ejabberdctl.example
|
||||
[ -f $(ETCDIR)/ejabberdctl.cfg ] && install -b -m 644 ejabberdctl.cfg.example $(ETCDIR)/ejabberdctl.cfg-new || install -b -m 644 ejabberdctl.cfg.example $(ETCDIR)/ejabberdctl.cfg
|
||||
install -b -m 644 ejabberd.inetrc $(ETCDIR)/ejabberd.inetrc
|
||||
install -d $(SBINDIR)
|
||||
install -m 755 ejabberdctl.example $(SBINDIR)/ejabberdctl
|
||||
install -d $(LOGDIR)
|
||||
#
|
||||
# Spool directory
|
||||
install -d -m 750 $(O_USER) $(SPOOLDIR)
|
||||
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(SPOOLDIR) >$(CHOWN_OUTPUT)
|
||||
chmod -R 750 $(SPOOLDIR)
|
||||
[ ! -f $(COOKIEFILE) ] || { $(CHOWN_COMMAND) @INSTALLUSER@ $(COOKIEFILE) >$(CHOWN_OUTPUT) ; chmod 400 $(COOKIEFILE) ; }
|
||||
#
|
||||
# ejabberdctl lock directory
|
||||
install -d -m 750 $(O_USER) $(CTLLOCKDIR)
|
||||
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(CTLLOCKDIR) >$(CHOWN_OUTPUT)
|
||||
chmod -R 750 $(CTLLOCKDIR)
|
||||
#
|
||||
# Log directory
|
||||
install -d -m 750 $(O_USER) $(LOGDIR)
|
||||
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(LOGDIR) >$(CHOWN_OUTPUT)
|
||||
chmod -R 750 $(LOGDIR)
|
||||
#
|
||||
# Documentation
|
||||
install -d $(DOCDIR)
|
||||
install -m 644 ../doc/dev.html $(DOCDIR)
|
||||
install -m 644 ../doc/guide.html $(DOCDIR)
|
||||
install -m 644 ../doc/*.png $(DOCDIR)
|
||||
install -m 644 ../doc/*.txt $(DOCDIR)
|
||||
[ -f ../doc/guide.pdf ] \
|
||||
&& install -m 644 ../doc/guide.pdf $(DOCDIR) \
|
||||
|| echo "No ../doc/guide.pdf was built"
|
||||
install -m 644 ../COPYING $(DOCDIR)
|
||||
|
||||
uninstall: uninstall-binary
|
||||
|
||||
uninstall-binary:
|
||||
rm -rf $(BEAMDIR)
|
||||
rm -rf $(SODIR)
|
||||
rm -rf $(MSGSDIR)
|
||||
rm -rf $(PRIVDIR)
|
||||
rm -rf $(SBINDIR)/ejabberdctl
|
||||
rm -f $(SBINDIR)/ejabberdctl
|
||||
rm -fr $(DOCDIR)
|
||||
rm -f $(BEAMDIR)/*.beam
|
||||
rm -f $(BEAMDIR)/*.app
|
||||
rm -fr $(BEAMDIR)
|
||||
rm -f $(INCLUDEDIR)/*.hrl
|
||||
rm -fr $(INCLUDEDIR)
|
||||
rm -fr $(PBINDIR)
|
||||
rm -f $(SODIR)/*.so
|
||||
rm -fr $(SODIR)
|
||||
rm -f $(MSGSDIR)/*.msgs
|
||||
rm -fr $(MSGSDIR)
|
||||
rm -fr $(PRIVDIR)
|
||||
rm -fr $(EJABBERDDIR)
|
||||
|
||||
uninstall-all: uninstall-binary
|
||||
rm -rf $(ETCDIR)
|
||||
rm -rf $(LOGDIR)
|
||||
rm -rf $(EJABBERDDIR)
|
||||
rm -rf $(SPOOLDIR)
|
||||
rm -rf $(CTLLOCKDIR)
|
||||
rm -rf $(LOGDIR)
|
||||
|
||||
clean: clean-recursive clean-local
|
||||
|
||||
clean-local:
|
||||
rm -f *.beam $(ERLSHLIBS) epam
|
||||
rm -f *.beam $(ERLSHLIBS) epam ejabberdctl.example
|
||||
rm -f XmppAddr.asn1db XmppAddr.erl XmppAddr.hrl
|
||||
|
||||
distclean: distclean-recursive clean-local
|
||||
rm -f config.status
|
||||
rm -f config.log
|
||||
rm -f Makefile
|
||||
[ ! -f ../ChangeLog ] || rm -f ../ChangeLog
|
||||
|
||||
TAGS:
|
||||
etags *.erl
|
||||
@@ -144,4 +314,3 @@ Makefile: Makefile.in
|
||||
|
||||
dialyzer: $(BEAMS)
|
||||
@dialyzer -c .
|
||||
|
||||
|
||||
@@ -66,6 +66,9 @@ release : build release_clean
|
||||
copy stringprep\*.erl $(SRC_DIR)\stringprep
|
||||
copy stringprep\*.c $(SRC_DIR)\stringprep
|
||||
copy stringprep\*.tcl $(SRC_DIR)\stringprep
|
||||
mkdir $(SRC_DIR)\stun
|
||||
copy stun\*.erl $(SRC_DIR)\stun
|
||||
copy stun\*.hrl $(SRC_DIR)\stun
|
||||
mkdir $(SRC_DIR)\tls
|
||||
copy tls\*.erl $(SRC_DIR)\tls
|
||||
copy tls\*.c $(SRC_DIR)\tls
|
||||
@@ -101,6 +104,8 @@ all-recursive :
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\stringprep
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\stun
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\tls
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\ejabberd_zlib
|
||||
@@ -142,6 +147,8 @@ clean-recursive :
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\stringprep
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\stun
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\tls
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\ejabberd_zlib
|
||||
@@ -166,5 +173,4 @@ $(DLL) : $(OBJECT)
|
||||
$(LD) $(LD_FLAGS) -out:$(DLL) $(OBJECT)
|
||||
|
||||
$(OBJECT) : $(SOURCE)
|
||||
$(CC) $(CC_FLAGS) -c -Fo$(OBJECT) $(SOURCE)
|
||||
|
||||
$(CC) $(CC_FLAGS) -c -Fo$(OBJECT) $(SOURCE)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
AC_DEFUN(AM_WITH_EXPAT,
|
||||
AC_DEFUN([AM_WITH_EXPAT],
|
||||
[ AC_ARG_WITH(expat,
|
||||
[ --with-expat=PREFIX prefix where EXPAT is installed])
|
||||
[AC_HELP_STRING([--with-expat=PREFIX], [prefix where EXPAT is installed])])
|
||||
|
||||
EXPAT_CFLAGS=
|
||||
EXPAT_LIBS=
|
||||
@@ -8,14 +8,14 @@ AC_DEFUN(AM_WITH_EXPAT,
|
||||
EXPAT_CFLAGS="-I$with_expat/include"
|
||||
EXPAT_LIBS="-L$with_expat/lib"
|
||||
fi
|
||||
|
||||
|
||||
AC_CHECK_LIB(expat, XML_ParserCreate,
|
||||
[ EXPAT_LIBS="$EXPAT_LIBS -lexpat"
|
||||
expat_found=yes ],
|
||||
[ expat_found=no ],
|
||||
"$EXPAT_LIBS")
|
||||
if test $expat_found = no; then
|
||||
AC_MSG_ERROR([Could not find the Expat library])
|
||||
AC_MSG_ERROR([Could not find development files of Expat library])
|
||||
fi
|
||||
expat_save_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS $EXPAT_CFLAGS"
|
||||
@@ -32,24 +32,25 @@ AC_DEFUN(AM_WITH_EXPAT,
|
||||
AC_SUBST(EXPAT_LIBS)
|
||||
])
|
||||
|
||||
AC_DEFUN(AM_WITH_ZLIB,
|
||||
AC_DEFUN([AM_WITH_ZLIB],
|
||||
[ AC_ARG_WITH(zlib,
|
||||
[ --with-zlib=PREFIX prefix where zlib is installed])
|
||||
[AC_HELP_STRING([--with-zlib=PREFIX], [prefix where zlib is installed])])
|
||||
|
||||
if test x"$ejabberd_zlib" != x; then
|
||||
ZLIB_CFLAGS=
|
||||
ZLIB_LIBS=
|
||||
if test x"$with_zlib" != x; then
|
||||
ZLIB_CFLAGS="-I$with_zlib/include"
|
||||
ZLIB_LIBS="-L$with_zlib/lib"
|
||||
fi
|
||||
|
||||
|
||||
AC_CHECK_LIB(z, gzgets,
|
||||
[ ZLIB_LIBS="$ZLIB_LIBS -lz"
|
||||
zlib_found=yes ],
|
||||
[ zlib_found=no ],
|
||||
"$ZLIB_LIBS")
|
||||
if test $zlib_found = no; then
|
||||
AC_MSG_ERROR([Could not find the zlib library])
|
||||
AC_MSG_ERROR([Could not find development files of zlib library. Install them or disable `ejabberd_zlib' with: --disable-ejabberd_zlib])
|
||||
fi
|
||||
zlib_save_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS $ZLIB_CFLAGS"
|
||||
@@ -57,33 +58,34 @@ AC_DEFUN(AM_WITH_ZLIB,
|
||||
CPPFLAGS="$CPPFLAGS $ZLIB_CFLAGS"
|
||||
AC_CHECK_HEADERS(zlib.h, , zlib_found=no)
|
||||
if test $zlib_found = no; then
|
||||
AC_MSG_ERROR([Could not find zlib.h])
|
||||
AC_MSG_ERROR([Could not find zlib.h. Install it or disable `ejabberd_zlib' with: --disable-ejabberd_zlib])
|
||||
fi
|
||||
CFLAGS="$zlib_save_CFLAGS"
|
||||
CPPFLAGS="$zlib_save_CPPFLAGS"
|
||||
|
||||
AC_SUBST(ZLIB_CFLAGS)
|
||||
AC_SUBST(ZLIB_LIBS)
|
||||
fi
|
||||
])
|
||||
|
||||
AC_DEFUN(AM_WITH_PAM,
|
||||
AC_DEFUN([AM_WITH_PAM],
|
||||
[ AC_ARG_WITH(pam,
|
||||
[ --with-pam=PREFIX prefix where PAM is installed])
|
||||
|
||||
[AC_HELP_STRING([--with-pam=PREFIX], [prefix where PAM is installed])])
|
||||
if test x"$pam" != x; then
|
||||
PAM_CFLAGS=
|
||||
PAM_LIBS=
|
||||
if test x"$with_pam" != x; then
|
||||
PAM_CFLAGS="-I$with_pam/include"
|
||||
PAM_LIBS="-L$with_pam/lib"
|
||||
fi
|
||||
|
||||
|
||||
AC_CHECK_LIB(pam, pam_start,
|
||||
[ PAM_LIBS="$PAM_LIBS -lpam"
|
||||
pam_found=yes ],
|
||||
[ pam_found=no ],
|
||||
"$PAM_LIBS")
|
||||
if test $pam_found = no; then
|
||||
AC_MSG_WARN([Could not find the PAM library])
|
||||
AC_MSG_ERROR([Could not find development files of PAM library. Install them or disable `pam' with: --disable-pam])
|
||||
fi
|
||||
pam_save_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS $PAM_CFLAGS"
|
||||
@@ -91,29 +93,30 @@ AC_DEFUN(AM_WITH_PAM,
|
||||
CPPFLAGS="$CPPFLAGS $PAM_CFLAGS"
|
||||
AC_CHECK_HEADERS(security/pam_appl.h, , pam_found=no)
|
||||
if test $pam_found = no; then
|
||||
AC_MSG_WARN([Could not find security/pam_appl.h])
|
||||
AC_MSG_ERROR([Could not find security/pam_appl.h. Install it or disable `pam' with: --disable-pam])
|
||||
fi
|
||||
CFLAGS="$pam_save_CFLAGS"
|
||||
CPPFLAGS="$pam_save_CPPFLAGS"
|
||||
|
||||
AC_SUBST(PAM_CFLAGS)
|
||||
AC_SUBST(PAM_LIBS)
|
||||
fi
|
||||
])
|
||||
|
||||
AC_DEFUN(AM_WITH_ERLANG,
|
||||
AC_DEFUN([AM_WITH_ERLANG],
|
||||
[ AC_ARG_WITH(erlang,
|
||||
[ --with-erlang=PREFIX path to erlc and erl ])
|
||||
[AC_HELP_STRING([--with-erlang=PREFIX], [path to erlc and erl])])
|
||||
|
||||
AC_PATH_TOOL(ERLC, erlc, , $with_erlang:$with_erlang/bin:$PATH)
|
||||
AC_PATH_TOOL(ERL, erl, , $with_erlang:$with_erlang/bin:$PATH)
|
||||
|
||||
|
||||
if test "z$ERLC" = "z" || test "z$ERL" = "z"; then
|
||||
AC_MSG_ERROR([erlang not found])
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
cat >>conftest.erl <<_EOF
|
||||
|
||||
|
||||
-module(conftest).
|
||||
-author('alexey@sevcom.net').
|
||||
|
||||
@@ -123,9 +126,17 @@ start() ->
|
||||
EIDirS = code:lib_dir("erl_interface") ++ "\n",
|
||||
EILibS = libpath("erl_interface") ++ "\n",
|
||||
RootDirS = code:root_dir() ++ "\n",
|
||||
file:write_file("conftest.out", list_to_binary(EIDirS ++ EILibS ++ RootDirS)),
|
||||
file:write_file("conftest.out", list_to_binary(EIDirS ++ EILibS ++ ssldef() ++ RootDirS)),
|
||||
halt().
|
||||
|
||||
ssldef() ->
|
||||
OTP = (catch erlang:system_info(otp_release)),
|
||||
if
|
||||
OTP >= "R14" -> "-DSSL40\n";
|
||||
OTP >= "R12" -> "-DSSL39\n";
|
||||
true -> "\n"
|
||||
end.
|
||||
|
||||
%% return physical architecture based on OS/Processor
|
||||
archname() ->
|
||||
ArchStr = erlang:system_info(system_architecture),
|
||||
@@ -154,45 +165,47 @@ libpath(App) ->
|
||||
%% ({error, enoent}):
|
||||
_Error -> code:lib_dir("erl_interface") ++ "/lib"
|
||||
end.
|
||||
|
||||
|
||||
_EOF
|
||||
|
||||
|
||||
if ! $ERLC conftest.erl; then
|
||||
AC_MSG_ERROR([could not compile sample program])
|
||||
fi
|
||||
|
||||
|
||||
if ! $ERL -s conftest -noshell; then
|
||||
AC_MSG_ERROR([could not run sample program])
|
||||
fi
|
||||
|
||||
|
||||
if ! test -f conftest.out; then
|
||||
AC_MSG_ERROR([erlang program was not properly executed, (conftest.out was not produced)])
|
||||
fi
|
||||
|
||||
|
||||
# First line
|
||||
ERLANG_EI_DIR=`cat conftest.out | head -n 1`
|
||||
# Second line
|
||||
ERLANG_EI_LIB=`cat conftest.out | head -n 2 | tail -n 1`
|
||||
# Third line
|
||||
ERLANG_SSLVER=`cat conftest.out | head -n 3 | tail -n 1`
|
||||
# End line
|
||||
ERLANG_DIR=`cat conftest.out | tail -n 1`
|
||||
|
||||
|
||||
ERLANG_CFLAGS="-I$ERLANG_EI_DIR/include -I$ERLANG_DIR/usr/include"
|
||||
ERLANG_LIBS="-L$ERLANG_EI_LIB -lerl_interface -lei"
|
||||
|
||||
|
||||
AC_SUBST(ERLANG_CFLAGS)
|
||||
AC_SUBST(ERLANG_LIBS)
|
||||
AC_SUBST(ERLANG_SSLVER)
|
||||
AC_SUBST(ERLC)
|
||||
AC_SUBST(ERL)
|
||||
])
|
||||
|
||||
|
||||
AC_DEFUN(AC_MOD_ENABLE,
|
||||
AC_DEFUN([AC_MOD_ENABLE],
|
||||
[
|
||||
$1=
|
||||
make_$1=
|
||||
AC_MSG_CHECKING([whether build $1])
|
||||
AC_ARG_ENABLE($1,
|
||||
[ --enable-$1 enable $1 (default: $2)],
|
||||
[AC_HELP_STRING([--enable-$1], [enable $1 (default: $2)])],
|
||||
[mr_enable_$1="$enableval"],
|
||||
[mr_enable_$1=$2])
|
||||
if test "$mr_enable_$1" = "yes"; then
|
||||
@@ -213,7 +226,7 @@ AC_DEFUN([AM_ICONV],
|
||||
dnl Some systems have iconv in libc, some have it in libiconv (OSF/1 and
|
||||
dnl those with the standalone portable GNU libiconv installed).
|
||||
AC_ARG_WITH([libiconv-prefix],
|
||||
[ --with-libiconv-prefix=PREFIX prefix where libiconv is installed], [
|
||||
[AC_HELP_STRING([--with-libiconv-prefix=PREFIX], [prefix where libiconv is installed])], [
|
||||
for dir in `echo "$withval" | tr : ' '`; do
|
||||
if test -d $dir/include; then CPPFLAGS="$CPPFLAGS -I$dir/include"; fi
|
||||
if test -d $dir/include; then CFLAGS="$CFLAGS -I$dir/include"; fi
|
||||
@@ -262,7 +275,7 @@ AC_DEFUN([AM_ICONV],
|
||||
CFLAGS="$am_save_CFLAGS")
|
||||
LIBS="$am_save_LIBS"
|
||||
fi
|
||||
|
||||
|
||||
])
|
||||
if test "$am_cv_func_iconv" = yes; then
|
||||
AC_DEFINE(HAVE_ICONV, 1, [Define if you have the iconv() function.])
|
||||
@@ -296,9 +309,9 @@ size_t iconv();
|
||||
])
|
||||
|
||||
dnl <openssl>
|
||||
AC_DEFUN(AM_WITH_OPENSSL,
|
||||
AC_DEFUN([AM_WITH_OPENSSL],
|
||||
[ AC_ARG_WITH(openssl,
|
||||
[ --with-openssl=PREFIX prefix where OPENSSL is installed ])
|
||||
[AC_HELP_STRING([--with-openssl=PREFIX], [prefix where OPENSSL is installed])])
|
||||
unset SSL_LIBS;
|
||||
unset SSL_CFLAGS;
|
||||
have_openssl=no
|
||||
@@ -328,7 +341,7 @@ if test x"$tls" != x; then
|
||||
fi
|
||||
done
|
||||
if test x${have_openssl} != xyes; then
|
||||
AC_MSG_ERROR([openssl library cannot be found. Install openssl or disable `tls' module (--disable-tls).])
|
||||
AC_MSG_ERROR([Could not find development files of OpenSSL library. Install them or disable `tls' with: --disable-tls])
|
||||
fi
|
||||
AC_SUBST(SSL_LIBS)
|
||||
AC_SUBST(SSL_CFLAGS)
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 18 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -48,12 +48,12 @@ start() ->
|
||||
ok.
|
||||
|
||||
to_record(Host, ACLName, ACLSpec) ->
|
||||
#acl{aclname = {ACLName, Host}, aclspec = ACLSpec}.
|
||||
#acl{aclname = {ACLName, Host}, aclspec = normalize_spec(ACLSpec)}.
|
||||
|
||||
add(Host, ACLName, ACLSpec) ->
|
||||
F = fun() ->
|
||||
mnesia:write(#acl{aclname = {ACLName, Host},
|
||||
aclspec = ACLSpec})
|
||||
aclspec = normalize_spec(ACLSpec)})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
@@ -75,7 +75,7 @@ add_list(Host, ACLs, Clear) ->
|
||||
aclspec = ACLSpec} ->
|
||||
mnesia:write(
|
||||
#acl{aclname = {ACLName, Host},
|
||||
aclspec = ACLSpec})
|
||||
aclspec = normalize_spec(ACLSpec)})
|
||||
end
|
||||
end, ACLs)
|
||||
end,
|
||||
@@ -86,6 +86,17 @@ add_list(Host, ACLs, Clear) ->
|
||||
false
|
||||
end.
|
||||
|
||||
normalize(A) ->
|
||||
jlib:nodeprep(A).
|
||||
normalize_spec({A, B}) ->
|
||||
{A, normalize(B)};
|
||||
normalize_spec({A, B, C}) ->
|
||||
{A, normalize(B), normalize(C)};
|
||||
normalize_spec(all) ->
|
||||
all;
|
||||
normalize_spec(none) ->
|
||||
none.
|
||||
|
||||
|
||||
|
||||
match_rule(global, Rule, JID) ->
|
||||
@@ -147,7 +158,7 @@ match_acl(ACL, JID, Host) ->
|
||||
all -> true;
|
||||
none -> false;
|
||||
_ ->
|
||||
{User, Server, _Resource} = jlib:jid_tolower(JID),
|
||||
{User, Server, Resource} = jlib:jid_tolower(JID),
|
||||
lists:any(fun(#acl{aclspec = Spec}) ->
|
||||
case Spec of
|
||||
all ->
|
||||
@@ -162,16 +173,24 @@ match_acl(ACL, JID, Host) ->
|
||||
(U == User) andalso (S == Server);
|
||||
{server, S} ->
|
||||
S == Server;
|
||||
{resource, R} ->
|
||||
R == Resource;
|
||||
{user_regexp, UR} ->
|
||||
((Host == Server) orelse
|
||||
((Host == global) andalso
|
||||
lists:member(Server, ?MYHOSTS)))
|
||||
andalso is_regexp_match(User, UR);
|
||||
{shared_group, G} ->
|
||||
mod_shared_roster:is_user_in_group({User, Server}, G, Host);
|
||||
{shared_group, G, H} ->
|
||||
mod_shared_roster:is_user_in_group({User, Server}, G, H);
|
||||
{user_regexp, UR, S} ->
|
||||
(S == Server) andalso
|
||||
is_regexp_match(User, UR);
|
||||
{server_regexp, SR} ->
|
||||
is_regexp_match(Server, SR);
|
||||
{resource_regexp, RR} ->
|
||||
is_regexp_match(Resource, RR);
|
||||
{node_regexp, UR, SR} ->
|
||||
is_regexp_match(Server, SR) andalso
|
||||
is_regexp_match(User, UR);
|
||||
@@ -186,6 +205,8 @@ match_acl(ACL, JID, Host) ->
|
||||
is_glob_match(User, UR);
|
||||
{server_glob, SR} ->
|
||||
is_glob_match(Server, SR);
|
||||
{resource_glob, RR} ->
|
||||
is_glob_match(Resource, RR);
|
||||
{node_glob, UR, SR} ->
|
||||
is_glob_match(Server, SR) andalso
|
||||
is_glob_match(User, UR);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : adhoc.erl
|
||||
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%% Purpose : Provide helper functions for ad-hoc commands (JEP-0050)
|
||||
%%% Purpose : Provide helper functions for ad-hoc commands (XEP-0050)
|
||||
%%% Created : 31 Oct 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -44,10 +44,11 @@ parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}
|
||||
Action = xml:get_tag_attr_s("action", SubEl),
|
||||
XData = find_xdata_el(SubEl),
|
||||
{xmlelement, _, _, AllEls} = SubEl,
|
||||
if XData ->
|
||||
Others = lists:delete(XData, AllEls);
|
||||
true ->
|
||||
Others = AllEls
|
||||
Others = case XData of
|
||||
false ->
|
||||
AllEls;
|
||||
_ ->
|
||||
lists:delete(XData, AllEls)
|
||||
end,
|
||||
|
||||
#adhoc_request{lang = Lang,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -11,7 +11,7 @@
|
||||
%%% 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
|
||||
|
||||
@@ -0,0 +1,604 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : cache_tab.erl
|
||||
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Description : Caching key-value table
|
||||
%%%
|
||||
%%% Created : 29 Aug 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2011 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(cache_tab).
|
||||
|
||||
-define(GEN_SERVER, gen_server).
|
||||
|
||||
-behaviour(?GEN_SERVER).
|
||||
|
||||
%% API
|
||||
-export([start_link/4, new/2, delete/1, delete/3, lookup/3,
|
||||
insert/4, info/2, tab2list/1, setopts/2,
|
||||
dirty_lookup/3, dirty_insert/4, dirty_delete/3,
|
||||
all/0, test/0]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(state, {tab = treap:empty(),
|
||||
name,
|
||||
size = 0,
|
||||
owner,
|
||||
max_size,
|
||||
life_time,
|
||||
warn,
|
||||
hits = 0,
|
||||
miss = 0,
|
||||
procs_num,
|
||||
cache_missed,
|
||||
lru,
|
||||
shrink_size}).
|
||||
|
||||
-define(PROCNAME, ?MODULE).
|
||||
-define(CALL_TIMEOUT, 60000).
|
||||
|
||||
%% Defaults
|
||||
-define(MAX_SIZE, 1000).
|
||||
-define(WARN, true).
|
||||
-define(CACHE_MISSED, true).
|
||||
-define(LRU, true).
|
||||
-define(LIFETIME, 600). %% 10 minutes
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
start_link(Proc, Tab, Opts, Owner) ->
|
||||
?GEN_SERVER:start_link(
|
||||
{local, Proc}, ?MODULE, [Tab, Opts, get_proc_num(), Owner], []).
|
||||
|
||||
new(Tab, Opts) ->
|
||||
Res = lists:flatmap(
|
||||
fun(Proc) ->
|
||||
Spec = {{Tab, Proc},
|
||||
{?MODULE, start_link,
|
||||
[Proc, Tab, Opts, self()]},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[?MODULE]},
|
||||
case supervisor:start_child(cache_tab_sup, Spec) of
|
||||
{ok, _Pid} ->
|
||||
[ok];
|
||||
R ->
|
||||
[R]
|
||||
end
|
||||
end, get_all_procs(Tab)),
|
||||
case lists:filter(fun(ok) -> false; (_) -> true end, Res) of
|
||||
[] ->
|
||||
ok;
|
||||
Err ->
|
||||
{error, Err}
|
||||
end.
|
||||
|
||||
delete(Tab) ->
|
||||
lists:foreach(
|
||||
fun(Proc) ->
|
||||
supervisor:terminate_child(cache_tab_sup, {Tab, Proc}),
|
||||
supervisor:delete_child(cache_tab_sup, {Tab, Proc})
|
||||
end, get_all_procs(Tab)).
|
||||
|
||||
delete(Tab, Key, F) ->
|
||||
?GEN_SERVER:call(
|
||||
get_proc_by_hash(Tab, Key), {delete, Key, F}, ?CALL_TIMEOUT).
|
||||
|
||||
dirty_delete(Tab, Key, F) ->
|
||||
F(),
|
||||
?GEN_SERVER:call(
|
||||
get_proc_by_hash(Tab, Key), {cache_delete, Key}, ?CALL_TIMEOUT).
|
||||
|
||||
lookup(Tab, Key, F) ->
|
||||
?GEN_SERVER:call(
|
||||
get_proc_by_hash(Tab, Key), {lookup, Key, F}, ?CALL_TIMEOUT).
|
||||
|
||||
dirty_lookup(Tab, Key, F) ->
|
||||
Proc = get_proc_by_hash(Tab, Key),
|
||||
case ?GEN_SERVER:call(Proc, {cache_lookup, Key}, ?CALL_TIMEOUT) of
|
||||
{ok, '$cached_mismatch'} ->
|
||||
error;
|
||||
{ok, Val} ->
|
||||
{ok, Val};
|
||||
_ ->
|
||||
{Result, NewVal} = case F() of
|
||||
{ok, Val} ->
|
||||
{{ok, Val}, Val};
|
||||
_ ->
|
||||
{error, '$cached_mismatch'}
|
||||
end,
|
||||
?GEN_SERVER:call(
|
||||
Proc, {cache_insert, Key, NewVal}, ?CALL_TIMEOUT),
|
||||
Result
|
||||
end.
|
||||
|
||||
insert(Tab, Key, Val, F) ->
|
||||
?GEN_SERVER:call(
|
||||
get_proc_by_hash(Tab, Key), {insert, Key, Val, F}, ?CALL_TIMEOUT).
|
||||
|
||||
dirty_insert(Tab, Key, Val, F) ->
|
||||
F(),
|
||||
?GEN_SERVER:call(
|
||||
get_proc_by_hash(Tab, Key), {cache_insert, Key, Val}, ?CALL_TIMEOUT).
|
||||
|
||||
info(Tab, Info) ->
|
||||
case lists:map(
|
||||
fun(Proc) ->
|
||||
?GEN_SERVER:call(Proc, {info, Info}, ?CALL_TIMEOUT)
|
||||
end, get_all_procs(Tab)) of
|
||||
Res when Info == size ->
|
||||
{ok, lists:sum(Res)};
|
||||
Res when Info == all ->
|
||||
{ok, Res};
|
||||
Res when Info == ratio ->
|
||||
{H, M} = lists:foldl(
|
||||
fun({Hits, Miss}, {HitsAcc, MissAcc}) ->
|
||||
{HitsAcc + Hits, MissAcc + Miss}
|
||||
end, {0, 0}, Res),
|
||||
{ok, [{hits, H}, {miss, M}]};
|
||||
_ ->
|
||||
{error, badarg}
|
||||
end.
|
||||
|
||||
setopts(Tab, Opts) ->
|
||||
lists:foreach(
|
||||
fun(Proc) ->
|
||||
?GEN_SERVER:call(Proc, {setopts, Opts}, ?CALL_TIMEOUT)
|
||||
end, get_all_procs(Tab)).
|
||||
|
||||
tab2list(Tab) ->
|
||||
lists:flatmap(
|
||||
fun(Proc) ->
|
||||
?GEN_SERVER:call(Proc, tab2list, ?CALL_TIMEOUT)
|
||||
end, get_all_procs(Tab)).
|
||||
|
||||
all() ->
|
||||
lists:usort(
|
||||
[Tab || {{Tab, _}, _, _, _} <- supervisor:which_children(cache_tab_sup)]).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([Tab, Opts, N, Pid]) ->
|
||||
State = #state{procs_num = N,
|
||||
owner = Pid,
|
||||
name = Tab},
|
||||
{ok, do_setopts(State, Opts)}.
|
||||
|
||||
handle_call({lookup, Key, F}, _From, #state{tab = T} = State) ->
|
||||
CleanPrio = clean_priority(State#state.life_time),
|
||||
case treap:lookup(Key, T) of
|
||||
{ok, Prio, Val} when (State#state.lru == true) or (Prio =< CleanPrio) ->
|
||||
Hits = State#state.hits,
|
||||
NewState = treap_update(Key, Val, State#state{hits = Hits + 1}),
|
||||
case Val of
|
||||
'$cached_mismatch' ->
|
||||
{reply, error, NewState};
|
||||
_ ->
|
||||
{reply, {ok, Val}, NewState}
|
||||
end;
|
||||
_ ->
|
||||
case catch F() of
|
||||
{ok, Val} ->
|
||||
Miss = State#state.miss,
|
||||
NewState = treap_insert(Key, Val, State),
|
||||
{reply, {ok, Val}, NewState#state{miss = Miss + 1}};
|
||||
{'EXIT', Reason} ->
|
||||
print_error(lookup, [Key], Reason, State),
|
||||
{reply, error, State};
|
||||
_ ->
|
||||
Miss = State#state.miss,
|
||||
NewState = State#state{miss = Miss + 1},
|
||||
if State#state.cache_missed ->
|
||||
{reply, error,
|
||||
treap_insert(Key, '$cached_mismatch', NewState)};
|
||||
true ->
|
||||
{reply, error, NewState}
|
||||
end
|
||||
end
|
||||
end;
|
||||
handle_call({cache_lookup, Key}, _From, #state{tab = T} = State) ->
|
||||
CleanPrio = clean_priority(State#state.life_time),
|
||||
case treap:lookup(Key, T) of
|
||||
{ok, Prio, Val} when (State#state.lru == true) or (Prio =< CleanPrio) ->
|
||||
Hits = State#state.hits,
|
||||
NewState = treap_update(Key, Val, State#state{hits = Hits + 1}),
|
||||
{reply, {ok, Val}, NewState};
|
||||
_ ->
|
||||
Miss = State#state.miss,
|
||||
NewState = State#state{miss = Miss + 1},
|
||||
{reply, error, NewState}
|
||||
end;
|
||||
handle_call({insert, Key, Val, F}, _From, #state{tab = T} = State) ->
|
||||
case treap:lookup(Key, T) of
|
||||
{ok, _Prio, Val} ->
|
||||
{reply, ok, treap_update(Key, Val, State)};
|
||||
_ ->
|
||||
case catch F() of
|
||||
{'EXIT', Reason} ->
|
||||
print_error(insert, [Key, Val], Reason, State),
|
||||
{reply, ok, State};
|
||||
_ ->
|
||||
{reply, ok, treap_insert(Key, Val, State)}
|
||||
end
|
||||
end;
|
||||
handle_call({cache_insert, _, '$cached_mismatch'}, _From,
|
||||
#state{cache_missed = false} = State) ->
|
||||
{reply, ok, State};
|
||||
handle_call({cache_insert, Key, Val}, _From, State) ->
|
||||
{reply, ok, treap_insert(Key, Val, State)};
|
||||
handle_call({delete, Key, F}, _From, State) ->
|
||||
NewState = treap_delete(Key, State),
|
||||
case catch F() of
|
||||
{'EXIT', Reason} ->
|
||||
print_error(delete, [Key], Reason, State);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{reply, ok, NewState};
|
||||
handle_call({cache_delete, Key}, _From, State) ->
|
||||
NewState = treap_delete(Key, State),
|
||||
{reply, ok, NewState};
|
||||
handle_call({info, Info}, _From, State) ->
|
||||
Res = case Info of
|
||||
size ->
|
||||
State#state.size;
|
||||
ratio ->
|
||||
{State#state.hits, State#state.miss};
|
||||
all ->
|
||||
[{max_size, State#state.max_size},
|
||||
{life_time, State#state.life_time},
|
||||
{shrink_size, State#state.shrink_size},
|
||||
{size, State#state.size},
|
||||
{owner, State#state.owner},
|
||||
{hits, State#state.hits},
|
||||
{miss, State#state.miss},
|
||||
{cache_missed, State#state.cache_missed},
|
||||
{lru, State#state.lru},
|
||||
{warn, State#state.warn}];
|
||||
_ ->
|
||||
badarg
|
||||
end,
|
||||
{reply, Res, State};
|
||||
handle_call(tab2list, _From, #state{tab = T} = State) ->
|
||||
Res = treap:fold(
|
||||
fun({Key, _, Val}, Acc) ->
|
||||
[{Key, Val}|Acc]
|
||||
end, [], T),
|
||||
{reply, Res, State};
|
||||
handle_call({setopts, Opts}, _From, State) ->
|
||||
{reply, ok, do_setopts(State, Opts)};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
do_setopts(#state{procs_num = N} = State, Opts) ->
|
||||
MaxSize = case {proplists:get_value(max_size, Opts),
|
||||
State#state.max_size} of
|
||||
{MS, _} when is_integer(MS), MS > 0 ->
|
||||
round(MS/N);
|
||||
{unlimited, _} ->
|
||||
unlimited;
|
||||
{_, undefined} ->
|
||||
round(?MAX_SIZE/N);
|
||||
{_, MS} ->
|
||||
MS
|
||||
end,
|
||||
LifeTime = case {proplists:get_value(life_time, Opts),
|
||||
State#state.life_time} of
|
||||
{LT, _} when is_integer(LT), LT > 0 ->
|
||||
LT*1000*1000;
|
||||
{unlimited, _} ->
|
||||
unlimited;
|
||||
{_, undefined} ->
|
||||
?LIFETIME*1000*1000;
|
||||
{_, LT} ->
|
||||
LT
|
||||
end,
|
||||
ShrinkSize = case {proplists:get_value(shrink_size, Opts),
|
||||
State#state.shrink_size} of
|
||||
{SS, _} when is_integer(SS), SS > 0 ->
|
||||
round(SS/N);
|
||||
_ when is_integer(MaxSize) ->
|
||||
round(MaxSize/2);
|
||||
_ ->
|
||||
unlimited
|
||||
end,
|
||||
Warn = case {proplists:get_value(warn, Opts),
|
||||
State#state.warn} of
|
||||
{true, _} ->
|
||||
true;
|
||||
{false, _} ->
|
||||
false;
|
||||
{_, undefined} ->
|
||||
?WARN;
|
||||
{_, W} ->
|
||||
W
|
||||
end,
|
||||
CacheMissed = case proplists:get_value(
|
||||
cache_missed, Opts, State#state.cache_missed) of
|
||||
false ->
|
||||
false;
|
||||
true ->
|
||||
true;
|
||||
_ ->
|
||||
?CACHE_MISSED
|
||||
end,
|
||||
LRU = case proplists:get_value(
|
||||
lru, Opts, State#state.lru) of
|
||||
false ->
|
||||
false;
|
||||
true ->
|
||||
true;
|
||||
_ ->
|
||||
?LRU
|
||||
end,
|
||||
State#state{max_size = MaxSize,
|
||||
warn = Warn,
|
||||
life_time = LifeTime,
|
||||
cache_missed = CacheMissed,
|
||||
lru = LRU,
|
||||
shrink_size = ShrinkSize}.
|
||||
|
||||
get_proc_num() ->
|
||||
erlang:system_info(logical_processors).
|
||||
|
||||
get_proc_by_hash(Tab, Term) ->
|
||||
N = erlang:phash2(Term, get_proc_num()) + 1,
|
||||
get_proc(Tab, N).
|
||||
|
||||
get_proc(Tab, N) ->
|
||||
list_to_atom(atom_to_list(?PROCNAME) ++ "_" ++
|
||||
atom_to_list(Tab) ++ "_" ++ integer_to_list(N)).
|
||||
|
||||
get_all_procs(Tab) ->
|
||||
[get_proc(Tab, N) || N <- lists:seq(1, get_proc_num())].
|
||||
|
||||
now_priority() ->
|
||||
{MSec, Sec, USec} = now(),
|
||||
-((MSec*1000000 + Sec)*1000000 + USec).
|
||||
|
||||
clean_priority(LifeTime) ->
|
||||
if is_integer(LifeTime) ->
|
||||
now_priority() + LifeTime;
|
||||
true ->
|
||||
unlimited
|
||||
end.
|
||||
|
||||
treap_update(Key, Val, #state{tab = T, lru = LRU} = State) ->
|
||||
if LRU ->
|
||||
Priority = now_priority(),
|
||||
NewT = treap:insert(Key, Priority, Val, T),
|
||||
State#state{tab = NewT};
|
||||
true ->
|
||||
State
|
||||
end.
|
||||
|
||||
treap_insert(Key, Val, State) ->
|
||||
State1 = clean_treap(State),
|
||||
#state{size = Size} = State2 = shrink_treap(State1),
|
||||
T = State2#state.tab,
|
||||
case treap:lookup(Key, T) of
|
||||
{ok, _, Val} ->
|
||||
treap_update(Key, Val, State2);
|
||||
{ok, _, _} ->
|
||||
NewT = treap:insert(Key, now_priority(), Val, T),
|
||||
State2#state{tab = NewT};
|
||||
_ ->
|
||||
NewT = treap:insert(Key, now_priority(), Val, T),
|
||||
State2#state{tab = NewT, size = Size+1}
|
||||
end.
|
||||
|
||||
treap_delete(Key, #state{tab = T, size = Size} = State) ->
|
||||
case treap:lookup(Key, T) of
|
||||
{ok, _, _} ->
|
||||
NewT = treap:delete(Key, T),
|
||||
clean_treap(State#state{tab = NewT, size = Size-1});
|
||||
_ ->
|
||||
State
|
||||
end.
|
||||
|
||||
clean_treap(#state{tab = T, size = Size, life_time = LifeTime} = State) ->
|
||||
if is_integer(LifeTime) ->
|
||||
Priority = now_priority(),
|
||||
{Cleaned, NewT} = clean_treap(T, Priority + LifeTime, 0),
|
||||
State#state{size = Size - Cleaned, tab = NewT};
|
||||
true ->
|
||||
State
|
||||
end.
|
||||
|
||||
clean_treap(Treap, CleanPriority, N) ->
|
||||
case treap:is_empty(Treap) of
|
||||
true ->
|
||||
{N, Treap};
|
||||
false ->
|
||||
{_Key, Priority, _Value} = treap:get_root(Treap),
|
||||
if Priority > CleanPriority ->
|
||||
clean_treap(treap:delete_root(Treap), CleanPriority, N+1);
|
||||
true ->
|
||||
{N, Treap}
|
||||
end
|
||||
end.
|
||||
|
||||
shrink_treap(#state{tab = T,
|
||||
max_size = MaxSize,
|
||||
shrink_size = ShrinkSize,
|
||||
warn = Warn,
|
||||
size = Size} = State) when Size >= MaxSize ->
|
||||
if Warn ->
|
||||
?WARNING_MSG("shrinking table:~n"
|
||||
"** Table: ~p~n"
|
||||
"** Processes Number: ~p~n"
|
||||
"** Max Size: ~p items~n"
|
||||
"** Shrink Size: ~p items~n"
|
||||
"** Life Time: ~p microseconds~n"
|
||||
"** Hits/Miss: ~p/~p~n"
|
||||
"** Owner: ~p~n"
|
||||
"** Cache Missed: ~p~n"
|
||||
"** Instruction: you have to tune cacheing options"
|
||||
" if this message repeats too frequently",
|
||||
[State#state.name, State#state.procs_num,
|
||||
MaxSize, ShrinkSize, State#state.life_time,
|
||||
State#state.hits, State#state.miss,
|
||||
State#state.owner, State#state.cache_missed]);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{Shrinked, NewT} = shrink_treap(T, ShrinkSize, 0),
|
||||
State#state{tab = NewT, size = Size - Shrinked};
|
||||
shrink_treap(State) ->
|
||||
State.
|
||||
|
||||
shrink_treap(T, ShrinkSize, ShrinkSize) ->
|
||||
{ShrinkSize, T};
|
||||
shrink_treap(T, ShrinkSize, N) ->
|
||||
case treap:is_empty(T) of
|
||||
true ->
|
||||
{N, T};
|
||||
false ->
|
||||
shrink_treap(treap:delete_root(T), ShrinkSize, N+1)
|
||||
end.
|
||||
|
||||
print_error(Operation, Args, Reason, State) ->
|
||||
?ERROR_MSG("callback failed:~n"
|
||||
"** Tab: ~p~n"
|
||||
"** Owner: ~p~n"
|
||||
"** Operation: ~p~n"
|
||||
"** Args: ~p~n"
|
||||
"** Reason: ~p",
|
||||
[State#state.name, State#state.owner,
|
||||
Operation, Args, Reason]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Tests
|
||||
%%--------------------------------------------------------------------
|
||||
-define(lookup, dirty_lookup).
|
||||
-define(delete, dirty_delete).
|
||||
-define(insert, dirty_insert).
|
||||
%%-define(lookup, lookup).
|
||||
%%-define(delete, delete).
|
||||
%%-define(insert, insert).
|
||||
|
||||
test() ->
|
||||
LifeTime = 2,
|
||||
ok = new(test_tbl, [{life_time, LifeTime}, {max_size, unlimited}]),
|
||||
check([]),
|
||||
ok = ?insert(test_tbl, "key", "value", fun() -> ok end),
|
||||
check([{"key", "value"}]),
|
||||
{ok, "value"} = ?lookup(test_tbl, "key", fun() -> error end),
|
||||
check([{"key", "value"}]),
|
||||
io:format("** waiting for ~p seconds to check if LRU works fine...~n",
|
||||
[LifeTime+1]),
|
||||
timer:sleep(timer:seconds(LifeTime+1)),
|
||||
ok = ?insert(test_tbl, "key1", "value1", fun() -> ok end),
|
||||
check([{"key1", "value1"}]),
|
||||
ok = ?delete(test_tbl, "key1", fun() -> ok end),
|
||||
{ok, "value"} = ?lookup(test_tbl, "key", fun() -> {ok, "value"} end),
|
||||
check([{"key", "value"}]),
|
||||
ok = ?delete(test_tbl, "key", fun() -> ok end),
|
||||
check([]),
|
||||
%% io:format("** testing buggy callbacks...~n"),
|
||||
%% delete(test_tbl, "key", fun() -> erlang:error(badarg) end),
|
||||
%% insert(test_tbl, "key", "val", fun() -> erlang:error(badarg) end),
|
||||
%% lookup(test_tbl, "key", fun() -> erlang:error(badarg) end),
|
||||
check([]),
|
||||
delete(test_tbl),
|
||||
test1().
|
||||
|
||||
test1() ->
|
||||
MaxSize = 10,
|
||||
ok = new(test_tbl, [{max_size, MaxSize}, {shrink_size, 1}, {warn, false}]),
|
||||
lists:foreach(
|
||||
fun(N) ->
|
||||
ok = ?insert(test_tbl, N, N, fun() -> ok end)
|
||||
end, lists:seq(1, MaxSize*get_proc_num())),
|
||||
{ok, MaxSize} = info(test_tbl, size),
|
||||
delete(test_tbl),
|
||||
test2().
|
||||
|
||||
test2() ->
|
||||
LifeTime = 2,
|
||||
ok = new(test_tbl, [{life_time, LifeTime},
|
||||
{max_size, unlimited},
|
||||
{lru, false}]),
|
||||
check([]),
|
||||
ok = ?insert(test_tbl, "key", "value", fun() -> ok end),
|
||||
{ok, "value"} = ?lookup(test_tbl, "key", fun() -> error end),
|
||||
check([{"key", "value"}]),
|
||||
io:format("** waiting for ~p seconds to check if non-LRU works fine...~n",
|
||||
[LifeTime+1]),
|
||||
timer:sleep(timer:seconds(LifeTime+1)),
|
||||
error = ?lookup(test_tbl, "key", fun() -> error end),
|
||||
check([{"key", '$cached_mismatch'}]),
|
||||
ok = ?insert(test_tbl, "key", "value1", fun() -> ok end),
|
||||
check([{"key", "value1"}]),
|
||||
delete(test_tbl),
|
||||
io:format("** testing speed, this may take a while...~n"),
|
||||
test3(1000),
|
||||
test3(10000),
|
||||
test3(100000),
|
||||
test3(1000000).
|
||||
|
||||
test3(Iter) ->
|
||||
ok = new(test_tbl, [{max_size, unlimited}, {life_time, unlimited}]),
|
||||
L = lists:seq(1, Iter),
|
||||
T1 = now(),
|
||||
lists:foreach(
|
||||
fun(N) ->
|
||||
ok = ?insert(test_tbl, N, N, fun() -> ok end)
|
||||
end, L),
|
||||
io:format("** average insert (size = ~p): ~p usec~n",
|
||||
[Iter, round(timer:now_diff(now(), T1)/Iter)]),
|
||||
T2 = now(),
|
||||
lists:foreach(
|
||||
fun(N) ->
|
||||
{ok, N} = ?lookup(test_tbl, N, fun() -> ok end)
|
||||
end, L),
|
||||
io:format("** average lookup (size = ~p): ~p usec~n",
|
||||
[Iter, round(timer:now_diff(now(), T2)/Iter)]),
|
||||
{ok, Iter} = info(test_tbl, size),
|
||||
delete(test_tbl).
|
||||
|
||||
check(List) ->
|
||||
Size = length(List),
|
||||
{ok, Size} = info(test_tbl, size),
|
||||
List = tab2list(test_tbl).
|
||||
@@ -0,0 +1,53 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : cache_tab_sup.erl
|
||||
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Description : Cache tables supervisor
|
||||
%%%
|
||||
%%% Created : 30 Aug 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2011 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(cache_tab_sup).
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
%% API
|
||||
-export([start_link/0]).
|
||||
|
||||
%% Supervisor callbacks
|
||||
-export([init/1]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
%%====================================================================
|
||||
%% API functions
|
||||
%%====================================================================
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
|
||||
|
||||
%%====================================================================
|
||||
%% Supervisor callbacks
|
||||
%%====================================================================
|
||||
init([]) ->
|
||||
{ok, {{one_for_one,10,1}, []}}.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
@@ -2,7 +2,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ(2.53)
|
||||
AC_INIT(ejabberd.erl,, ejabberd@process-one.net)
|
||||
AC_INIT(ejabberd, m4_esyscmd([grep -o -E "\{vsn,.\".*\"\}" ejabberd.app | cut -d \" -f 2 | tr -d '\n']), [ejabberd@process-one.net], [ejabberd])
|
||||
|
||||
# Checks for programs.
|
||||
AC_PROG_CC
|
||||
@@ -18,14 +18,13 @@ AM_WITH_ERLANG
|
||||
AM_ICONV
|
||||
#locating libexpat
|
||||
AM_WITH_EXPAT
|
||||
#locating zlib
|
||||
AM_WITH_ZLIB
|
||||
#locating PAM
|
||||
AM_WITH_PAM
|
||||
|
||||
# Checks for typedefs, structures, and compiler characteristics.
|
||||
AC_C_CONST
|
||||
|
||||
# Check Erlang headers are installed
|
||||
#AC_CHECK_HEADER(erl_driver.h,,[AC_MSG_ERROR([cannot find Erlang header files])])
|
||||
|
||||
# Change default prefix
|
||||
AC_PREFIX_DEFAULT(/)
|
||||
|
||||
@@ -33,19 +32,25 @@ AC_PREFIX_DEFAULT(/)
|
||||
AC_FUNC_MALLOC
|
||||
AC_HEADER_STDC
|
||||
|
||||
AC_MOD_ENABLE(mod_pubsub, yes)
|
||||
AC_MOD_ENABLE(mod_irc, yes)
|
||||
AC_MOD_ENABLE(mod_muc, yes)
|
||||
AC_MOD_ENABLE(mod_proxy65, yes)
|
||||
AC_MOD_ENABLE(mod_pubsub, yes)
|
||||
AC_MOD_ENABLE(eldap, yes)
|
||||
AC_MOD_ENABLE(pam, no)
|
||||
AC_MOD_ENABLE(web, yes)
|
||||
AC_MOD_ENABLE(tls, yes)
|
||||
AC_MOD_ENABLE(odbc, no)
|
||||
AC_MOD_ENABLE(tls, yes)
|
||||
AC_MOD_ENABLE(web, yes)
|
||||
|
||||
AC_MOD_ENABLE(ejabberd_zlib, yes)
|
||||
#locating zlib
|
||||
AM_WITH_ZLIB
|
||||
|
||||
AC_MOD_ENABLE(pam, no)
|
||||
#locating PAM
|
||||
AM_WITH_PAM
|
||||
|
||||
AC_ARG_ENABLE(hipe,
|
||||
[ --enable-hipe Compile natively with HiPE, not recommended (default: no)],
|
||||
[AC_HELP_STRING([--enable-hipe], [compile natively with HiPE, not recommended (default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) hipe=true ;;
|
||||
no) hipe=false ;;
|
||||
@@ -54,7 +59,7 @@ esac],[hipe=false])
|
||||
AC_SUBST(hipe)
|
||||
|
||||
AC_ARG_ENABLE(roster_gateway_workaround,
|
||||
[ --enable-roster-gateway-workaround Turn on workaround for processing gateway subscriptions (default: no)],
|
||||
[AC_HELP_STRING([--enable-roster-gateway-workaround], [turn on workaround for processing gateway subscriptions (default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) roster_gateway_workaround=true ;;
|
||||
no) roster_gateway_workaround=false ;;
|
||||
@@ -63,7 +68,7 @@ esac],[roster_gateway_workaround=false])
|
||||
AC_SUBST(roster_gateway_workaround)
|
||||
|
||||
AC_ARG_ENABLE(mssql,
|
||||
[ --enable-mssql Use Microsoft SQL Server database (default: no, requires --enable-odbc)],
|
||||
[AC_HELP_STRING([--enable-mssql], [use Microsoft SQL Server database (default: no, requires --enable-odbc)])],
|
||||
[case "${enableval}" in
|
||||
yes) db_type=mssql ;;
|
||||
no) db_type=generic ;;
|
||||
@@ -72,16 +77,16 @@ esac],[db_type=generic])
|
||||
AC_SUBST(db_type)
|
||||
|
||||
AC_ARG_ENABLE(transient_supervisors,
|
||||
[ --enable-transient_supervisors Use Erlang supervision for transient process (default: yes)],
|
||||
[AC_HELP_STRING([--enable-transient_supervisors], [use Erlang supervision for transient process (default: yes)])],
|
||||
[case "${enableval}" in
|
||||
yes) transient_supervisors=true ;;
|
||||
no) transient_supervisors=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-full-xml) ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-transient_supervisors) ;;
|
||||
esac],[transient_supervisors=true])
|
||||
AC_SUBST(transient_supervisors)
|
||||
|
||||
AC_ARG_ENABLE(full_xml,
|
||||
[ --enable-full-xml Use XML features in XMPP stream (ex: CDATA) (default: no, requires XML compliant clients)],
|
||||
[AC_HELP_STRING([--enable-full-xml], [use XML features in XMPP stream (ex: CDATA) (default: no, requires XML compliant clients)])],
|
||||
[case "${enableval}" in
|
||||
yes) full_xml=true ;;
|
||||
no) full_xml=false ;;
|
||||
@@ -89,6 +94,15 @@ AC_ARG_ENABLE(full_xml,
|
||||
esac],[full_xml=false])
|
||||
AC_SUBST(full_xml)
|
||||
|
||||
AC_ARG_ENABLE(nif,
|
||||
[AC_HELP_STRING([--enable-nif], [replace some functions with C equivalents. Requires Erlang R13B04 or higher (default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) nif=true ;;
|
||||
no) nif=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-nif) ;;
|
||||
esac],[nif=false])
|
||||
AC_SUBST(nif)
|
||||
|
||||
AC_CONFIG_FILES([Makefile
|
||||
$make_mod_irc
|
||||
$make_mod_muc
|
||||
@@ -98,6 +112,7 @@ AC_CONFIG_FILES([Makefile
|
||||
$make_pam
|
||||
$make_web
|
||||
stringprep/Makefile
|
||||
stun/Makefile
|
||||
$make_tls
|
||||
$make_odbc
|
||||
$make_ejabberd_zlib])
|
||||
@@ -119,4 +134,48 @@ else
|
||||
fi
|
||||
AC_CHECK_HEADER(krb5.h,,)
|
||||
|
||||
ENABLEUSER=""
|
||||
AC_ARG_ENABLE(user,
|
||||
[AS_HELP_STRING([--enable-user[[[[=USER]]]]], [allow this system user to start ejabberd (default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) ENABLEUSER=`whoami` ;;
|
||||
no) ENABLEUSER="" ;;
|
||||
*) ENABLEUSER=$enableval
|
||||
esac],
|
||||
[])
|
||||
if test "$ENABLEUSER" != ""; then
|
||||
echo "allow this system user to start ejabberd: $ENABLEUSER"
|
||||
AC_SUBST([INSTALLUSER], [$ENABLEUSER])
|
||||
fi
|
||||
|
||||
AC_CHECK_HEADER(openssl/md2.h, md2=true, md2=false)
|
||||
AC_SUBST(md2)
|
||||
|
||||
AC_CANONICAL_SYSTEM
|
||||
#AC_DEFINE_UNQUOTED(CPU_VENDOR_OS, "$target")
|
||||
#AC_SUBST(target_os)
|
||||
|
||||
|
||||
case "$target_os" in
|
||||
*darwin10*)
|
||||
echo "Target OS is 'Darwin10'"
|
||||
AC_LANG(Erlang)
|
||||
AC_RUN_IFELSE(
|
||||
[AC_LANG_PROGRAM([],[dnl
|
||||
halt(case erlang:system_info(wordsize) of
|
||||
8 -> 0; 4 -> 1 end)])],
|
||||
[AC_MSG_NOTICE(found 64-bit Erlang)
|
||||
CBIT=-m64],
|
||||
[AC_MSG_NOTICE(found 32-bit Erlang)
|
||||
CBIT=-m32])
|
||||
;;
|
||||
*)
|
||||
echo "Target OS is '$target_os'"
|
||||
CBIT=""
|
||||
;;
|
||||
esac
|
||||
CFLAGS="$CFLAGS $CBIT"
|
||||
LD_SHARED="$LD_SHARED $CBIT"
|
||||
echo "CBIT is set to '$CBIT'"
|
||||
|
||||
AC_OUTPUT
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : configure.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose :
|
||||
%%% Purpose :
|
||||
%%% Created : 27 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -58,15 +58,17 @@ start() ->
|
||||
end,
|
||||
|
||||
EVersion = "ERLANG_VERSION = " ++ erlang:system_info(version) ++ "\n",
|
||||
EIDirS = "EI_DIR = " ++ code:lib_dir("erl_interface") ++ "\n",
|
||||
EIDirS = "EI_DIR = " ++ code:lib_dir(erl_interface) ++ "\n",
|
||||
RootDirS = "ERLANG_DIR = " ++ code:root_dir() ++ "\n",
|
||||
%% Load the ejabberd application description so that ?VERSION can read the vsn key
|
||||
application:load(ejabberd),
|
||||
Version = "EJABBERD_VERSION = " ++ ?VERSION ++ "\n",
|
||||
ExpatDir = "EXPAT_DIR = c:\\sdk\\Expat-2.0.0\n",
|
||||
OpenSSLDir = "OPENSSL_DIR = c:\\sdk\\OpenSSL\n",
|
||||
DBType = "DBTYPE = generic\n", %% 'generic' or 'mssql'
|
||||
|
||||
SSLDir = "SSLDIR = " ++ code:lib_dir("ssl") ++ "\n",
|
||||
StdLibDir = "STDLIBDIR = " ++ code:lib_dir("stdlib") ++ "\n",
|
||||
SSLDir = "SSLDIR = " ++ code:lib_dir(ssl) ++ "\n",
|
||||
StdLibDir = "STDLIBDIR = " ++ code:lib_dir(stdlib) ++ "\n",
|
||||
|
||||
file:write_file("Makefile.inc",
|
||||
list_to_binary(EVersion ++
|
||||
@@ -85,5 +87,3 @@ start() ->
|
||||
ZlibDir ++
|
||||
ZlibLib)),
|
||||
halt().
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -30,19 +30,19 @@
|
||||
-export([start/0,
|
||||
register_mechanism/3,
|
||||
listmech/1,
|
||||
server_new/6,
|
||||
server_new/7,
|
||||
server_start/3,
|
||||
server_step/2]).
|
||||
|
||||
-record(sasl_mechanism, {mechanism, module, require_plain_password}).
|
||||
-record(sasl_state, {service, myname, realm,
|
||||
get_password, check_password,
|
||||
get_password, check_password, check_password_digest,
|
||||
mech_mod, mech_state}).
|
||||
|
||||
-export([behaviour_info/1]).
|
||||
|
||||
behaviour_info(callbacks) ->
|
||||
[{mech_new, 3}, {mech_step, 2}];
|
||||
[{mech_new, 4}, {mech_step, 2}];
|
||||
behaviour_info(_Other) ->
|
||||
undefined.
|
||||
|
||||
@@ -113,12 +113,13 @@ listmech(Host) ->
|
||||
filter_anonymous(Host, Mechs).
|
||||
|
||||
server_new(Service, ServerFQDN, UserRealm, _SecFlags,
|
||||
GetPassword, CheckPassword) ->
|
||||
GetPassword, CheckPassword, CheckPasswordDigest) ->
|
||||
#sasl_state{service = Service,
|
||||
myname = ServerFQDN,
|
||||
realm = UserRealm,
|
||||
get_password = GetPassword,
|
||||
check_password = CheckPassword}.
|
||||
check_password = CheckPassword,
|
||||
check_password_digest= CheckPasswordDigest}.
|
||||
|
||||
server_start(State, Mech, ClientIn) ->
|
||||
case lists:member(Mech, listmech(State#sasl_state.myname)) of
|
||||
@@ -128,7 +129,8 @@ server_start(State, Mech, ClientIn) ->
|
||||
{ok, MechState} = Module:mech_new(
|
||||
State#sasl_state.myname,
|
||||
State#sasl_state.get_password,
|
||||
State#sasl_state.check_password),
|
||||
State#sasl_state.check_password,
|
||||
State#sasl_state.check_password_digest),
|
||||
server_step(State#sasl_state{mech_mod = Module,
|
||||
mech_state = MechState},
|
||||
ClientIn);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
%%% Created : 23 Aug 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -17,7 +17,7 @@
|
||||
%%% 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
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
-module(cyrsasl_anonymous).
|
||||
|
||||
-export([start/1, stop/0, mech_new/3, mech_step/2]).
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2]).
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
@@ -40,7 +40,7 @@ start(_Opts) ->
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
mech_new(Host, _GetPassword, _CheckPassword) ->
|
||||
mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
|
||||
{ok, #state{server = Host}}.
|
||||
|
||||
mech_step(State, _ClientIn) ->
|
||||
@@ -51,5 +51,6 @@ mech_step(State, _ClientIn) ->
|
||||
%% Checks that the username is available
|
||||
case ejabberd_auth:is_user_exists(User, Server) of
|
||||
true -> {error, "not-authorized"};
|
||||
false -> {ok, [{username, User}]}
|
||||
false -> {ok, [{username, User},
|
||||
{auth_module, ejabberd_auth_anonymous}]}
|
||||
end.
|
||||
|
||||
@@ -3,7 +3,25 @@
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : DIGEST-MD5 SASL mechanism
|
||||
%%% Created : 11 Mar 2003 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Id : $Id$
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2011 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(cyrsasl_digest).
|
||||
@@ -11,12 +29,15 @@
|
||||
|
||||
-export([start/1,
|
||||
stop/0,
|
||||
mech_new/3,
|
||||
mech_new/4,
|
||||
mech_step/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state, {step, nonce, username, authzid, get_password}).
|
||||
-record(state, {step, nonce, username, authzid, get_password, check_password, auth_module,
|
||||
host}).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, true).
|
||||
@@ -24,10 +45,12 @@ start(_Opts) ->
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
mech_new(_Host, GetPassword, _CheckPassword) ->
|
||||
mech_new(Host, GetPassword, _CheckPassword, CheckPasswordDigest) ->
|
||||
{ok, #state{step = 1,
|
||||
nonce = randoms:get_string(),
|
||||
get_password = GetPassword}}.
|
||||
host = Host,
|
||||
get_password = GetPassword,
|
||||
check_password = CheckPasswordDigest}}.
|
||||
|
||||
mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
|
||||
{continue,
|
||||
@@ -39,38 +62,51 @@ mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
|
||||
bad ->
|
||||
{error, "bad-protocol"};
|
||||
KeyVals ->
|
||||
DigestURI = xml:get_attr_s("digest-uri", KeyVals),
|
||||
UserName = xml:get_attr_s("username", KeyVals),
|
||||
AuthzId = xml:get_attr_s("authzid", KeyVals),
|
||||
case (State#state.get_password)(UserName) of
|
||||
case is_digesturi_valid(DigestURI, State#state.host) of
|
||||
false ->
|
||||
?DEBUG("User login not authorized because digest-uri "
|
||||
"seems invalid: ~p", [DigestURI]),
|
||||
{error, "not-authorized", UserName};
|
||||
Passwd ->
|
||||
Response = response(KeyVals, UserName, Passwd,
|
||||
Nonce, AuthzId, "AUTHENTICATE"),
|
||||
case xml:get_attr_s("response", KeyVals) of
|
||||
Response ->
|
||||
RspAuth = response(KeyVals,
|
||||
UserName, Passwd,
|
||||
Nonce, AuthzId, ""),
|
||||
{continue,
|
||||
"rspauth=" ++ RspAuth,
|
||||
State#state{step = 5,
|
||||
username = UserName,
|
||||
authzid = AuthzId}};
|
||||
_ ->
|
||||
{error, "not-authorized", UserName}
|
||||
true ->
|
||||
AuthzId = xml:get_attr_s("authzid", KeyVals),
|
||||
case (State#state.get_password)(UserName) of
|
||||
{false, _} ->
|
||||
{error, "not-authorized", UserName};
|
||||
{Passwd, AuthModule} ->
|
||||
case (State#state.check_password)(UserName, "",
|
||||
xml:get_attr_s("response", KeyVals),
|
||||
fun(PW) -> response(KeyVals, UserName, PW, Nonce, AuthzId,
|
||||
"AUTHENTICATE") end) of
|
||||
{true, _} ->
|
||||
RspAuth = response(KeyVals,
|
||||
UserName, Passwd,
|
||||
Nonce, AuthzId, ""),
|
||||
{continue,
|
||||
"rspauth=" ++ RspAuth,
|
||||
State#state{step = 5,
|
||||
auth_module = AuthModule,
|
||||
username = UserName,
|
||||
authzid = AuthzId}};
|
||||
false ->
|
||||
{error, "not-authorized", UserName};
|
||||
{false, _} ->
|
||||
{error, "not-authorized", UserName}
|
||||
end
|
||||
end
|
||||
end
|
||||
end;
|
||||
mech_step(#state{step = 5,
|
||||
auth_module = AuthModule,
|
||||
username = UserName,
|
||||
authzid = AuthzId}, "") ->
|
||||
{ok, [{username, UserName}, {authzid, AuthzId}]};
|
||||
{ok, [{username, UserName}, {authzid, AuthzId},
|
||||
{auth_module, AuthModule}]};
|
||||
mech_step(A, B) ->
|
||||
io:format("SASL DIGEST: A ~p B ~p", [A,B]),
|
||||
?DEBUG("SASL DIGEST: A ~p B ~p", [A,B]),
|
||||
{error, "bad-protocol"}.
|
||||
|
||||
|
||||
parse(S) ->
|
||||
parse1(S, "", []).
|
||||
|
||||
@@ -87,15 +123,17 @@ parse1([], [], T) ->
|
||||
parse1([], _S, _T) ->
|
||||
bad.
|
||||
|
||||
parse2([$" | Cs], Key, Val, Ts) ->
|
||||
parse2([$\" | Cs], Key, Val, Ts) ->
|
||||
parse3(Cs, Key, Val, Ts);
|
||||
parse2([C | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, [C | Val], Ts);
|
||||
parse2([], _, _, _) ->
|
||||
bad.
|
||||
|
||||
parse3([$" | Cs], Key, Val, Ts) ->
|
||||
parse3([$\" | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, Val, Ts);
|
||||
parse3([$\\, C | Cs], Key, Val, Ts) ->
|
||||
parse3(Cs, Key, [C | Val], Ts);
|
||||
parse3([C | Cs], Key, Val, Ts) ->
|
||||
parse3(Cs, Key, [C | Val], Ts);
|
||||
parse3([], _, _, _) ->
|
||||
@@ -111,6 +149,23 @@ parse4([], Key, Val, Ts) ->
|
||||
parse1([], "", [{Key, lists:reverse(Val)} | Ts]).
|
||||
|
||||
|
||||
%% @doc Check if the digest-uri is valid.
|
||||
%% RFC-2831 allows to provide the IP address in Host,
|
||||
%% however ejabberd doesn't allow that.
|
||||
%% If the service (for example jabber.example.org)
|
||||
%% is provided by several hosts (being one of them server3.example.org),
|
||||
%% then digest-uri can be like xmpp/server3.example.org/jabber.example.org
|
||||
%% In that case, ejabberd only checks the service name, not the host.
|
||||
is_digesturi_valid(DigestURICase, JabberHost) ->
|
||||
DigestURI = stringprep:tolower(DigestURICase),
|
||||
case catch string:tokens(DigestURI, "/") of
|
||||
["xmpp", Host] when Host == JabberHost ->
|
||||
true;
|
||||
["xmpp", _Host, ServName] when ServName == JabberHost ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -27,7 +27,7 @@
|
||||
-module(cyrsasl_plain).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/1, stop/0, mech_new/3, mech_step/2, parse/1]).
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1]).
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
@@ -40,15 +40,16 @@ start(_Opts) ->
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
mech_new(_Host, _GetPassword, CheckPassword) ->
|
||||
mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) ->
|
||||
{ok, #state{check_password = CheckPassword}}.
|
||||
|
||||
mech_step(State, ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
case prepare(ClientIn) of
|
||||
[AuthzId, User, Password] ->
|
||||
case (State#state.check_password)(User, Password) of
|
||||
true ->
|
||||
{ok, [{username, User}, {authzid, AuthzId}]};
|
||||
{true, AuthModule} ->
|
||||
{ok, [{username, User}, {authzid, AuthzId},
|
||||
{auth_module, AuthModule}]};
|
||||
_ ->
|
||||
{error, "not-authorized", User}
|
||||
end;
|
||||
@@ -56,6 +57,24 @@ mech_step(State, ClientIn) ->
|
||||
{error, "bad-protocol"}
|
||||
end.
|
||||
|
||||
prepare(ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
[[], UserMaybeDomain, Password] ->
|
||||
case parse_domain(UserMaybeDomain) of
|
||||
%% <NUL>login@domain<NUL>pwd
|
||||
[User, _Domain] ->
|
||||
[UserMaybeDomain, User, Password];
|
||||
%% <NUL>login<NUL>pwd
|
||||
[User] ->
|
||||
["", User, Password]
|
||||
end;
|
||||
%% login@domain<NUL>login<NUL>pwd
|
||||
[AuthzId, User, Password] ->
|
||||
[AuthzId, User, Password];
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
|
||||
parse(S) ->
|
||||
parse1(S, "", []).
|
||||
@@ -70,5 +89,12 @@ parse1([], S, T) ->
|
||||
lists:reverse([lists:reverse(S) | T]).
|
||||
|
||||
|
||||
parse_domain(S) ->
|
||||
parse_domain1(S, "", []).
|
||||
|
||||
|
||||
parse_domain1([$@ | Cs], S, T) ->
|
||||
parse_domain1(Cs, "", [lists:reverse(S) | T]);
|
||||
parse_domain1([C | Cs], S, T) ->
|
||||
parse_domain1(Cs, [C | S], T);
|
||||
parse_domain1([], S, T) ->
|
||||
lists:reverse([lists:reverse(S) | T]).
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
%% Copyright (c) 2007
|
||||
%% Mats Cronqvist <mats.cronqvist@ericsson.com>
|
||||
%% Chris Newcombe <chris.newcombe@gmail.com>
|
||||
%% Jacob Vorreuter <jacob.vorreuter@gmail.com>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person
|
||||
%% obtaining a copy of this software and associated documentation
|
||||
%% files (the "Software"), to deal in the Software without
|
||||
%% restriction, including without limitation the rights to use,
|
||||
%% copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the
|
||||
%% Software is furnished to do so, subject to the following
|
||||
%% conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be
|
||||
%% included in all copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
%% OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : dynamic_compile.erl
|
||||
%%% Description :
|
||||
%%% Authors : Mats Cronqvist <mats.cronqvist@ericsson.com>
|
||||
%%% Chris Newcombe <chris.newcombe@gmail.com>
|
||||
%%% Jacob Vorreuter <jacob.vorreuter@gmail.com>
|
||||
%%% TODO :
|
||||
%%% - add support for limit include-file depth (and prevent circular references)
|
||||
%%% prevent circular macro expansion set FILE correctly when -module() is found
|
||||
%%% -include_lib support $ENVVAR in include filenames
|
||||
%%% substitute-stringize (??MACRO)
|
||||
%%% -undef/-ifdef/-ifndef/-else/-endif
|
||||
%%% -file(File, Line)
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(dynamic_compile).
|
||||
|
||||
%% API
|
||||
-export([from_string/1, from_string/2]).
|
||||
|
||||
-import(lists, [reverse/1, keyreplace/4]).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function:
|
||||
%% Description:
|
||||
%% Returns a binary that can be used with
|
||||
%% code:load_binary(Module, ModuleFilenameForInternalRecords, Binary).
|
||||
%%--------------------------------------------------------------------
|
||||
from_string(CodeStr) ->
|
||||
from_string(CodeStr, []).
|
||||
|
||||
% takes Options as for compile:forms/2
|
||||
from_string(CodeStr, CompileFormsOptions) ->
|
||||
%% Initialise the macro dictionary with the default predefined macros,
|
||||
%% (adapted from epp.erl:predef_macros/1
|
||||
Filename = "compiled_from_string",
|
||||
%%Machine = list_to_atom(erlang:system_info(machine)),
|
||||
Ms0 = dict:new(),
|
||||
% Ms1 = dict:store('FILE', {[], "compiled_from_string"}, Ms0),
|
||||
% Ms2 = dict:store('LINE', {[], 1}, Ms1), % actually we might add special code for this
|
||||
% Ms3 = dict:store('MODULE', {[], undefined}, Ms2),
|
||||
% Ms4 = dict:store('MODULE_STRING', {[], undefined}, Ms3),
|
||||
% Ms5 = dict:store('MACHINE', {[], Machine}, Ms4),
|
||||
% InitMD = dict:store(Machine, {[], true}, Ms5),
|
||||
InitMD = Ms0,
|
||||
|
||||
%% From the docs for compile:forms:
|
||||
%% When encountering an -include or -include_dir directive, the compiler searches for header files in the following directories:
|
||||
%% 1. ".", the current working directory of the file server;
|
||||
%% 2. the base name of the compiled file;
|
||||
%% 3. the directories specified using the i option. The directory specified last is searched first.
|
||||
%% In this case, #2 is meaningless.
|
||||
IncludeSearchPath = ["." | reverse([Dir || {i, Dir} <- CompileFormsOptions])],
|
||||
{RevForms, _OutMacroDict} = scan_and_parse(CodeStr, Filename, 1, [], InitMD, IncludeSearchPath),
|
||||
Forms = reverse(RevForms),
|
||||
|
||||
%% note: 'binary' is forced as an implicit option, whether it is provided or not.
|
||||
case compile:forms(Forms, CompileFormsOptions) of
|
||||
{ok, ModuleName, CompiledCodeBinary} when is_binary(CompiledCodeBinary) ->
|
||||
{ModuleName, CompiledCodeBinary};
|
||||
{ok, ModuleName, CompiledCodeBinary, []} when is_binary(CompiledCodeBinary) -> % empty warnings list
|
||||
{ModuleName, CompiledCodeBinary};
|
||||
{ok, _ModuleName, _CompiledCodeBinary, Warnings} ->
|
||||
throw({?MODULE, warnings, Warnings});
|
||||
Other ->
|
||||
throw({?MODULE, compile_forms, Other})
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
%%% Code from Mats Cronqvist
|
||||
%%% See http://www.erlang.org/pipermail/erlang-questions/2007-March/025507.html
|
||||
%%%## 'scan_and_parse'
|
||||
%%%
|
||||
%%% basically we call the OTP scanner and parser (erl_scan and
|
||||
%%% erl_parse) line-by-line, but check each scanned line for (or
|
||||
%%% definitions of) macros before parsing.
|
||||
%% returns {ReverseForms, FinalMacroDict}
|
||||
scan_and_parse([], _CurrFilename, _CurrLine, RevForms, MacroDict, _IncludeSearchPath) ->
|
||||
{RevForms, MacroDict};
|
||||
|
||||
scan_and_parse(RemainingText, CurrFilename, CurrLine, RevForms, MacroDict, IncludeSearchPath) ->
|
||||
case scanner(RemainingText, CurrLine, MacroDict) of
|
||||
{tokens, NLine, NRemainingText, Toks} ->
|
||||
{ok, Form} = erl_parse:parse_form(Toks),
|
||||
scan_and_parse(NRemainingText, CurrFilename, NLine, [Form | RevForms], MacroDict, IncludeSearchPath);
|
||||
{macro, NLine, NRemainingText, NMacroDict} ->
|
||||
scan_and_parse(NRemainingText, CurrFilename, NLine, RevForms,NMacroDict, IncludeSearchPath);
|
||||
{include, NLine, NRemainingText, IncludeFilename} ->
|
||||
IncludeFileRemainingTextents = read_include_file(IncludeFilename, IncludeSearchPath),
|
||||
%%io:format("include file ~p contents: ~n~p~nRemainingText = ~p~n", [IncludeFilename,IncludeFileRemainingTextents, RemainingText]),
|
||||
%% Modify the FILE macro to reflect the filename
|
||||
%%IncludeMacroDict = dict:store('FILE', {[],IncludeFilename}, MacroDict),
|
||||
IncludeMacroDict = MacroDict,
|
||||
|
||||
%% Process the header file (inc. any nested header files)
|
||||
{RevIncludeForms, IncludedMacroDict} = scan_and_parse(IncludeFileRemainingTextents, IncludeFilename, 1, [], IncludeMacroDict, IncludeSearchPath),
|
||||
%io:format("include file results = ~p~n", [R]),
|
||||
%% Restore the FILE macro in the NEW MacroDict (so we keep any macros defined in the header file)
|
||||
%%NMacroDict = dict:store('FILE', {[],CurrFilename}, IncludedMacroDict),
|
||||
NMacroDict = IncludedMacroDict,
|
||||
|
||||
%% Continue with the original file
|
||||
scan_and_parse(NRemainingText, CurrFilename, NLine, RevIncludeForms ++ RevForms, NMacroDict, IncludeSearchPath);
|
||||
done ->
|
||||
scan_and_parse([], CurrFilename, CurrLine, RevForms, MacroDict, IncludeSearchPath)
|
||||
end.
|
||||
|
||||
scanner(Text, Line, MacroDict) ->
|
||||
case erl_scan:tokens([],Text,Line) of
|
||||
{done, {ok,Toks,NLine}, LeftOverChars} ->
|
||||
case pre_proc(Toks, MacroDict) of
|
||||
{tokens, NToks} -> {tokens, NLine, LeftOverChars, NToks};
|
||||
{macro, NMacroDict} -> {macro, NLine, LeftOverChars, NMacroDict};
|
||||
{include, Filename} -> {include, NLine, LeftOverChars, Filename}
|
||||
end;
|
||||
{more, _Continuation} ->
|
||||
%% This is supposed to mean "term is not yet complete" (i.e. a '.' has
|
||||
%% not been reached yet).
|
||||
%% However, for some bizarre reason we also get this if there is a comment after the final '.' in a file.
|
||||
%% So we check to see if Text only consists of comments.
|
||||
case is_only_comments(Text) of
|
||||
true ->
|
||||
done;
|
||||
false ->
|
||||
throw({incomplete_term, Text, Line})
|
||||
end
|
||||
end.
|
||||
|
||||
is_only_comments(Text) -> is_only_comments(Text, not_in_comment).
|
||||
|
||||
is_only_comments([], _) -> true;
|
||||
is_only_comments([$ |T], not_in_comment) -> is_only_comments(T, not_in_comment); % skipping whitspace outside of comment
|
||||
is_only_comments([$\t |T], not_in_comment) -> is_only_comments(T, not_in_comment); % skipping whitspace outside of comment
|
||||
is_only_comments([$\n |T], not_in_comment) -> is_only_comments(T, not_in_comment); % skipping whitspace outside of comment
|
||||
is_only_comments([$% |T], not_in_comment) -> is_only_comments(T, in_comment); % found start of a comment
|
||||
is_only_comments(_, not_in_comment) -> false;
|
||||
% found any significant char NOT in a comment
|
||||
is_only_comments([$\n |T], in_comment) -> is_only_comments(T, not_in_comment); % found end of a comment
|
||||
is_only_comments([_ |T], in_comment) -> is_only_comments(T, in_comment). % skipping over in-comment chars
|
||||
|
||||
%%%## 'pre-proc'
|
||||
%%%
|
||||
%%% have to implement a subset of the pre-processor, since epp insists
|
||||
%%% on running on a file.
|
||||
%%% only handles 2 cases;
|
||||
%% -define(MACRO, something).
|
||||
%% -define(MACRO(VAR1,VARN),{stuff,VAR1,more,stuff,VARN,extra,stuff}).
|
||||
pre_proc([{'-',_},{atom,_,define},{'(',_},{_,_,Name}|DefToks],MacroDict) ->
|
||||
false = dict:is_key(Name, MacroDict),
|
||||
case DefToks of
|
||||
[{',',_} | Macro] ->
|
||||
{macro, dict:store(Name, {[], macro_body_def(Macro, [])}, MacroDict)};
|
||||
[{'(',_} | Macro] ->
|
||||
{macro, dict:store(Name, macro_params_body_def(Macro, []), MacroDict)}
|
||||
end;
|
||||
|
||||
pre_proc([{'-',_}, {atom,_,include}, {'(',_}, {string,_,Filename}, {')',_}, {dot,_}], _MacroDict) ->
|
||||
{include, Filename};
|
||||
|
||||
pre_proc(Toks,MacroDict) ->
|
||||
{tokens, subst_macros(Toks, MacroDict)}.
|
||||
|
||||
macro_params_body_def([{')',_},{',',_} | Toks], RevParams) ->
|
||||
{reverse(RevParams), macro_body_def(Toks, [])};
|
||||
macro_params_body_def([{var,_,Param} | Toks], RevParams) ->
|
||||
macro_params_body_def(Toks, [Param | RevParams]);
|
||||
macro_params_body_def([{',',_}, {var,_,Param} | Toks], RevParams) ->
|
||||
macro_params_body_def(Toks, [Param | RevParams]).
|
||||
|
||||
macro_body_def([{')',_}, {dot,_}], RevMacroBodyToks) ->
|
||||
reverse(RevMacroBodyToks);
|
||||
macro_body_def([Tok|Toks], RevMacroBodyToks) ->
|
||||
macro_body_def(Toks, [Tok | RevMacroBodyToks]).
|
||||
|
||||
subst_macros(Toks, MacroDict) ->
|
||||
reverse(subst_macros_rev(Toks, MacroDict, [])).
|
||||
|
||||
%% returns a reversed list of tokes
|
||||
subst_macros_rev([{'?',_}, {_,LineNum,'LINE'} | Toks], MacroDict, RevOutToks) ->
|
||||
%% special-case for ?LINE, to avoid creating a new MacroDict for every line in the source file
|
||||
subst_macros_rev(Toks, MacroDict, [{integer,LineNum,LineNum}] ++ RevOutToks);
|
||||
|
||||
subst_macros_rev([{'?',_}, {_,_,Name}, {'(',_} = Paren | Toks], MacroDict, RevOutToks) ->
|
||||
case dict:fetch(Name, MacroDict) of
|
||||
{[], MacroValue} ->
|
||||
%% This macro does not have any vars, so ignore the fact that the invocation is followed by "(...stuff"
|
||||
%% Recursively expand any macro calls inside this macro's value
|
||||
%% TODO: avoid infinite expansion due to circular references (even indirect ones)
|
||||
RevExpandedOtherMacrosToks = subst_macros_rev(MacroValue, MacroDict, []),
|
||||
subst_macros_rev([Paren|Toks], MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks);
|
||||
ParamsAndBody ->
|
||||
%% This macro does have vars.
|
||||
%% Collect all of the passe arguments, in an ordered list
|
||||
{NToks, Arguments} = subst_macros_get_args(Toks, []),
|
||||
%% Expand the varibles
|
||||
ExpandedParamsToks = subst_macros_subst_args_for_vars(ParamsAndBody, Arguments),
|
||||
%% Recursively expand any macro calls inside this macro's value
|
||||
%% TODO: avoid infinite expansion due to circular references (even indirect ones)
|
||||
RevExpandedOtherMacrosToks = subst_macros_rev(ExpandedParamsToks, MacroDict, []),
|
||||
subst_macros_rev(NToks, MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks)
|
||||
end;
|
||||
|
||||
subst_macros_rev([{'?',_}, {_,_,Name} | Toks], MacroDict, RevOutToks) ->
|
||||
%% This macro invocation does not have arguments.
|
||||
%% Therefore the definition should not have parameters
|
||||
{[], MacroValue} = dict:fetch(Name, MacroDict),
|
||||
|
||||
%% Recursively expand any macro calls inside this macro's value
|
||||
%% TODO: avoid infinite expansion due to circular references (even indirect ones)
|
||||
RevExpandedOtherMacrosToks = subst_macros_rev(MacroValue, MacroDict, []),
|
||||
subst_macros_rev(Toks, MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks);
|
||||
|
||||
subst_macros_rev([Tok|Toks], MacroDict, RevOutToks) ->
|
||||
subst_macros_rev(Toks, MacroDict, [Tok|RevOutToks]);
|
||||
subst_macros_rev([], _MacroDict, RevOutToks) -> RevOutToks.
|
||||
|
||||
subst_macros_get_args([{')',_} | Toks], RevArgs) ->
|
||||
{Toks, reverse(RevArgs)};
|
||||
subst_macros_get_args([{',',_}, {var,_,ArgName} | Toks], RevArgs) ->
|
||||
subst_macros_get_args(Toks, [ArgName| RevArgs]);
|
||||
subst_macros_get_args([{var,_,ArgName} | Toks], RevArgs) ->
|
||||
subst_macros_get_args(Toks, [ArgName | RevArgs]).
|
||||
|
||||
subst_macros_subst_args_for_vars({[], BodyToks}, []) ->
|
||||
BodyToks;
|
||||
subst_macros_subst_args_for_vars({[Param | Params], BodyToks}, [Arg|Args]) ->
|
||||
NBodyToks = keyreplace(Param, 3, BodyToks, {var,1,Arg}),
|
||||
subst_macros_subst_args_for_vars({Params, NBodyToks}, Args).
|
||||
|
||||
read_include_file(Filename, IncludeSearchPath) ->
|
||||
case file:path_open(IncludeSearchPath, Filename, [read, raw, binary]) of
|
||||
{ok, IoDevice, FullName} ->
|
||||
{ok, Data} = file:read(IoDevice, filelib:file_size(FullName)),
|
||||
file:close(IoDevice),
|
||||
binary_to_list(Data);
|
||||
{error, Reason} ->
|
||||
throw({failed_to_read_include_file, Reason, Filename, IncludeSearchPath})
|
||||
end.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{application, ejabberd,
|
||||
[{description, "ejabberd"},
|
||||
{vsn, "2.0.0"},
|
||||
{vsn, "2.1.7"},
|
||||
{modules, [acl,
|
||||
adhoc,
|
||||
configure,
|
||||
@@ -115,7 +115,7 @@
|
||||
nodetree_virtual,
|
||||
p1_fsm,
|
||||
p1_mnesia,
|
||||
ram_file_io_server,
|
||||
p1_prof,
|
||||
randoms,
|
||||
sha,
|
||||
shaper,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
%%%
|
||||
%%% ejabberd configuration file
|
||||
%%%
|
||||
%%%'
|
||||
|
||||
%%% The parameters used in this configuration file are explained in more detail
|
||||
%%% in the ejabberd Installation and Operation Guide.
|
||||
%%% Please consult the Guide in case of doubts, it is included in
|
||||
%%% Please consult the Guide in case of doubts, it is included with
|
||||
%%% your copy of ejabberd, and is also available online at
|
||||
%%% http://www.process-one.net/en/ejabberd/docs/
|
||||
|
||||
@@ -16,26 +17,26 @@
|
||||
%%% - Each term ends in a dot, for example:
|
||||
%%% override_global.
|
||||
%%%
|
||||
%%% - A tuple has a fixed definition, its elements are
|
||||
%%% - A tuple has a fixed definition, its elements are
|
||||
%%% enclosed in {}, and separated with commas:
|
||||
%%% {loglevel, 4}.
|
||||
%%%
|
||||
%%% - A list can have as many elements as you want,
|
||||
%%% - A list can have as many elements as you want,
|
||||
%%% and is enclosed in [], for example:
|
||||
%%% [http_poll, web_admin, tls]
|
||||
%%%
|
||||
%%% - A keyword of ejabberd is a word in lowercase.
|
||||
%%% The strings are enclosed in "" and can have spaces, dots...
|
||||
%%% - A keyword of ejabberd is a word in lowercase.
|
||||
%%% Strings are enclosed in "" and can contain spaces, dots, ...
|
||||
%%% {language, "en"}.
|
||||
%%% {ldap_rootdn, "dc=example,dc=com"}.
|
||||
%%% {ldap_rootdn, "dc=example,dc=com"}.
|
||||
%%%
|
||||
%%% - This term includes a tuple, a keyword, a list and two strings:
|
||||
%%% - This term includes a tuple, a keyword, a list, and two strings:
|
||||
%%% {hosts, ["jabber.example.net", "im.example.com"]}.
|
||||
%%%
|
||||
|
||||
|
||||
%%% =======================
|
||||
%%% OVERRIDE STORED OPTIONS
|
||||
%%%. =======================
|
||||
%%%' OVERRIDE STORED OPTIONS
|
||||
|
||||
%%
|
||||
%% Override the old values stored in the database.
|
||||
@@ -57,8 +58,8 @@
|
||||
%%override_acls.
|
||||
|
||||
|
||||
%%% =========
|
||||
%%% DEBUGGING
|
||||
%%%. =========
|
||||
%%%' DEBUGGING
|
||||
|
||||
%%
|
||||
%% loglevel: Verbosity of log files generated by ejabberd.
|
||||
@@ -72,14 +73,15 @@
|
||||
{loglevel, 4}.
|
||||
|
||||
%%
|
||||
%% watchdog_admins: If an ejabberd process consumes too much memory,
|
||||
%% send live notifications to those Jabber accounts.
|
||||
%% watchdog_admins: Only useful for developers: if an ejabberd process
|
||||
%% consumes a lot of memory, send live notifications to these XMPP
|
||||
%% accounts.
|
||||
%%
|
||||
%%{watchdog_admins, ["bob@example.com"]}.
|
||||
|
||||
|
||||
%%% ================
|
||||
%%% SERVED HOSTNAMES
|
||||
%%%. ================
|
||||
%%%' SERVED HOSTNAMES
|
||||
|
||||
%%
|
||||
%% hosts: Domains served by ejabberd.
|
||||
@@ -89,19 +91,19 @@
|
||||
{hosts, ["localhost"]}.
|
||||
|
||||
%%
|
||||
%% route_subdomains: Delegate subdomains to other Jabber server.
|
||||
%% route_subdomains: Delegate subdomains to other XMPP servers.
|
||||
%% For example, if this ejabberd serves example.org and you want
|
||||
%% to allow communication with a Jabber server called im.example.org.
|
||||
%% to allow communication with an XMPP server called im.example.org.
|
||||
%%
|
||||
%%{route_subdomains, s2s}.
|
||||
|
||||
|
||||
%%% ===============
|
||||
%%% LISTENING PORTS
|
||||
%%%. ===============
|
||||
%%%' LISTENING PORTS
|
||||
|
||||
%%
|
||||
%% listen: Which ports will ejabberd listen, which service handles it
|
||||
%% and what options to start it with.
|
||||
%% listen: The ports ejabberd will listen on, which service each is handled
|
||||
%% by and what options to start it with.
|
||||
%%
|
||||
{listen,
|
||||
[
|
||||
@@ -109,8 +111,8 @@
|
||||
{5222, ejabberd_c2s, [
|
||||
|
||||
%%
|
||||
%% If TLS is compiled and you installed a SSL
|
||||
%% certificate, put the correct path to the
|
||||
%% If TLS is compiled in and you installed a SSL
|
||||
%% certificate, specify the full path to the
|
||||
%% file and uncomment this line:
|
||||
%%
|
||||
%%{certfile, "/path/to/ssl.pem"}, starttls,
|
||||
@@ -121,7 +123,7 @@
|
||||
]},
|
||||
|
||||
%%
|
||||
%% To enable the old SSL connection method in port 5223:
|
||||
%% To enable the old SSL connection method on port 5223:
|
||||
%%
|
||||
%%{5223, ejabberd_c2s, [
|
||||
%% {access, c2s},
|
||||
@@ -136,10 +138,10 @@
|
||||
]},
|
||||
|
||||
%%
|
||||
%% ejabberd_service: Interact with external components (transports...)
|
||||
%% ejabberd_service: Interact with external components (transports, ...)
|
||||
%%
|
||||
%%{8888, ejabberd_service, [
|
||||
%% {access, all},
|
||||
%% {access, all},
|
||||
%% {shaper_rule, fast},
|
||||
%% {ip, {127, 0, 0, 1}},
|
||||
%% {hosts, ["icq.example.org", "sms.example.org"],
|
||||
@@ -147,8 +149,20 @@
|
||||
%% }
|
||||
%% ]},
|
||||
|
||||
%%
|
||||
%% ejabberd_stun: Handles STUN Binding requests
|
||||
%%
|
||||
%%{{3478, udp}, ejabberd_stun, []},
|
||||
|
||||
{5280, ejabberd_http, [
|
||||
http_poll,
|
||||
%%{request_handlers,
|
||||
%% [
|
||||
%% {["pub", "archive"], mod_http_fileserver}
|
||||
%% ]},
|
||||
captcha,
|
||||
http_bind,
|
||||
http_poll,
|
||||
%%register,
|
||||
web_admin
|
||||
]}
|
||||
|
||||
@@ -156,10 +170,10 @@
|
||||
|
||||
%%
|
||||
%% s2s_use_starttls: Enable STARTTLS + Dialback for S2S connections.
|
||||
%% Allowed values are: true or false.
|
||||
%% Allowed values are: false optional required required_trusted
|
||||
%% You must specify a certificate file.
|
||||
%%
|
||||
%%{s2s_use_starttls, true}.
|
||||
%%{s2s_use_starttls, optional}.
|
||||
|
||||
%%
|
||||
%% s2s_certfile: Specify a certificate file.
|
||||
@@ -185,14 +199,22 @@
|
||||
%%{{s2s_host, "goodhost.org"}, allow}.
|
||||
%%{{s2s_host, "badhost.org"}, deny}.
|
||||
|
||||
%%
|
||||
%% Outgoing S2S options
|
||||
%%
|
||||
%% Preferred address families (which to try first) and connect timeout
|
||||
%% in milliseconds.
|
||||
%%
|
||||
%%{outgoing_s2s_options, [ipv4, ipv6], 10000}.
|
||||
|
||||
%%% ==============
|
||||
%%% AUTHENTICATION
|
||||
|
||||
%%%. ==============
|
||||
%%%' AUTHENTICATION
|
||||
|
||||
%%
|
||||
%% auth_method: Method used to authenticate the users.
|
||||
%% The default method is the internal.
|
||||
%% If you want to use a different method,
|
||||
%% If you want to use a different method,
|
||||
%% comment this line and enable the correct ones.
|
||||
%%
|
||||
{auth_method, internal}.
|
||||
@@ -222,19 +244,30 @@
|
||||
%%{auth_method, ldap}.
|
||||
%%
|
||||
%% List of LDAP servers:
|
||||
%%{ldap_servers, ["localhost"]}.
|
||||
%%{ldap_servers, ["localhost"]}.
|
||||
%%
|
||||
%% LDAP attribute that holds user ID:
|
||||
%%{ldap_uids, [{"mail", "%u@mail.example.org"}]}.
|
||||
%% Encryption of connection to LDAP servers:
|
||||
%%{ldap_encrypt, none}.
|
||||
%%{ldap_encrypt, tls}.
|
||||
%%
|
||||
%% Search base of LDAP directory:
|
||||
%%{ldap_base, "dc=example,dc=com"}.
|
||||
%% Port to connect to on LDAP servers:
|
||||
%%{ldap_port, 389}.
|
||||
%%{ldap_port, 636}.
|
||||
%%
|
||||
%% LDAP manager:
|
||||
%%{ldap_rootdn, "dc=example,dc=com"}.
|
||||
%%{ldap_rootdn, "dc=example,dc=com"}.
|
||||
%%
|
||||
%% Password to LDAP manager:
|
||||
%%{ldap_password, "******"}.
|
||||
%% Password of LDAP manager:
|
||||
%%{ldap_password, "******"}.
|
||||
%%
|
||||
%% Search base of LDAP directory:
|
||||
%%{ldap_base, "dc=example,dc=com"}.
|
||||
%%
|
||||
%% LDAP attribute that holds user ID:
|
||||
%%{ldap_uids, [{"mail", "%u@mail.example.org"}]}.
|
||||
%%
|
||||
%% LDAP filter:
|
||||
%%{ldap_filter, "(objectClass=shadowAccount)"}.
|
||||
|
||||
%%
|
||||
%% Anonymous login support:
|
||||
@@ -251,14 +284,14 @@
|
||||
%%{host_config, "public.example.org", [{auth_method, [internal, anonymous]}]}.
|
||||
|
||||
|
||||
%%% ==============
|
||||
%%% DATABASE SETUP
|
||||
%%%. ==============
|
||||
%%%' DATABASE SETUP
|
||||
|
||||
%% ejabberd uses by default the internal Mnesia database,
|
||||
%% so you can avoid this section.
|
||||
%% ejabberd by default uses the internal Mnesia database,
|
||||
%% so you do not necessarily need this section.
|
||||
%% This section provides configuration examples in case
|
||||
%% you want to use other database backends.
|
||||
%% Please consult the ejabberd Guide for details about database creation.
|
||||
%% Please consult the ejabberd Guide for details on database creation.
|
||||
|
||||
%%
|
||||
%% MySQL server:
|
||||
@@ -286,27 +319,44 @@
|
||||
%%
|
||||
%%{odbc_server, "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"}.
|
||||
|
||||
|
||||
%%% ===============
|
||||
%%% TRAFFIC SHAPERS
|
||||
%%
|
||||
%% Number of connections to open to the database for each virtual host
|
||||
%%
|
||||
%%{odbc_pool_size, 10}.
|
||||
|
||||
%%
|
||||
%% The "normal" shaper limits traffic speed to 1.000 B/s
|
||||
%% Interval to make a dummy SQL request to keep the connections to the
|
||||
%% database alive. Specify in seconds: for example 28800 means 8 hours
|
||||
%%
|
||||
%%{odbc_keepalive_interval, undefined}.
|
||||
|
||||
|
||||
%%%. ===============
|
||||
%%%' TRAFFIC SHAPERS
|
||||
|
||||
%%
|
||||
%% The "normal" shaper limits traffic speed to 1000 B/s
|
||||
%%
|
||||
{shaper, normal, {maxrate, 1000}}.
|
||||
|
||||
%%
|
||||
%% The "fast" shaper limits traffic speed to 50.000 B/s
|
||||
%% The "fast" shaper limits traffic speed to 50000 B/s
|
||||
%%
|
||||
{shaper, fast, {maxrate, 50000}}.
|
||||
|
||||
%%
|
||||
%% This option specifies the maximum number of elements in the queue
|
||||
%% of the FSM. Refer to the documentation for details.
|
||||
%%
|
||||
{max_fsm_queue, 1000}.
|
||||
|
||||
%%% ====================
|
||||
%%% ACCESS CONTROL LISTS
|
||||
|
||||
%%%. ====================
|
||||
%%%' ACCESS CONTROL LISTS
|
||||
|
||||
%%
|
||||
%% The 'admin' ACL grants administrative privileges to Jabber accounts.
|
||||
%% You can put as many accounts as you want.
|
||||
%% The 'admin' ACL grants administrative privileges to XMPP accounts.
|
||||
%% You can put here as many accounts as you want.
|
||||
%%
|
||||
%%{acl, admin, {user, "aleksey", "localhost"}}.
|
||||
%%{acl, admin, {user, "ermine", "example.org"}}.
|
||||
@@ -330,13 +380,25 @@
|
||||
%%{acl, test, {user_regexp, "^test"}}.
|
||||
%%{acl, test, {user_glob, "test*"}}.
|
||||
|
||||
%%
|
||||
%% Define specific ACLs in a virtual host.
|
||||
%%
|
||||
%%{host_config, "localhost",
|
||||
%% [
|
||||
%% {acl, admin, {user, "bob-local", "localhost"}}
|
||||
%% ]
|
||||
%%}.
|
||||
|
||||
%%% ============
|
||||
%%% ACCESS RULES
|
||||
|
||||
%% Define the maximum number of time a single user is allowed to connect:
|
||||
%%%. ============
|
||||
%%%' ACCESS RULES
|
||||
|
||||
%% Maximum number of simultaneous sessions allowed for a single user:
|
||||
{access, max_user_sessions, [{10, all}]}.
|
||||
|
||||
%% Maximum number of offline messages that users can have:
|
||||
{access, max_user_offline_messages, [{5000, admin}, {100, all}]}.
|
||||
|
||||
%% This rule allows access only for local users:
|
||||
{access, local, [{allow, local}]}.
|
||||
|
||||
@@ -344,44 +406,86 @@
|
||||
{access, c2s, [{deny, blocked},
|
||||
{allow, all}]}.
|
||||
|
||||
%% For all users except admins used "normal" shaper
|
||||
%% For C2S connections, all users except admins use the "normal" shaper
|
||||
{access, c2s_shaper, [{none, admin},
|
||||
{normal, all}]}.
|
||||
|
||||
%% For all S2S connections used "fast" shaper
|
||||
%% All S2S connections use the "fast" shaper
|
||||
{access, s2s_shaper, [{fast, all}]}.
|
||||
|
||||
%% Only admins can send announcement messages:
|
||||
{access, announce, [{allow, admin}]}.
|
||||
|
||||
%% Only admins can use configuration interface:
|
||||
%% Only admins can use the configuration interface:
|
||||
{access, configure, [{allow, admin}]}.
|
||||
|
||||
%% Admins of this server are also admins of MUC service:
|
||||
%% Admins of this server are also admins of the MUC service:
|
||||
{access, muc_admin, [{allow, admin}]}.
|
||||
|
||||
%% All users are allowed to use MUC service:
|
||||
%% Only accounts of the local ejabberd server can create rooms:
|
||||
{access, muc_create, [{allow, local}]}.
|
||||
|
||||
%% All users are allowed to use the MUC service:
|
||||
{access, muc, [{allow, all}]}.
|
||||
|
||||
%% Every username can be registered via in-band registration:
|
||||
%% Only accounts on the local ejabberd server can create Pubsub nodes:
|
||||
{access, pubsub_createnode, [{allow, local}]}.
|
||||
|
||||
%% In-band registration allows registration of any possible username.
|
||||
%% To disable in-band registration, replace 'allow' with 'deny'.
|
||||
{access, register, [{allow, all}]}.
|
||||
|
||||
%% Everybody can create pubsub nodes
|
||||
{access, pubsub_createnode, [{allow, all}]}.
|
||||
%% By default the frequency of account registrations from the same IP
|
||||
%% is limited to 1 account every 10 minutes. To disable, specify: infinity
|
||||
%%{registration_timeout, 600}.
|
||||
|
||||
%%
|
||||
%% Define specific Access Rules in a virtual host.
|
||||
%%
|
||||
%%{host_config, "localhost",
|
||||
%% [
|
||||
%% {access, c2s, [{allow, admin}, {deny, all}]},
|
||||
%% {access, register, [{deny, all}]}
|
||||
%% ]
|
||||
%%}.
|
||||
|
||||
|
||||
%%% ================
|
||||
%%% DEFAULT LANGUAGE
|
||||
%%%. ================
|
||||
%%%' DEFAULT LANGUAGE
|
||||
|
||||
%%
|
||||
%% language: Default language used for server messages.
|
||||
%%
|
||||
{language, "en"}.
|
||||
|
||||
%%
|
||||
%% Set a different default language in a virtual host.
|
||||
%%
|
||||
%%{host_config, "localhost",
|
||||
%% [{language, "ru"}]
|
||||
%%}.
|
||||
|
||||
%%% =======
|
||||
%%% MODULES
|
||||
|
||||
%%%. =======
|
||||
%%%' CAPTCHA
|
||||
|
||||
%%
|
||||
%% Full path to a script that generates the image.
|
||||
%%
|
||||
%%{captcha_cmd, "/lib/ejabberd/priv/bin/captcha.sh"}.
|
||||
|
||||
%%
|
||||
%% Host for the URL and port where ejabberd listens for CAPTCHA requests.
|
||||
%%
|
||||
%%{captcha_host, "example.org:5280"}.
|
||||
|
||||
%%
|
||||
%% Limit CAPTCHA calls per minute for JID/IP to avoid DoS.
|
||||
%%
|
||||
%%{captcha_limit, 5}.
|
||||
|
||||
%%%. =======
|
||||
%%%' MODULES
|
||||
|
||||
%%
|
||||
%% Modules enabled in all ejabberd virtual hosts.
|
||||
@@ -389,45 +493,84 @@
|
||||
{modules,
|
||||
[
|
||||
{mod_adhoc, []},
|
||||
{mod_announce, [{access, announce}]}, % requires mod_adhoc
|
||||
{mod_caps, []},
|
||||
{mod_announce, [{access, announce}]}, % recommends mod_adhoc
|
||||
{mod_blocking,[]}, % requires mod_privacy
|
||||
{mod_caps, []},
|
||||
{mod_configure,[]}, % requires mod_adhoc
|
||||
{mod_disco, []},
|
||||
%%{mod_echo, [{host, "echo.localhost"}]},
|
||||
{mod_irc, []},
|
||||
{mod_http_bind, []},
|
||||
%%{mod_http_fileserver, [
|
||||
%% {docroot, "/var/www"},
|
||||
%% {accesslog, "/var/log/ejabberd/access.log"}
|
||||
%% ]},
|
||||
{mod_last, []},
|
||||
{mod_muc, [
|
||||
%%{host, "conference.@HOST@"},
|
||||
{access, muc},
|
||||
{access_create, muc},
|
||||
{access_persistent, muc},
|
||||
{access_create, muc_create},
|
||||
{access_persistent, muc_create},
|
||||
{access_admin, muc_admin}
|
||||
]},
|
||||
%%{mod_muc_log,[]},
|
||||
{mod_offline, []},
|
||||
{mod_offline, [{access_max_user_messages, max_user_offline_messages}]},
|
||||
{mod_ping, []},
|
||||
%%{mod_pres_counter,[{count, 5}, {interval, 60}]},
|
||||
{mod_privacy, []},
|
||||
{mod_private, []},
|
||||
%%{mod_proxy65,[]},
|
||||
{mod_pubsub, [ % requires mod_caps
|
||||
{mod_pubsub, [
|
||||
{access_createnode, pubsub_createnode},
|
||||
{plugins, ["default", "pep"]}
|
||||
{ignore_pep_from_offline, true}, % reduces resource comsumption, but XEP incompliant
|
||||
%%{ignore_pep_from_offline, false}, % XEP compliant, but increases resource comsumption
|
||||
{last_item_cache, false},
|
||||
{plugins, ["flat", "hometree", "pep"]} % pep requires mod_caps
|
||||
]},
|
||||
{mod_register, [
|
||||
%%
|
||||
%% After successful registration, the user receives
|
||||
%% a message with this subject and body.
|
||||
%% Protect In-Band account registrations with CAPTCHA.
|
||||
%%
|
||||
{welcome_message, {"Welcome!",
|
||||
"Welcome to this Jabber server."}},
|
||||
%%{captcha_protected, true},
|
||||
|
||||
%%
|
||||
%% When a user registers, send a notification to
|
||||
%% these Jabber accounts.
|
||||
%% Set the minimum informational entropy for passwords.
|
||||
%%
|
||||
%%{password_strength, 32},
|
||||
|
||||
%%
|
||||
%% After successful registration, the user receives
|
||||
%% a message with this subject and body.
|
||||
%%
|
||||
{welcome_message, {"Welcome!",
|
||||
"Hi.\nWelcome to this XMPP server."}},
|
||||
|
||||
%%
|
||||
%% When a user registers, send a notification to
|
||||
%% these XMPP accounts.
|
||||
%%
|
||||
%%{registration_watchers, ["admin1@example.org"]},
|
||||
|
||||
%%
|
||||
%% Only clients in the server machine can register accounts
|
||||
%%
|
||||
{ip_access, [{allow, "127.0.0.0/8"},
|
||||
{deny, "0.0.0.0/0"}]},
|
||||
|
||||
%%
|
||||
%% Local c2s or remote s2s users cannot register accounts
|
||||
%%
|
||||
%%{access_from, deny},
|
||||
|
||||
{access, register}
|
||||
]},
|
||||
%%{mod_register_web, [
|
||||
%%
|
||||
%% When a user registers, send a notification to
|
||||
%% these XMPP accounts.
|
||||
%%
|
||||
%%{registration_watchers, ["admin1@example.org"]}
|
||||
%% ]},
|
||||
{mod_roster, []},
|
||||
%%{mod_service_log,[]},
|
||||
{mod_shared_roster,[]},
|
||||
@@ -437,10 +580,24 @@
|
||||
{mod_version, []}
|
||||
]}.
|
||||
|
||||
%%
|
||||
%% Enable modules with custom options in a specific virtual host
|
||||
%%
|
||||
%%{host_config, "localhost",
|
||||
%% [{{add, modules},
|
||||
%% [
|
||||
%% {mod_echo, [{host, "mirror.localhost"}]}
|
||||
%% ]
|
||||
%% }
|
||||
%% ]}.
|
||||
|
||||
|
||||
%%%.
|
||||
%%%'
|
||||
|
||||
%%% $Id$
|
||||
|
||||
%%% Local Variables:
|
||||
%%% mode: erlang
|
||||
%%% End:
|
||||
%%% vim: set filetype=erlang tabstop=8:
|
||||
%%% vim: set filetype=erlang tabstop=8 foldmarker=%%%',%%%. foldmethod=marker:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 16 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -28,14 +28,16 @@
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0, stop/0,
|
||||
get_so_path/0]).
|
||||
get_pid_file/0,
|
||||
get_so_path/0, get_bin_path/0]).
|
||||
|
||||
start() ->
|
||||
%%ejabberd_cover:start(),
|
||||
application:start(ejabberd).
|
||||
|
||||
stop() ->
|
||||
application:stop(ejabberd).
|
||||
|
||||
%%ejabberd_cover:stop().
|
||||
|
||||
get_so_path() ->
|
||||
case os:getenv("EJABBERD_SO_PATH") of
|
||||
@@ -49,3 +51,27 @@ get_so_path() ->
|
||||
Path ->
|
||||
Path
|
||||
end.
|
||||
|
||||
get_bin_path() ->
|
||||
case os:getenv("EJABBERD_BIN_PATH") of
|
||||
false ->
|
||||
case code:priv_dir(ejabberd) of
|
||||
{error, _} ->
|
||||
".";
|
||||
Path ->
|
||||
filename:join([Path, "bin"])
|
||||
end;
|
||||
Path ->
|
||||
Path
|
||||
end.
|
||||
|
||||
%% @spec () -> false | string()
|
||||
get_pid_file() ->
|
||||
case os:getenv("EJABBERD_PID_PATH") of
|
||||
false ->
|
||||
false;
|
||||
"" ->
|
||||
false;
|
||||
Path ->
|
||||
Path
|
||||
end.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -11,7 +11,7 @@
|
||||
%%% 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
|
||||
@@ -19,14 +19,31 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%-define(ejabberd_debug, true).
|
||||
%-define(DBGFSM, true).
|
||||
%% 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, element(2, application:get_key(ejabberd,vsn))).
|
||||
|
||||
-define(VERSION, "2.0.0-beta1").
|
||||
-define(MYHOSTS, ejabberd_config:get_global_option(hosts)).
|
||||
-define(MYNAME, hd(ejabberd_config:get_global_option(hosts))).
|
||||
-define(MYLANG, ejabberd_config:get_global_option(language)).
|
||||
|
||||
-define(MSGS_DIR, "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).
|
||||
|
||||
%% ---------------------------------
|
||||
%% Logging mechanism
|
||||
|
||||
%% Print in standard output
|
||||
-define(PRINT(Format, Args),
|
||||
io:format(Format, Args)).
|
||||
|
||||
-define(DEBUG(Format, Args),
|
||||
ejabberd_logger:debug_msg(?MODULE,?LINE,Format, Args)).
|
||||
|
||||
@@ -42,15 +59,3 @@
|
||||
-define(CRITICAL_MSG(Format, Args),
|
||||
ejabberd_logger:critical_msg(?MODULE,?LINE,Format, Args)).
|
||||
|
||||
-define(MYHOSTS, ejabberd_config:get_global_option(hosts)).
|
||||
-define(MYNAME, hd(ejabberd_config:get_global_option(hosts))).
|
||||
-define(S2STIMEOUT, 600000).
|
||||
-define(MYLANG, ejabberd_config:get_global_option(language)).
|
||||
|
||||
-define(MSGS_DIR, "msgs").
|
||||
-define(CONFIG_PATH, "ejabberd.cfg").
|
||||
-define(LOG_PATH, "ejabberd.log").
|
||||
|
||||
-define(PRIVACY_SUPPORT, true).
|
||||
|
||||
-define(EJABBERD_URI, "http://www.process-one.net/en/ejabberd/").
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#! /bin/sh
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: ejabberd
|
||||
# Required-Start: $remote_fs $network $named $time
|
||||
# Required-Stop: $remote_fs $network $named $time
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Starts ejabberd jabber server
|
||||
# Description: Starts ejabberd jabber server, an XMPP
|
||||
# compliant server written in Erlang.
|
||||
### END INIT INFO
|
||||
|
||||
# chkconfig: 2345 90 10
|
||||
# description: ejabberd XMPP server
|
||||
|
||||
set -o errexit
|
||||
|
||||
DIR=@ctlscriptpath@
|
||||
CTL="$DIR"/ejabberdctl
|
||||
USER=@installuser@
|
||||
|
||||
test -x "$CTL" || {
|
||||
echo "ERROR: ejabberd not found: $DIR"
|
||||
exit 1
|
||||
}
|
||||
grep ^"$USER": /etc/passwd >/dev/null || {
|
||||
echo "ERROR: System user not found: $USER"
|
||||
exit 2
|
||||
}
|
||||
|
||||
export PATH="${PATH:+$PATH:}/usr/sbin:/sbin"
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
test -x "$CTL" || exit 0
|
||||
echo "Starting ejabberd..."
|
||||
su - $USER -c "$CTL start"
|
||||
su - $USER -c "$CTL started"
|
||||
echo "done."
|
||||
;;
|
||||
stop)
|
||||
test -x "$CTL" || exit 0
|
||||
echo "Stopping ejabberd..."
|
||||
su - $USER -c "$CTL stop"
|
||||
su - $USER -c "$CTL stopped"
|
||||
echo "done."
|
||||
;;
|
||||
status)
|
||||
test -x "$CTL" || exit 0
|
||||
echo "Getting ejabberd status..."
|
||||
su - $USER -c "$CTL status"
|
||||
;;
|
||||
force-reload|restart)
|
||||
"$0" stop
|
||||
"$0" start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|force-reload|status}"
|
||||
exit 1
|
||||
esac
|
||||
|
||||
exit 0
|
||||
@@ -1,15 +1,11 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : ejabberd_admin.erl
|
||||
%%% Author : Mickael Remond <mremond@process-one.net>
|
||||
%%% Description : This module gathers admin functions used by different
|
||||
%%% access method:
|
||||
%%% - ejabberdctl command-line tool
|
||||
%%% - web admin interface
|
||||
%%% - adhoc mode
|
||||
%%% Purpose : Administrative functions and commands
|
||||
%%% Created : 7 May 2006 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -20,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -31,9 +27,400 @@
|
||||
-module(ejabberd_admin).
|
||||
-author('mickael.remond@process-one.net').
|
||||
|
||||
-export([restore/1]).
|
||||
-export([start/0, stop/0,
|
||||
%% Server
|
||||
status/0, reopen_log/0,
|
||||
stop_kindly/2, send_service_message_all_mucs/2,
|
||||
%% Erlang
|
||||
update_list/0, update/1,
|
||||
%% Accounts
|
||||
register/3, unregister/2,
|
||||
registered_users/1,
|
||||
%% Migration jabberd1.4
|
||||
import_file/1, import_dir/1,
|
||||
%% Purge DB
|
||||
delete_expired_messages/0, delete_old_messages/1,
|
||||
%% Mnesia
|
||||
set_master/1,
|
||||
backup_mnesia/1, restore_mnesia/1,
|
||||
dump_mnesia/1, dump_table/2, load_mnesia/1,
|
||||
install_fallback_mnesia/1,
|
||||
dump_to_textfile/1, dump_to_textfile/2,
|
||||
mnesia_change_nodename/4,
|
||||
restore/1 % Still used by some modules
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
|
||||
start() ->
|
||||
ejabberd_commands:register_commands(commands()).
|
||||
|
||||
stop() ->
|
||||
ejabberd_commands:unregister_commands(commands()).
|
||||
|
||||
%%%
|
||||
%%% ejabberd commands
|
||||
%%%
|
||||
|
||||
commands() ->
|
||||
[
|
||||
%% The commands status, stop and restart are implemented also in ejabberd_ctl
|
||||
%% They are defined here so that other interfaces can use them too
|
||||
#ejabberd_commands{name = status, tags = [server],
|
||||
desc = "Get status of the ejabberd server",
|
||||
module = ?MODULE, function = status,
|
||||
args = [], result = {res, restuple}},
|
||||
#ejabberd_commands{name = stop, tags = [server],
|
||||
desc = "Stop ejabberd gracefully",
|
||||
module = init, function = stop,
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = restart, tags = [server],
|
||||
desc = "Restart ejabberd gracefully",
|
||||
module = init, function = restart,
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = reopen_log, tags = [logs, server],
|
||||
desc = "Reopen the log files",
|
||||
module = ?MODULE, function = reopen_log,
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = stop_kindly, tags = [server],
|
||||
desc = "Inform users and rooms, wait, and stop the server",
|
||||
module = ?MODULE, function = stop_kindly,
|
||||
args = [{delay, integer}, {announcement, string}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = get_loglevel, tags = [logs, server],
|
||||
desc = "Get the current loglevel",
|
||||
module = ejabberd_loglevel, function = get,
|
||||
args = [],
|
||||
result = {leveltuple, {tuple, [{levelnumber, integer},
|
||||
{levelatom, atom},
|
||||
{leveldesc, string}
|
||||
]}}},
|
||||
|
||||
#ejabberd_commands{name = update_list, tags = [server],
|
||||
desc = "List modified modules that can be updated",
|
||||
module = ?MODULE, function = update_list,
|
||||
args = [],
|
||||
result = {modules, {list, {module, string}}}},
|
||||
#ejabberd_commands{name = update, tags = [server],
|
||||
desc = "Update the given module, or use the keyword: all",
|
||||
module = ?MODULE, function = update,
|
||||
args = [{module, string}],
|
||||
result = {res, restuple}},
|
||||
|
||||
#ejabberd_commands{name = register, tags = [accounts],
|
||||
desc = "Register a user",
|
||||
module = ?MODULE, function = register,
|
||||
args = [{user, string}, {host, string}, {password, string}],
|
||||
result = {res, restuple}},
|
||||
#ejabberd_commands{name = unregister, tags = [accounts],
|
||||
desc = "Unregister a user",
|
||||
module = ?MODULE, function = unregister,
|
||||
args = [{user, string}, {host, string}],
|
||||
result = {res, restuple}},
|
||||
#ejabberd_commands{name = registered_users, tags = [accounts],
|
||||
desc = "List all registered users in HOST",
|
||||
module = ?MODULE, function = registered_users,
|
||||
args = [{host, string}],
|
||||
result = {users, {list, {username, string}}}},
|
||||
|
||||
#ejabberd_commands{name = import_file, tags = [mnesia],
|
||||
desc = "Import user data from jabberd14 spool file",
|
||||
module = ?MODULE, function = import_file,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = import_dir, tags = [mnesia],
|
||||
desc = "Import users data from jabberd14 spool dir",
|
||||
module = ?MODULE, function = import_dir,
|
||||
args = [{file, string}],
|
||||
result = {res, restuple}},
|
||||
|
||||
#ejabberd_commands{name = import_piefxis, tags = [mnesia],
|
||||
desc = "Import users data from a PIEFXIS file (XEP-0227)",
|
||||
module = ejabberd_piefxis, function = import_file,
|
||||
args = [{file, string}], result = {res, rescode}},
|
||||
#ejabberd_commands{name = export_piefxis, tags = [mnesia],
|
||||
desc = "Export data of all users in the server to PIEFXIS files (XEP-0227)",
|
||||
module = ejabberd_piefxis, function = export_server,
|
||||
args = [{dir, string}], result = {res, rescode}},
|
||||
#ejabberd_commands{name = export_piefxis_host, tags = [mnesia],
|
||||
desc = "Export data of users in a host to PIEFXIS files (XEP-0227)",
|
||||
module = ejabberd_piefxis, function = export_host,
|
||||
args = [{dir, string}, {host, string}], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = delete_expired_messages, tags = [purge],
|
||||
desc = "Delete expired offline messages from database",
|
||||
module = ?MODULE, function = delete_expired_messages,
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = delete_old_messages, tags = [purge],
|
||||
desc = "Delete offline messages older than DAYS",
|
||||
module = ?MODULE, function = delete_old_messages,
|
||||
args = [{days, integer}], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = rename_default_nodeplugin, tags = [mnesia],
|
||||
desc = "Update PubSub table from old ejabberd trunk SVN to 2.1.0",
|
||||
module = mod_pubsub, function = rename_default_nodeplugin,
|
||||
args = [], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = set_master, tags = [mnesia],
|
||||
desc = "Set master node of the clustered Mnesia tables",
|
||||
longdesc = "If you provide as nodename \"self\", this "
|
||||
"node will be set as its own master.",
|
||||
module = ?MODULE, function = set_master,
|
||||
args = [{nodename, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = mnesia_change_nodename, tags = [mnesia],
|
||||
desc = "Change the erlang node name in a backup file",
|
||||
module = ?MODULE, function = mnesia_change_nodename,
|
||||
args = [{oldnodename, string}, {newnodename, string},
|
||||
{oldbackup, string}, {newbackup, string}],
|
||||
result = {res, restuple}},
|
||||
#ejabberd_commands{name = backup, tags = [mnesia],
|
||||
desc = "Store the database to backup file",
|
||||
module = ?MODULE, function = backup_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = restore, tags = [mnesia],
|
||||
desc = "Restore the database from backup file",
|
||||
module = ?MODULE, function = restore_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = dump, tags = [mnesia],
|
||||
desc = "Dump the database to text file",
|
||||
module = ?MODULE, function = dump_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = dump_table, tags = [mnesia],
|
||||
desc = "Dump a table to text file",
|
||||
module = ?MODULE, function = dump_table,
|
||||
args = [{file, string}, {table, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = load, tags = [mnesia],
|
||||
desc = "Restore the database from text file",
|
||||
module = ?MODULE, function = load_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = install_fallback, tags = [mnesia],
|
||||
desc = "Install the database from a fallback file",
|
||||
module = ?MODULE, function = install_fallback_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}}
|
||||
].
|
||||
|
||||
|
||||
%%%
|
||||
%%% Server management
|
||||
%%%
|
||||
|
||||
status() ->
|
||||
{InternalStatus, ProvidedStatus} = init:get_status(),
|
||||
String1 = io_lib:format("The node ~p is ~p. Status: ~p",
|
||||
[node(), InternalStatus, ProvidedStatus]),
|
||||
{Is_running, String2} =
|
||||
case lists:keysearch(ejabberd, 1, application:which_applications()) of
|
||||
false ->
|
||||
{ejabberd_not_running, "ejabberd is not running in that node."};
|
||||
{value, {_, _, Version}} ->
|
||||
{ok, io_lib:format("ejabberd ~s is running in that node", [Version])}
|
||||
end,
|
||||
{Is_running, String1 ++ String2}.
|
||||
|
||||
reopen_log() ->
|
||||
ejabberd_hooks:run(reopen_log_hook, []),
|
||||
%% TODO: Use the Reopen log API for logger_h ?
|
||||
ejabberd_logger_h:reopen_log(),
|
||||
case application:get_env(sasl,sasl_error_logger) of
|
||||
{ok, {file, SASLfile}} ->
|
||||
error_logger:delete_report_handler(sasl_report_file_h),
|
||||
ejabberd_logger_h:rotate_log(SASLfile),
|
||||
error_logger:add_report_handler(sasl_report_file_h,
|
||||
{SASLfile, get_sasl_error_logger_type()});
|
||||
_ -> false
|
||||
end,
|
||||
ok.
|
||||
|
||||
%% Function copied from Erlang/OTP lib/sasl/src/sasl.erl which doesn't export it
|
||||
get_sasl_error_logger_type () ->
|
||||
case application:get_env (sasl, errlog_type) of
|
||||
{ok, error} -> error;
|
||||
{ok, progress} -> progress;
|
||||
{ok, all} -> all;
|
||||
{ok, Bad} -> exit ({bad_config, {sasl, {errlog_type, Bad}}});
|
||||
_ -> all
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% Stop Kindly
|
||||
%%%
|
||||
|
||||
stop_kindly(DelaySeconds, AnnouncementText) ->
|
||||
Subject = io_lib:format("Server stop in ~p seconds!", [DelaySeconds]),
|
||||
WaitingDesc = io_lib:format("Waiting ~p seconds", [DelaySeconds]),
|
||||
Steps = [
|
||||
{"Stopping ejabberd port listeners",
|
||||
ejabberd_listener, stop_listeners, []},
|
||||
{"Sending announcement to connected users",
|
||||
mod_announce, send_announcement_to_all,
|
||||
[?MYNAME, Subject, AnnouncementText]},
|
||||
{"Sending service message to MUC rooms",
|
||||
ejabberd_admin, send_service_message_all_mucs,
|
||||
[Subject, AnnouncementText]},
|
||||
{WaitingDesc, timer, sleep, [DelaySeconds * 1000]},
|
||||
{"Stopping ejabberd", application, stop, [ejabberd]},
|
||||
{"Stopping Mnesia", mnesia, stop, []},
|
||||
{"Stopping Erlang node", init, stop, []}
|
||||
],
|
||||
NumberLast = length(Steps),
|
||||
TimestampStart = calendar:datetime_to_gregorian_seconds({date(), time()}),
|
||||
lists:foldl(
|
||||
fun({Desc, Mod, Func, Args}, NumberThis) ->
|
||||
SecondsDiff =
|
||||
calendar:datetime_to_gregorian_seconds({date(), time()})
|
||||
- TimestampStart,
|
||||
io:format("[~p/~p ~ps] ~s... ",
|
||||
[NumberThis, NumberLast, SecondsDiff, Desc]),
|
||||
Result = apply(Mod, Func, Args),
|
||||
io:format("~p~n", [Result]),
|
||||
NumberThis+1
|
||||
end,
|
||||
1,
|
||||
Steps),
|
||||
ok.
|
||||
|
||||
send_service_message_all_mucs(Subject, AnnouncementText) ->
|
||||
Message = io_lib:format("~s~n~s", [Subject, AnnouncementText]),
|
||||
lists:foreach(
|
||||
fun(ServerHost) ->
|
||||
MUCHost = gen_mod:get_module_opt_host(
|
||||
ServerHost, mod_muc, "conference.@HOST@"),
|
||||
mod_muc:broadcast_service_message(MUCHost, Message)
|
||||
end,
|
||||
?MYHOSTS).
|
||||
|
||||
%%%
|
||||
%%% ejabberd_update
|
||||
%%%
|
||||
|
||||
update_list() ->
|
||||
{ok, _Dir, UpdatedBeams, _Script, _LowLevelScript, _Check} =
|
||||
ejabberd_update:update_info(),
|
||||
[atom_to_list(Beam) || Beam <- UpdatedBeams].
|
||||
|
||||
update("all") ->
|
||||
[update_module(ModStr) || ModStr <- update_list()];
|
||||
update(ModStr) ->
|
||||
update_module(ModStr).
|
||||
|
||||
update_module(ModuleNameString) ->
|
||||
ModuleName = list_to_atom(ModuleNameString),
|
||||
case ejabberd_update:update([ModuleName]) of
|
||||
{ok, Res} -> {ok, io_lib:format("Updated: ~p", [Res])};
|
||||
{error, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% Account management
|
||||
%%%
|
||||
|
||||
register(User, Host, Password) ->
|
||||
case ejabberd_auth:try_register(User, Host, Password) of
|
||||
{atomic, ok} ->
|
||||
{ok, io_lib:format("User ~s@~s successfully registered", [User, Host])};
|
||||
{atomic, exists} ->
|
||||
String = io_lib:format("User ~s@~s already registered at node ~p",
|
||||
[User, Host, node()]),
|
||||
{exists, String};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't register user ~s@~s at node ~p: ~p",
|
||||
[User, Host, node(), Reason]),
|
||||
{cannot_register, String}
|
||||
end.
|
||||
|
||||
unregister(User, Host) ->
|
||||
ejabberd_auth:remove_user(User, Host),
|
||||
{ok, ""}.
|
||||
|
||||
registered_users(Host) ->
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
SUsers = lists:sort(Users),
|
||||
lists:map(fun({U, _S}) -> U end, SUsers).
|
||||
|
||||
|
||||
%%%
|
||||
%%% Migration management
|
||||
%%%
|
||||
|
||||
import_file(Path) ->
|
||||
case jd2ejd:import_file(Path) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't import jabberd14 spool file ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_import_file, String}
|
||||
end.
|
||||
|
||||
import_dir(Path) ->
|
||||
case jd2ejd:import_dir(Path) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't import jabberd14 spool dir ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_import_dir, String}
|
||||
end.
|
||||
|
||||
|
||||
%%%
|
||||
%%% Purge DB
|
||||
%%%
|
||||
|
||||
delete_expired_messages() ->
|
||||
{atomic, ok} = mod_offline:remove_expired_messages(),
|
||||
ok.
|
||||
|
||||
delete_old_messages(Days) ->
|
||||
{atomic, _} = mod_offline:remove_old_messages(Days),
|
||||
ok.
|
||||
|
||||
|
||||
%%%
|
||||
%%% Mnesia management
|
||||
%%%
|
||||
|
||||
set_master("self") ->
|
||||
set_master(node());
|
||||
set_master(NodeString) when is_list(NodeString) ->
|
||||
set_master(list_to_atom(NodeString));
|
||||
set_master(Node) when is_atom(Node) ->
|
||||
case mnesia:set_master_nodes([Node]) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't set master node ~p at node ~p:~n~p",
|
||||
[Node, node(), Reason]),
|
||||
{error, String}
|
||||
end.
|
||||
|
||||
backup_mnesia(Path) ->
|
||||
case mnesia:backup(Path) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't store backup in ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_backup, String}
|
||||
end.
|
||||
|
||||
restore_mnesia(Path) ->
|
||||
case ejabberd_admin:restore(Path) of
|
||||
{atomic, _} ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't restore backup from ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_restore, String};
|
||||
{aborted,{no_exists,Table}} ->
|
||||
String = io_lib:format("Can't restore backup from ~p at node ~p: Table ~p does not exist.",
|
||||
[filename:absname(Path), node(), Table]),
|
||||
{table_not_exists, String};
|
||||
{aborted,enoent} ->
|
||||
String = io_lib:format("Can't restore backup from ~p at node ~p: File not found.",
|
||||
[filename:absname(Path), node()]),
|
||||
{file_not_found, String}
|
||||
end.
|
||||
|
||||
%% Mnesia database restore
|
||||
%% This function is called from ejabberd_ctl, ejabberd_web_admin and
|
||||
@@ -71,3 +458,127 @@ module_tables(mod_roster) -> [roster];
|
||||
module_tables(mod_shared_roster) -> [sr_group, sr_user];
|
||||
module_tables(mod_vcard) -> [vcard, vcard_search];
|
||||
module_tables(_Other) -> [].
|
||||
|
||||
get_local_tables() ->
|
||||
Tabs1 = lists:delete(schema, mnesia:system_info(local_tables)),
|
||||
Tabs = lists:filter(
|
||||
fun(T) ->
|
||||
case mnesia:table_info(T, storage_type) of
|
||||
disc_copies -> true;
|
||||
disc_only_copies -> true;
|
||||
_ -> false
|
||||
end
|
||||
end, Tabs1),
|
||||
Tabs.
|
||||
|
||||
dump_mnesia(Path) ->
|
||||
Tabs = get_local_tables(),
|
||||
dump_tables(Path, Tabs).
|
||||
|
||||
dump_table(Path, STable) ->
|
||||
Table = list_to_atom(STable),
|
||||
dump_tables(Path, [Table]).
|
||||
|
||||
dump_tables(Path, Tables) ->
|
||||
case dump_to_textfile(Path, Tables) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't store dump in ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_dump, String}
|
||||
end.
|
||||
|
||||
dump_to_textfile(File) ->
|
||||
Tabs = get_local_tables(),
|
||||
dump_to_textfile(File, Tabs).
|
||||
|
||||
dump_to_textfile(File, Tabs) ->
|
||||
dump_to_textfile(mnesia:system_info(is_running), Tabs, file:open(File, [write])).
|
||||
dump_to_textfile(yes, Tabs, {ok, F}) ->
|
||||
Defs = lists:map(
|
||||
fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)},
|
||||
{attributes, mnesia:table_info(T, attributes)}]}
|
||||
end,
|
||||
Tabs),
|
||||
io:format(F, "~p.~n", [{tables, Defs}]),
|
||||
lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs),
|
||||
file:close(F);
|
||||
dump_to_textfile(_, _, {ok, F}) ->
|
||||
file:close(F),
|
||||
{error, mnesia_not_running};
|
||||
dump_to_textfile(_, _, {error, Reason}) ->
|
||||
{error, Reason}.
|
||||
|
||||
dump_tab(F, T) ->
|
||||
W = mnesia:table_info(T, wild_pattern),
|
||||
{atomic,All} = mnesia:transaction(
|
||||
fun() -> mnesia:match_object(T, W, read) end),
|
||||
lists:foreach(
|
||||
fun(Term) -> io:format(F,"~p.~n", [setelement(1, Term, T)]) end, All).
|
||||
|
||||
load_mnesia(Path) ->
|
||||
case mnesia:load_textfile(Path) of
|
||||
{atomic, ok} ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't load dump in ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_load, String}
|
||||
end.
|
||||
|
||||
install_fallback_mnesia(Path) ->
|
||||
case mnesia:install_fallback(Path) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't install fallback from ~p at node ~p: ~p",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
{cannot_fallback, String}
|
||||
end.
|
||||
|
||||
mnesia_change_nodename(FromString, ToString, Source, Target) ->
|
||||
From = list_to_atom(FromString),
|
||||
To = list_to_atom(ToString),
|
||||
Switch =
|
||||
fun
|
||||
(Node) when Node == From ->
|
||||
io:format(" - Replacing nodename: '~p' with: '~p'~n", [From, To]),
|
||||
To;
|
||||
(Node) when Node == To ->
|
||||
%% throw({error, already_exists});
|
||||
io:format(" - Node: '~p' will not be modified (it is already '~p')~n", [Node, To]),
|
||||
Node;
|
||||
(Node) ->
|
||||
io:format(" - Node: '~p' will not be modified (it is not '~p')~n", [Node, From]),
|
||||
Node
|
||||
end,
|
||||
Convert =
|
||||
fun
|
||||
({schema, db_nodes, Nodes}, Acc) ->
|
||||
io:format(" +++ db_nodes ~p~n", [Nodes]),
|
||||
{[{schema, db_nodes, lists:map(Switch,Nodes)}], Acc};
|
||||
({schema, version, Version}, Acc) ->
|
||||
io:format(" +++ version: ~p~n", [Version]),
|
||||
{[{schema, version, Version}], Acc};
|
||||
({schema, cookie, Cookie}, Acc) ->
|
||||
io:format(" +++ cookie: ~p~n", [Cookie]),
|
||||
{[{schema, cookie, Cookie}], Acc};
|
||||
({schema, Tab, CreateList}, Acc) ->
|
||||
io:format("~n * Checking table: '~p'~n", [Tab]),
|
||||
Keys = [ram_copies, disc_copies, disc_only_copies],
|
||||
OptSwitch =
|
||||
fun({Key, Val}) ->
|
||||
case lists:member(Key, Keys) of
|
||||
true ->
|
||||
io:format(" + Checking key: '~p'~n", [Key]),
|
||||
{Key, lists:map(Switch, Val)};
|
||||
false-> {Key, Val}
|
||||
end
|
||||
end,
|
||||
Res = {[{schema, Tab, lists:map(OptSwitch, CreateList)}], Acc},
|
||||
Res;
|
||||
(Other, Acc) ->
|
||||
{[Other], Acc}
|
||||
end,
|
||||
mnesia:traverse_backup(Source, Target, Convert, switched).
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_app.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : ejabberd OTP application definition.
|
||||
%%% Purpose : ejabberd's application callback module
|
||||
%%% Created : 31 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -29,41 +29,73 @@
|
||||
|
||||
-behaviour(application).
|
||||
|
||||
-export([start/2, stop/1, init/0]).
|
||||
-export([start_modules/0,start/2, get_log_path/0, prep_stop/1, stop/1, init/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
|
||||
%%%
|
||||
%%% Application API
|
||||
%%%
|
||||
|
||||
start(normal, _Args) ->
|
||||
ejabberd_loglevel:set(4),
|
||||
write_pid_file(),
|
||||
application:start(sasl),
|
||||
randoms:start(),
|
||||
db_init(),
|
||||
sha:start(),
|
||||
catch ssl:start(),
|
||||
stringprep_sup:start_link(),
|
||||
xml:start(),
|
||||
start(),
|
||||
translate:start(),
|
||||
acl:start(),
|
||||
ejabberd_ctl:init(),
|
||||
ejabberd_commands:init(),
|
||||
ejabberd_admin:start(),
|
||||
gen_mod:start(),
|
||||
ejabberd_config:start(),
|
||||
start(),
|
||||
ejabberd_check:config(),
|
||||
connect_nodes(),
|
||||
%% Loading ASN.1 driver explicitly to avoid races in LDAP
|
||||
catch asn1rt:load_driver(),
|
||||
Sup = ejabberd_sup:start_link(),
|
||||
ejabberd_rdbms:start(),
|
||||
ejabberd_auth:start(),
|
||||
cyrsasl:start(),
|
||||
% Profiling
|
||||
%eprof:start(),
|
||||
%eprof:profile([self()]),
|
||||
%fprof:trace(start, "/tmp/fprof"),
|
||||
load_modules(),
|
||||
%ejabberd_debug:eprof_start(),
|
||||
%ejabberd_debug:fprof_start(),
|
||||
maybe_add_nameservers(),
|
||||
start_modules(),
|
||||
ejabberd_listener:start_listeners(),
|
||||
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
|
||||
Sup;
|
||||
start(_, _) ->
|
||||
{error, badarg}.
|
||||
|
||||
stop(_StartArgs) ->
|
||||
%% Prepare the application for termination.
|
||||
%% This function is called when an application is about to be stopped,
|
||||
%% before shutting down the processes of the application.
|
||||
prep_stop(State) ->
|
||||
stop_modules(),
|
||||
ejabberd_admin:stop(),
|
||||
broadcast_c2s_shutdown(),
|
||||
timer:sleep(5000),
|
||||
State.
|
||||
|
||||
%% All the processes were killed when this function is called
|
||||
stop(_State) ->
|
||||
?INFO_MSG("ejabberd ~s is stopped in the node ~p", [?VERSION, node()]),
|
||||
delete_pid_file(),
|
||||
%%ejabberd_debug:stop(),
|
||||
ok.
|
||||
|
||||
|
||||
%%%
|
||||
%%% Internal functions
|
||||
%%%
|
||||
|
||||
start() ->
|
||||
spawn_link(?MODULE, init, []).
|
||||
|
||||
@@ -71,18 +103,7 @@ init() ->
|
||||
register(ejabberd, self()),
|
||||
%erlang:system_flag(fullsweep_after, 0),
|
||||
%error_logger:logfile({open, ?LOG_PATH}),
|
||||
LogPath =
|
||||
case application:get_env(log_path) of
|
||||
{ok, Path} ->
|
||||
Path;
|
||||
undefined ->
|
||||
case os:getenv("EJABBERD_LOG_PATH") of
|
||||
false ->
|
||||
?LOG_PATH;
|
||||
Path ->
|
||||
Path
|
||||
end
|
||||
end,
|
||||
LogPath = get_log_path(),
|
||||
error_logger:add_report_handler(ejabberd_logger_h, LogPath),
|
||||
erl_ddll:load_driver(ejabberd:get_so_path(), tls_drv),
|
||||
case erl_ddll:load_driver(ejabberd:get_so_path(), expat_erl) of
|
||||
@@ -106,10 +127,11 @@ db_init() ->
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
mnesia:start(),
|
||||
application:start(mnesia, permanent),
|
||||
mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity).
|
||||
|
||||
load_modules() ->
|
||||
%% Start all the modules in all the hosts
|
||||
start_modules() ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
case ejabberd_config:get_local_option({modules, Host}) of
|
||||
@@ -123,6 +145,21 @@ load_modules() ->
|
||||
end
|
||||
end, ?MYHOSTS).
|
||||
|
||||
%% Stop all the modules in all the hosts
|
||||
stop_modules() ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
case ejabberd_config:get_local_option({modules, Host}) of
|
||||
undefined ->
|
||||
ok;
|
||||
Modules ->
|
||||
lists:foreach(
|
||||
fun({Module, _Args}) ->
|
||||
gen_mod:stop_module_keep_config(Host, Module)
|
||||
end, Modules)
|
||||
end
|
||||
end, ?MYHOSTS).
|
||||
|
||||
connect_nodes() ->
|
||||
case ejabberd_config:get_local_option(cluster_nodes) of
|
||||
undefined ->
|
||||
@@ -133,5 +170,72 @@ connect_nodes() ->
|
||||
end, Nodes)
|
||||
end.
|
||||
|
||||
%% @spec () -> string()
|
||||
%% @doc Returns the full path to the ejabberd log file.
|
||||
%% It first checks for application configuration parameter 'log_path'.
|
||||
%% If not defined it checks the environment variable EJABBERD_LOG_PATH.
|
||||
%% And if that one is neither defined, returns the default value:
|
||||
%% "ejabberd.log" in current directory.
|
||||
get_log_path() ->
|
||||
case application:get_env(log_path) of
|
||||
{ok, Path} ->
|
||||
Path;
|
||||
undefined ->
|
||||
case os:getenv("EJABBERD_LOG_PATH") of
|
||||
false ->
|
||||
?LOG_PATH;
|
||||
Path ->
|
||||
Path
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
%% If ejabberd is running on some Windows machine, get nameservers and add to Erlang
|
||||
maybe_add_nameservers() ->
|
||||
case os:type() of
|
||||
{win32, _} -> add_windows_nameservers();
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
add_windows_nameservers() ->
|
||||
IPTs = win32_dns:get_nameservers(),
|
||||
?INFO_MSG("Adding machine's DNS IPs to Erlang system:~n~p", [IPTs]),
|
||||
lists:foreach(fun(IPT) -> inet_db:add_ns(IPT) end, IPTs).
|
||||
|
||||
|
||||
broadcast_c2s_shutdown() ->
|
||||
Children = supervisor:which_children(ejabberd_c2s_sup),
|
||||
lists:foreach(
|
||||
fun({_, C2SPid, _, _}) ->
|
||||
C2SPid ! system_shutdown
|
||||
end, Children).
|
||||
|
||||
%%%
|
||||
%%% PID file
|
||||
%%%
|
||||
|
||||
write_pid_file() ->
|
||||
case ejabberd:get_pid_file() of
|
||||
false ->
|
||||
ok;
|
||||
PidFilename ->
|
||||
write_pid_file(os:getpid(), PidFilename)
|
||||
end.
|
||||
|
||||
write_pid_file(Pid, PidFilename) ->
|
||||
case file:open(PidFilename, [write]) of
|
||||
{ok, Fd} ->
|
||||
io:format(Fd, "~s~n", [Pid]),
|
||||
file:close(Fd);
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("Cannot write PID file ~s~nReason: ~p", [PidFilename, Reason]),
|
||||
throw({cannot_write_pid_file, PidFilename, Reason})
|
||||
end.
|
||||
|
||||
delete_pid_file() ->
|
||||
case ejabberd:get_pid_file() of
|
||||
false ->
|
||||
ok;
|
||||
PidFilename ->
|
||||
file:delete(PidFilename)
|
||||
end.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 23 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -34,6 +34,8 @@
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
check_password_with_authmodule/3,
|
||||
check_password_with_authmodule/5,
|
||||
try_register/3,
|
||||
dirty_get_registered_users/0,
|
||||
get_vh_registered_users/1,
|
||||
@@ -42,18 +44,18 @@
|
||||
get_vh_registered_users_number/2,
|
||||
get_password/2,
|
||||
get_password_s/2,
|
||||
get_password_with_authmodule/2,
|
||||
is_user_exists/2,
|
||||
is_user_exists_in_other_modules/3,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
plain_password_required/1,
|
||||
ctl_process_get_registered/3
|
||||
entropy/1
|
||||
]).
|
||||
|
||||
-export([auth_modules/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_ctl.hrl").
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
@@ -73,18 +75,60 @@ plain_password_required(Server) ->
|
||||
M:plain_password_required()
|
||||
end, auth_modules(Server)).
|
||||
|
||||
%% @doc Check if the user and password can login in server.
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% true | false
|
||||
check_password(User, Server, Password) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
M:check_password(User, Server, Password)
|
||||
end, auth_modules(Server)).
|
||||
case check_password_with_authmodule(User, Server, Password) of
|
||||
{true, _AuthModule} -> true;
|
||||
false -> false
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password, StreamID, Digest) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
M:check_password(User, Server, Password, StreamID, Digest)
|
||||
end, auth_modules(Server)).
|
||||
%% @doc Check if the user and password can login in server.
|
||||
%% @spec (User::string(), Server::string(), Password::string(),
|
||||
%% Digest::string(), DigestGen::function()) ->
|
||||
%% true | false
|
||||
check_password(User, Server, Password, Digest, DigestGen) ->
|
||||
case check_password_with_authmodule(User, Server, Password,
|
||||
Digest, DigestGen) of
|
||||
{true, _AuthModule} -> true;
|
||||
false -> false
|
||||
end.
|
||||
|
||||
%% @doc Check if the user and password can login in server.
|
||||
%% The user can login if at least an authentication method accepts the user
|
||||
%% and the password.
|
||||
%% The first authentication method that accepts the credentials is returned.
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% {true, AuthModule} | false
|
||||
%% where
|
||||
%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external
|
||||
%% | ejabberd_auth_internal | ejabberd_auth_ldap
|
||||
%% | ejabberd_auth_odbc | ejabberd_auth_pam
|
||||
check_password_with_authmodule(User, Server, Password) ->
|
||||
check_password_loop(auth_modules(Server), [User, Server, Password]).
|
||||
|
||||
check_password_with_authmodule(User, Server, Password, Digest, DigestGen) ->
|
||||
check_password_loop(auth_modules(Server), [User, Server, Password,
|
||||
Digest, DigestGen]).
|
||||
|
||||
check_password_loop([], _Args) ->
|
||||
false;
|
||||
check_password_loop([AuthModule | AuthModules], Args) ->
|
||||
case apply(AuthModule, check_password, Args) of
|
||||
true ->
|
||||
{true, AuthModule};
|
||||
false ->
|
||||
check_password_loop(AuthModules, Args)
|
||||
end.
|
||||
|
||||
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% ok | {error, ErrorType}
|
||||
%% where ErrorType = empty_password | not_allowed | invalid_jid
|
||||
set_password(_User, _Server, "") ->
|
||||
%% We do not allow empty password
|
||||
{error, empty_password};
|
||||
set_password(User, Server, Password) ->
|
||||
lists:foldl(
|
||||
fun(M, {error, _}) ->
|
||||
@@ -93,6 +137,10 @@ set_password(User, Server, Password) ->
|
||||
Res
|
||||
end, {error, not_allowed}, auth_modules(Server)).
|
||||
|
||||
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, not_allowed}
|
||||
try_register(_User, _Server, "") ->
|
||||
%% We do not allow empty password
|
||||
{error, not_allowed};
|
||||
try_register(User, Server, Password) ->
|
||||
case is_user_exists(User,Server) of
|
||||
true ->
|
||||
@@ -100,12 +148,19 @@ try_register(User, Server, Password) ->
|
||||
false ->
|
||||
case lists:member(jlib:nameprep(Server), ?MYHOSTS) of
|
||||
true ->
|
||||
lists:foldl(
|
||||
Res = lists:foldl(
|
||||
fun(_M, {atomic, ok} = Res) ->
|
||||
Res;
|
||||
(M, _) ->
|
||||
M:try_register(User, Server, Password)
|
||||
end, {error, not_allowed}, auth_modules(Server));
|
||||
end, {error, not_allowed}, auth_modules(Server)),
|
||||
case Res of
|
||||
{atomic, ok} ->
|
||||
ejabberd_hooks:run(register_user, Server,
|
||||
[User, Server]),
|
||||
{atomic, ok};
|
||||
_ -> Res
|
||||
end;
|
||||
false ->
|
||||
{error, not_allowed}
|
||||
end
|
||||
@@ -116,7 +171,7 @@ dirty_get_registered_users() ->
|
||||
lists:flatmap(
|
||||
fun(M) ->
|
||||
M:dirty_get_registered_users()
|
||||
end, auth_modules(?MYNAME)).
|
||||
end, auth_modules()).
|
||||
|
||||
%% Registered users list do not include anonymous users logged
|
||||
get_vh_registered_users(Server) ->
|
||||
@@ -128,7 +183,13 @@ get_vh_registered_users(Server) ->
|
||||
get_vh_registered_users(Server, Opts) ->
|
||||
lists:flatmap(
|
||||
fun(M) ->
|
||||
M:get_vh_registered_users(Server, Opts)
|
||||
case erlang:function_exported(
|
||||
M, get_vh_registered_users, 2) of
|
||||
true ->
|
||||
M:get_vh_registered_users(Server, Opts);
|
||||
false ->
|
||||
M:get_vh_registered_users(Server)
|
||||
end
|
||||
end, auth_modules(Server)).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
@@ -157,6 +218,8 @@ get_vh_registered_users_number(Server, Opts) ->
|
||||
end
|
||||
end, auth_modules(Server))).
|
||||
|
||||
%% @doc Get the password of the user.
|
||||
%% @spec (User::string(), Server::string()) -> Password::string()
|
||||
get_password(User, Server) ->
|
||||
lists:foldl(
|
||||
fun(M, false) ->
|
||||
@@ -173,48 +236,126 @@ get_password_s(User, Server) ->
|
||||
Password
|
||||
end.
|
||||
|
||||
%% @doc Get the password of the user and the auth module.
|
||||
%% @spec (User::string(), Server::string()) ->
|
||||
%% {Password::string(), AuthModule::atom()} | {false, none}
|
||||
get_password_with_authmodule(User, Server) ->
|
||||
lists:foldl(
|
||||
fun(M, {false, _}) ->
|
||||
{M:get_password(User, Server), M};
|
||||
(_M, {Password, AuthModule}) ->
|
||||
{Password, AuthModule}
|
||||
end, {false, none}, auth_modules(Server)).
|
||||
|
||||
%% Returns true if the user exists in the DB or if an anonymous user is logged
|
||||
%% under the given name
|
||||
is_user_exists(User, Server) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
M:is_user_exists(User, Server)
|
||||
case M:is_user_exists(User, Server) of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("The authentication module ~p returned an "
|
||||
"error~nwhen checking user ~p in server ~p~n"
|
||||
"Error message: ~p",
|
||||
[M, User, Server, Error]),
|
||||
false;
|
||||
Else ->
|
||||
Else
|
||||
end
|
||||
end, auth_modules(Server)).
|
||||
|
||||
%% Check if the user exists in all authentications module except the module
|
||||
%% passed as parameter
|
||||
%% @spec (Module::atom(), User, Server) -> true | false | maybe
|
||||
is_user_exists_in_other_modules(Module, User, Server) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
M:is_user_exists(User, Server)
|
||||
end, auth_modules(Server)--[Module]).
|
||||
is_user_exists_in_other_modules_loop(
|
||||
auth_modules(Server)--[Module],
|
||||
User, Server).
|
||||
is_user_exists_in_other_modules_loop([], _User, _Server) ->
|
||||
false;
|
||||
is_user_exists_in_other_modules_loop([AuthModule|AuthModules], User, Server) ->
|
||||
case AuthModule:is_user_exists(User, Server) of
|
||||
true ->
|
||||
true;
|
||||
false ->
|
||||
is_user_exists_in_other_modules_loop(AuthModules, User, Server);
|
||||
{error, Error} ->
|
||||
?DEBUG("The authentication module ~p returned an error~nwhen "
|
||||
"checking user ~p in server ~p~nError message: ~p",
|
||||
[AuthModule, User, Server, Error]),
|
||||
maybe
|
||||
end.
|
||||
|
||||
|
||||
%% @spec (User, Server) -> ok | error | {error, not_allowed}
|
||||
%% @doc Remove user.
|
||||
%% Note: it may return ok even if there was some problem removing the user.
|
||||
remove_user(User, Server) ->
|
||||
lists:foreach(
|
||||
R = lists:foreach(
|
||||
fun(M) ->
|
||||
M:remove_user(User, Server)
|
||||
end, auth_modules(Server)).
|
||||
end, auth_modules(Server)),
|
||||
case R of
|
||||
ok -> ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]);
|
||||
_ -> none
|
||||
end,
|
||||
R.
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error
|
||||
%% @doc Try to remove user if the provided password is correct.
|
||||
%% The removal is attempted in each auth method provided:
|
||||
%% when one returns 'ok' the loop stops;
|
||||
%% if no method returns 'ok' then it returns the error message indicated by the last method attempted.
|
||||
remove_user(User, Server, Password) ->
|
||||
lists:foreach(
|
||||
fun(M) ->
|
||||
R = lists:foldl(
|
||||
fun(_M, ok = Res) ->
|
||||
Res;
|
||||
(M, _) ->
|
||||
M:remove_user(User, Server, Password)
|
||||
end, auth_modules(Server)).
|
||||
end, error, auth_modules(Server)),
|
||||
case R of
|
||||
ok -> ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]);
|
||||
_ -> none
|
||||
end,
|
||||
R.
|
||||
|
||||
|
||||
ctl_process_get_registered(_Val, Host, ["registered-users"]) ->
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
NewLine = io_lib:format("~n", []),
|
||||
SUsers = lists:sort(Users),
|
||||
FUsers = lists:map(fun({U, _S}) -> [U, NewLine] end, SUsers),
|
||||
io:format("~s", [FUsers]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process_get_registered(Val, _Host, _Args) ->
|
||||
Val.
|
||||
%% @spec (IOList) -> non_negative_float()
|
||||
%% @doc Calculate informational entropy.
|
||||
entropy(IOList) ->
|
||||
case binary_to_list(iolist_to_binary(IOList)) of
|
||||
"" ->
|
||||
0.0;
|
||||
S ->
|
||||
Set = lists:foldl(
|
||||
fun(C, [Digit, Printable, LowLetter, HiLetter, Other]) ->
|
||||
if C >= $a, C =< $z ->
|
||||
[Digit, Printable, 26, HiLetter, Other];
|
||||
C >= $0, C =< $9 ->
|
||||
[9, Printable, LowLetter, HiLetter, Other];
|
||||
C >= $A, C =< $Z ->
|
||||
[Digit, Printable, LowLetter, 26, Other];
|
||||
C >= 16#21, C =< 16#7e ->
|
||||
[Digit, 33, LowLetter, HiLetter, Other];
|
||||
true ->
|
||||
[Digit, Printable, LowLetter, HiLetter, 128]
|
||||
end
|
||||
end, [0, 0, 0, 0, 0], S),
|
||||
length(S) * math:log(lists:sum(Set))/math:log(2)
|
||||
end.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
%% Return the lists of all the auth modules actually used in the
|
||||
%% configuration
|
||||
auth_modules() ->
|
||||
lists:usort(
|
||||
lists:flatmap(
|
||||
fun(Server) ->
|
||||
auth_modules(Server)
|
||||
end, ?MYHOSTS)).
|
||||
|
||||
%% Return the list of authenticated modules for a given host
|
||||
auth_modules(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
Method = ejabberd_config:get_local_option({auth_method, LServer}),
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 17 Feb 2006 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -33,8 +33,8 @@
|
||||
is_login_anonymous_enabled/1,
|
||||
anonymous_user_exist/2,
|
||||
allow_multiple_connections/1,
|
||||
register_connection/2,
|
||||
unregister_connection/2
|
||||
register_connection/3,
|
||||
unregister_connection/3
|
||||
]).
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ allow_anonymous(Host) ->
|
||||
%% anonymous protocol can be: sasl_anon|login_anon|both
|
||||
is_sasl_anonymous_enabled(Host) ->
|
||||
case allow_anonymous(Host) of
|
||||
false -> false;
|
||||
false -> false;
|
||||
true ->
|
||||
case anonymous_protocol(Host) of
|
||||
sasl_anon -> true;
|
||||
@@ -97,7 +97,7 @@ is_login_anonymous_enabled(Host) ->
|
||||
true ->
|
||||
case anonymous_protocol(Host) of
|
||||
login_anon -> true;
|
||||
both -> true;
|
||||
both -> true;
|
||||
_Other -> false
|
||||
end
|
||||
end.
|
||||
@@ -115,10 +115,8 @@ anonymous_protocol(Host) ->
|
||||
%% Return true if multiple connections have been allowed in the config file
|
||||
%% defaults to false
|
||||
allow_multiple_connections(Host) ->
|
||||
case ejabberd_config:get_local_option({allow_multiple_connections, Host}) of
|
||||
true -> true;
|
||||
_Other -> false
|
||||
end.
|
||||
ejabberd_config:get_local_option(
|
||||
{allow_multiple_connections, Host}) =:= true.
|
||||
|
||||
%% Check if user exist in the anonymus database
|
||||
anonymous_user_exist(User, Server) ->
|
||||
@@ -141,14 +139,21 @@ remove_connection(SID, LUser, LServer) ->
|
||||
mnesia:transaction(F).
|
||||
|
||||
%% Register connection
|
||||
register_connection(SID, #jid{luser = LUser, lserver = LServer}) ->
|
||||
US = {LUser, LServer},
|
||||
mnesia:sync_dirty(
|
||||
fun() -> mnesia:write(#anonymous{us = US, sid=SID})
|
||||
end).
|
||||
register_connection(SID, #jid{luser = LUser, lserver = LServer}, Info) ->
|
||||
AuthModule = xml:get_attr_s(auth_module, Info),
|
||||
case AuthModule == ?MODULE of
|
||||
true ->
|
||||
ejabberd_hooks:run(register_user, LServer, [LUser, LServer]),
|
||||
US = {LUser, LServer},
|
||||
mnesia:sync_dirty(
|
||||
fun() -> mnesia:write(#anonymous{us = US, sid=SID})
|
||||
end);
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%% Remove an anonymous user from the anonymous users table
|
||||
unregister_connection(SID, #jid{luser = LUser, lserver = LServer}) ->
|
||||
unregister_connection(SID, #jid{luser = LUser, lserver = LServer}, _) ->
|
||||
purge_hook(anonymous_user_exist(LUser, LServer),
|
||||
LUser, LServer),
|
||||
remove_connection(SID, LUser, LServer).
|
||||
@@ -167,12 +172,15 @@ purge_hook(true, LUser, LServer) ->
|
||||
%% before allowing access
|
||||
check_password(User, Server, Password) ->
|
||||
check_password(User, Server, Password, undefined, undefined).
|
||||
check_password(User, Server, _Password, _StreamID, _Digest) ->
|
||||
check_password(User, Server, _Password, _Digest, _DigestGen) ->
|
||||
%% We refuse login for registered accounts (They cannot logged but
|
||||
%% they however are "reserved")
|
||||
case ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
|
||||
case ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
|
||||
User, Server) of
|
||||
%% If user exists in other module, reject anonnymous authentication
|
||||
true -> false;
|
||||
%% If we are not sure whether the user exists in other module, reject anon auth
|
||||
maybe -> false;
|
||||
false -> login(User, Server)
|
||||
end.
|
||||
|
||||
@@ -208,8 +216,8 @@ try_register(_User, _Server, _Password) ->
|
||||
dirty_get_registered_users() ->
|
||||
[].
|
||||
|
||||
get_vh_registered_users(_Server) ->
|
||||
[].
|
||||
get_vh_registered_users(Server) ->
|
||||
[{U, S} || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Server)].
|
||||
|
||||
|
||||
%% Return password of permanent user or false for anonymous users
|
||||
@@ -217,7 +225,7 @@ get_password(User, Server) ->
|
||||
get_password(User, Server, "").
|
||||
|
||||
get_password(User, Server, DefaultValue) ->
|
||||
case anonymous_user_exist(User, Server) of
|
||||
case anonymous_user_exist(User, Server) or login(User, Server) of
|
||||
%% We return the default value if the user is anonymous
|
||||
true ->
|
||||
DefaultValue;
|
||||
@@ -239,4 +247,3 @@ remove_user(_User, _Server, _Password) ->
|
||||
|
||||
plain_password_required() ->
|
||||
false.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -35,6 +35,9 @@
|
||||
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,
|
||||
@@ -43,47 +46,271 @@
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
extauth:start(
|
||||
Host, ejabberd_config:get_local_option({extauth_program, Host})),
|
||||
ok.
|
||||
case check_cache_last_options(Host) of
|
||||
cache ->
|
||||
ok = ejabberd_auth_internal:start(Host);
|
||||
no_cache ->
|
||||
ok
|
||||
end.
|
||||
|
||||
check_cache_last_options(Server) ->
|
||||
%% if extauth_cache is enabled, then a mod_last module must also be enabled
|
||||
case get_cache_option(Server) of
|
||||
false -> no_cache;
|
||||
{true, _CacheTime} ->
|
||||
case get_mod_last_configured(Server) of
|
||||
no_mod_last ->
|
||||
?ERROR_MSG("In host ~p extauth is used, extauth_cache is enabled but "
|
||||
"mod_last is not enabled.", [Server]),
|
||||
no_cache;
|
||||
_ -> cache
|
||||
end
|
||||
end.
|
||||
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
extauth:check_password(User, Server, Password).
|
||||
case get_cache_option(Server) of
|
||||
false -> check_password_extauth(User, Server, Password);
|
||||
{true, CacheTime} -> check_password_cache(User, Server, Password, CacheTime)
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password, _StreamID, _Digest) ->
|
||||
check_password(User, Server, Password, _Digest, _DigestGen) ->
|
||||
check_password(User, Server, Password).
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
extauth:set_password(User, Server, Password).
|
||||
case extauth:set_password(User, Server, Password) of
|
||||
true -> set_password_internal(User, Server, Password),
|
||||
ok;
|
||||
_ -> {error, unknown_problem}
|
||||
end.
|
||||
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
try_register(User, Server, Password) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> try_register_extauth(User, Server, Password);
|
||||
{true, _CacheTime} -> try_register_external_cache(User, Server, Password)
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
[].
|
||||
ejabberd_auth_internal:dirty_get_registered_users().
|
||||
|
||||
get_vh_registered_users(_Server) ->
|
||||
[].
|
||||
get_vh_registered_users(Server) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users(Server).
|
||||
|
||||
get_password(_User, _Server) ->
|
||||
false.
|
||||
get_vh_registered_users(Server, Data) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users(Server, Data).
|
||||
|
||||
get_password_s(_User, _Server) ->
|
||||
"".
|
||||
get_vh_registered_users_number(Server) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users_number(Server).
|
||||
|
||||
get_vh_registered_users_number(Server, Data) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users_number(Server, Data).
|
||||
|
||||
%% The password can only be returned if cache is enabled, cached info exists and is fresh enough.
|
||||
get_password(User, Server) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, CacheTime} -> get_password_cache(User, Server, CacheTime)
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case get_password(User, Server) of
|
||||
false -> [];
|
||||
Other -> Other
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
is_user_exists(User, Server) ->
|
||||
extauth:is_user_exists(User, Server).
|
||||
try extauth:is_user_exists(User, Server) of
|
||||
Res -> Res
|
||||
catch
|
||||
_:Error -> {error, Error}
|
||||
end.
|
||||
|
||||
remove_user(_User, _Server) ->
|
||||
{error, not_allowed}.
|
||||
remove_user(User, Server) ->
|
||||
case extauth:remove_user(User, Server) of
|
||||
false -> false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_internal:remove_user(User, Server)
|
||||
end
|
||||
end.
|
||||
|
||||
remove_user(_User, _Server, _Password) ->
|
||||
not_allowed.
|
||||
remove_user(User, Server, Password) ->
|
||||
case extauth:remove_user(User, Server, Password) of
|
||||
false -> false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_internal:remove_user(User, Server, Password)
|
||||
end
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% Extauth cache management
|
||||
%%%
|
||||
|
||||
%% @spec (Host::string()) -> false | {true, CacheTime::integer()}
|
||||
get_cache_option(Host) ->
|
||||
case ejabberd_config:get_local_option({extauth_cache, Host}) of
|
||||
CacheTime when is_integer(CacheTime) -> {true, CacheTime};
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false
|
||||
check_password_extauth(User, Server, Password) ->
|
||||
extauth:check_password(User, Server, Password) andalso Password /= "".
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false
|
||||
try_register_extauth(User, Server, Password) ->
|
||||
extauth:try_register(User, Server, Password).
|
||||
|
||||
check_password_cache(User, Server, Password, CacheTime) ->
|
||||
case get_last_access(User, Server) of
|
||||
online ->
|
||||
check_password_internal(User, Server, Password);
|
||||
never ->
|
||||
check_password_external_cache(User, Server, Password);
|
||||
mod_last_required ->
|
||||
?ERROR_MSG("extauth is used, extauth_cache is enabled but mod_last is not enabled in that host", []),
|
||||
check_password_external_cache(User, Server, Password);
|
||||
TimeStamp ->
|
||||
%% If last access exists, compare last access with cache refresh time
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
%% If no need to refresh, check password against Mnesia
|
||||
true ->
|
||||
case check_password_internal(User, Server, Password) of
|
||||
%% If password valid in Mnesia, accept it
|
||||
true ->
|
||||
true;
|
||||
%% Else (password nonvalid in Mnesia), check in extauth and cache result
|
||||
false ->
|
||||
check_password_external_cache(User, Server, Password)
|
||||
end;
|
||||
%% Else (need to refresh), check in extauth and cache result
|
||||
false ->
|
||||
check_password_external_cache(User, Server, Password)
|
||||
end
|
||||
end.
|
||||
|
||||
get_password_internal(User, Server) ->
|
||||
ejabberd_auth_internal:get_password(User, Server).
|
||||
|
||||
%% @spec (User, Server, CacheTime) -> false | Password::string()
|
||||
get_password_cache(User, Server, CacheTime) ->
|
||||
case get_last_access(User, Server) of
|
||||
online ->
|
||||
get_password_internal(User, Server);
|
||||
never ->
|
||||
false;
|
||||
mod_last_required ->
|
||||
?ERROR_MSG("extauth is used, extauth_cache is enabled but mod_last is not enabled in that host", []),
|
||||
false;
|
||||
TimeStamp ->
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
true ->
|
||||
get_password_internal(User, Server);
|
||||
false ->
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
%% Check the password using extauth; if success then cache it
|
||||
check_password_external_cache(User, Server, Password) ->
|
||||
case check_password_extauth(User, Server, Password) of
|
||||
true ->
|
||||
set_password_internal(User, Server, Password), true;
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% Try to register using extauth; if success then cache it
|
||||
try_register_external_cache(User, Server, Password) ->
|
||||
case try_register_extauth(User, Server, Password) of
|
||||
{atomic, ok} = R ->
|
||||
set_password_internal(User, Server, Password),
|
||||
R;
|
||||
_ -> {error, not_allowed}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false
|
||||
check_password_internal(User, Server, Password) ->
|
||||
ejabberd_auth_internal:check_password(User, Server, Password).
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
|
||||
set_password_internal(User, Server, Password) ->
|
||||
ejabberd_auth_internal:set_password(User, Server, Password).
|
||||
|
||||
%% @spec (TimeLast, CacheTime) -> true | false
|
||||
%% TimeLast = online | never | integer()
|
||||
%% CacheTime = integer() | false
|
||||
is_fresh_enough(online, _CacheTime) ->
|
||||
true;
|
||||
is_fresh_enough(never, _CacheTime) ->
|
||||
false;
|
||||
is_fresh_enough(TimeStampLast, CacheTime) ->
|
||||
{MegaSecs, Secs, _MicroSecs} = now(),
|
||||
Now = MegaSecs * 1000000 + Secs,
|
||||
(TimeStampLast + CacheTime > Now).
|
||||
|
||||
%% @spec (User, Server) -> online | never | mod_last_required | TimeStamp::integer()
|
||||
%% Code copied from mod_configure.erl
|
||||
%% Code copied from web/ejabberd_web_admin.erl
|
||||
%% TODO: Update time format to XEP-0202: Entity Time
|
||||
get_last_access(User, Server) ->
|
||||
case ejabberd_sm:get_user_resources(User, Server) of
|
||||
[] ->
|
||||
_US = {User, Server},
|
||||
case get_last_info(User, Server) of
|
||||
mod_last_required ->
|
||||
mod_last_required;
|
||||
not_found ->
|
||||
never;
|
||||
{ok, Timestamp, _Status} ->
|
||||
Timestamp
|
||||
end;
|
||||
_ ->
|
||||
online
|
||||
end.
|
||||
%% @spec (User, Server) -> {ok, Timestamp, Status} | not_found | mod_last_required
|
||||
get_last_info(User, Server) ->
|
||||
case get_mod_last_enabled(Server) of
|
||||
mod_last -> mod_last:get_last_info(User, Server);
|
||||
mod_last_odbc -> mod_last_odbc:get_last_info(User, Server);
|
||||
no_mod_last -> mod_last_required
|
||||
end.
|
||||
|
||||
%% @spec (Server) -> mod_last | mod_last_odbc | no_mod_last
|
||||
get_mod_last_enabled(Server) ->
|
||||
ML = gen_mod:is_loaded(Server, mod_last),
|
||||
MLO = gen_mod:is_loaded(Server, mod_last_odbc),
|
||||
case {ML, MLO} of
|
||||
{true, _} -> mod_last;
|
||||
{false, true} -> mod_last_odbc;
|
||||
{false, false} -> no_mod_last
|
||||
end.
|
||||
|
||||
get_mod_last_configured(Server) ->
|
||||
ML = is_configured(Server, mod_last),
|
||||
MLO = is_configured(Server, mod_last_odbc),
|
||||
case {ML, MLO} of
|
||||
{true, _} -> mod_last;
|
||||
{false, true} -> mod_last_odbc;
|
||||
{false, false} -> no_mod_last
|
||||
end.
|
||||
|
||||
is_configured(Host, Module) ->
|
||||
lists:keymember(Module, 1, ejabberd_config:get_local_option({modules, Host})).
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -49,6 +49,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(passwd, {us, password}).
|
||||
-record(reg_users_counter, {vhost, count}).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
@@ -56,13 +57,23 @@
|
||||
start(Host) ->
|
||||
mnesia:create_table(passwd, [{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, passwd)}]),
|
||||
mnesia:create_table(reg_users_counter,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, reg_users_counter)}]),
|
||||
update_table(),
|
||||
ejabberd_ctl:register_commands(
|
||||
Host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered),
|
||||
update_reg_users_counter_table(Host),
|
||||
ok.
|
||||
|
||||
update_reg_users_counter_table(Server) ->
|
||||
Set = get_vh_registered_users(Server),
|
||||
Size = length(Set),
|
||||
LServer = jlib:nameprep(Server),
|
||||
F = fun() ->
|
||||
mnesia:write(#reg_users_counter{vhost = LServer,
|
||||
count = Size})
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
plain_password_required() ->
|
||||
false.
|
||||
|
||||
@@ -72,12 +83,12 @@ check_password(User, Server, Password) ->
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Password}] ->
|
||||
true;
|
||||
Password /= "";
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password, StreamID, Digest) ->
|
||||
check_password(User, Server, Password, Digest, DigestGen) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
@@ -85,7 +96,7 @@ check_password(User, Server, Password, StreamID, Digest) ->
|
||||
[#passwd{password = Passwd}] ->
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
Digest == sha:sha(StreamID ++ Passwd);
|
||||
Digest == DigestGen(Passwd);
|
||||
true ->
|
||||
false
|
||||
end,
|
||||
@@ -98,6 +109,8 @@ check_password(User, Server, Password, StreamID, Digest) ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% ok | {error, invalid_jid}
|
||||
set_password(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
@@ -110,10 +123,11 @@ set_password(User, Server, Password) ->
|
||||
mnesia:write(#passwd{us = US,
|
||||
password = Password})
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
{atomic, ok} = mnesia:transaction(F),
|
||||
ok
|
||||
end.
|
||||
|
||||
|
||||
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {aborted, Reason}
|
||||
try_register(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
@@ -127,6 +141,9 @@ try_register(User, Server, Password) ->
|
||||
[] ->
|
||||
mnesia:write(#passwd{us = US,
|
||||
password = Password}),
|
||||
mnesia:dirty_update_counter(
|
||||
reg_users_counter,
|
||||
LServer, 1),
|
||||
ok;
|
||||
[_E] ->
|
||||
exists
|
||||
@@ -135,6 +152,7 @@ try_register(User, Server, Password) ->
|
||||
mnesia:transaction(F)
|
||||
end.
|
||||
|
||||
%% Get all registered users in Mnesia
|
||||
dirty_get_registered_users() ->
|
||||
mnesia:dirty_all_keys(passwd).
|
||||
|
||||
@@ -193,8 +211,17 @@ get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
Set = get_vh_registered_users(Server),
|
||||
length(Set).
|
||||
LServer = jlib:nameprep(Server),
|
||||
Query = mnesia:dirty_select(
|
||||
reg_users_counter,
|
||||
[{#reg_users_counter{vhost = LServer, count = '$1'},
|
||||
[],
|
||||
['$1']}]),
|
||||
case Query of
|
||||
[Count] ->
|
||||
Count;
|
||||
_ -> 0
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server, [{prefix, Prefix}]) when is_list(Prefix) ->
|
||||
Set = [{U, S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)],
|
||||
@@ -225,6 +252,7 @@ get_password_s(User, Server) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
is_user_exists(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
@@ -234,20 +262,27 @@ is_user_exists(User, Server) ->
|
||||
false;
|
||||
[_] ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
Other ->
|
||||
{error, Other}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> ok
|
||||
%% @doc Remove user.
|
||||
%% Note: it returns ok even if there was some problem removing the user.
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:delete({passwd, US})
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, -1)
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
ejabberd_hooks:run(remove_user, LServer, [User, Server]).
|
||||
ok.
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request
|
||||
%% @doc Remove user if the provided password is correct.
|
||||
remove_user(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
@@ -256,6 +291,8 @@ remove_user(User, Server, Password) ->
|
||||
case mnesia:read({passwd, US}) of
|
||||
[#passwd{password = Password}] ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, -1),
|
||||
ok;
|
||||
[_] ->
|
||||
not_allowed;
|
||||
@@ -265,7 +302,6 @@ remove_user(User, Server, Password) ->
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
ejabberd_hooks:run(remove_user, LServer, [User, Server]),
|
||||
ok;
|
||||
{atomic, Res} ->
|
||||
Res;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -48,6 +48,7 @@
|
||||
try_register/3,
|
||||
dirty_get_registered_users/0,
|
||||
get_vh_registered_users/1,
|
||||
get_vh_registered_users_number/1,
|
||||
get_password/2,
|
||||
get_password_s/2,
|
||||
is_user_exists/2,
|
||||
@@ -65,6 +66,7 @@
|
||||
servers,
|
||||
backups,
|
||||
port,
|
||||
tls_options,
|
||||
dn,
|
||||
password,
|
||||
base,
|
||||
@@ -85,6 +87,10 @@ handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
%% -----
|
||||
|
||||
|
||||
-define(LDAP_SEARCH_TIMEOUT, 5). % Timeout for LDAP search queries in seconds
|
||||
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -107,30 +113,25 @@ start_link(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, Host, []).
|
||||
|
||||
terminate(_Reason, State) ->
|
||||
ejabberd_ctl:unregister_commands(
|
||||
State#state.host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered).
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
init(Host) ->
|
||||
State = parse_options(Host),
|
||||
eldap_pool:start_link(State#state.eldap_id,
|
||||
State#state.servers,
|
||||
State#state.backups,
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password),
|
||||
State#state.servers,
|
||||
State#state.backups,
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password,
|
||||
State#state.tls_options),
|
||||
eldap_pool:start_link(State#state.bind_eldap_id,
|
||||
State#state.servers,
|
||||
State#state.backups,
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password),
|
||||
ejabberd_ctl:register_commands(
|
||||
Host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered),
|
||||
State#state.servers,
|
||||
State#state.backups,
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password,
|
||||
State#state.tls_options),
|
||||
{ok, State}.
|
||||
|
||||
plain_password_required() ->
|
||||
@@ -149,17 +150,28 @@ check_password(User, Server, Password) ->
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password, _StreamID, _Digest) ->
|
||||
check_password(User, Server, Password, _Digest, _DigestGen) ->
|
||||
check_password(User, Server, Password).
|
||||
|
||||
set_password(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
set_password(User, Server, Password) ->
|
||||
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
|
||||
case find_user_dn(User, State) of
|
||||
false ->
|
||||
{error, user_not_found};
|
||||
DN ->
|
||||
eldap_pool:modify_passwd(State#state.eldap_id, DN, Password)
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> {error, not_allowed}
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
get_vh_registered_users(?MYNAME).
|
||||
Servers = ejabberd_config:get_vh_by_auth_method(ldap),
|
||||
lists:flatmap(
|
||||
fun(Server) ->
|
||||
get_vh_registered_users(Server)
|
||||
end, Servers).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
case catch get_vh_registered_users_ldap(Server) of
|
||||
@@ -167,16 +179,20 @@ get_vh_registered_users(Server) ->
|
||||
Result -> Result
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
length(get_vh_registered_users(Server)).
|
||||
|
||||
get_password(_User, _Server) ->
|
||||
false.
|
||||
|
||||
get_password_s(_User, _Server) ->
|
||||
"".
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
is_user_exists(User, Server) ->
|
||||
case catch is_user_exists_ldap(User, Server) of
|
||||
{'EXIT', _} ->
|
||||
false;
|
||||
{'EXIT', Error} ->
|
||||
{error, Error};
|
||||
Result ->
|
||||
Result
|
||||
end.
|
||||
@@ -207,12 +223,13 @@ get_vh_registered_users_ldap(Server) ->
|
||||
UIDs = State#state.uids,
|
||||
Eldap_ID = State#state.eldap_id,
|
||||
Server = State#state.host,
|
||||
SortedDNAttrs = eldap_utils:usort_attrs(State#state.dn_filter_attrs),
|
||||
ResAttrs = result_attrs(State),
|
||||
case eldap_filter:parse(State#state.sfilter) of
|
||||
{ok, EldapFilter} ->
|
||||
case eldap_pool:search(Eldap_ID, [{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{attributes, SortedDNAttrs}]) of
|
||||
{timeout, ?LDAP_SEARCH_TIMEOUT},
|
||||
{attributes, ResAttrs}]) of
|
||||
#eldap_search_result{entries = Entries} ->
|
||||
lists:flatmap(
|
||||
fun(#eldap_entry{attributes = Attrs,
|
||||
@@ -258,15 +275,16 @@ handle_call(_Request, _From, State) ->
|
||||
{reply, bad_request, State}.
|
||||
|
||||
find_user_dn(User, State) ->
|
||||
DNAttrs = eldap_utils:usort_attrs(State#state.dn_filter_attrs),
|
||||
ResAttrs = result_attrs(State),
|
||||
case eldap_filter:parse(State#state.ufilter, [{"%u", User}]) of
|
||||
{ok, Filter} ->
|
||||
case eldap_pool:search(State#state.eldap_id, [{base, State#state.base},
|
||||
{filter, Filter},
|
||||
{attributes, DNAttrs}]) of
|
||||
case eldap_pool:search(State#state.eldap_id,
|
||||
[{base, State#state.base},
|
||||
{filter, Filter},
|
||||
{attributes, ResAttrs}]) of
|
||||
#eldap_search_result{entries = [#eldap_entry{attributes = Attrs,
|
||||
object_name = DN} | _]} ->
|
||||
dn_filter(DN, Attrs, State);
|
||||
dn_filter(DN, Attrs, State);
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
@@ -335,6 +353,14 @@ local_filter(equal, Attrs, FilterMatch) ->
|
||||
local_filter(notequal, Attrs, FilterMatch) ->
|
||||
not local_filter(equal, Attrs, FilterMatch).
|
||||
|
||||
result_attrs(#state{uids = UIDs, dn_filter_attrs = DNFilterAttrs}) ->
|
||||
lists:foldl(
|
||||
fun({UID}, Acc) ->
|
||||
[UID | Acc];
|
||||
({UID, _}, Acc) ->
|
||||
[UID | Acc]
|
||||
end, DNFilterAttrs, UIDs).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Auxiliary functions
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -346,8 +372,14 @@ parse_options(Host) ->
|
||||
undefined -> [];
|
||||
Backups -> Backups
|
||||
end,
|
||||
LDAPEncrypt = ejabberd_config:get_local_option({ldap_encrypt, Host}),
|
||||
LDAPTLSVerify = ejabberd_config:get_local_option({ldap_tls_verify, Host}),
|
||||
LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of
|
||||
undefined -> 389;
|
||||
undefined -> case LDAPEncrypt of
|
||||
tls -> ?LDAPS_PORT;
|
||||
starttls -> ?LDAP_PORT;
|
||||
_ -> ?LDAP_PORT
|
||||
end;
|
||||
P -> P
|
||||
end,
|
||||
RootDN = case ejabberd_config:get_local_option({ldap_rootdn, Host}) of
|
||||
@@ -372,8 +404,12 @@ parse_options(Host) ->
|
||||
LDAPBase = ejabberd_config:get_local_option({ldap_base, Host}),
|
||||
{DNFilter, DNFilterAttrs} =
|
||||
case ejabberd_config:get_local_option({ldap_dn_filter, Host}) of
|
||||
undefined -> {undefined, undefined};
|
||||
{DNF, DNFA} -> {DNF, DNFA}
|
||||
undefined ->
|
||||
{undefined, []};
|
||||
{DNF, undefined} ->
|
||||
{DNF, []};
|
||||
{DNF, DNFA} ->
|
||||
{DNF, DNFA}
|
||||
end,
|
||||
LocalFilter = ejabberd_config:get_local_option({ldap_local_filter, Host}),
|
||||
#state{host = Host,
|
||||
@@ -382,6 +418,8 @@ parse_options(Host) ->
|
||||
servers = LDAPServers,
|
||||
backups = LDAPBackups,
|
||||
port = LDAPPort,
|
||||
tls_options = [{encrypt, LDAPEncrypt},
|
||||
{tls_verify, LDAPTLSVerify}],
|
||||
dn = RootDN,
|
||||
password = Password,
|
||||
base = LDAPBase,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -51,16 +51,13 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
ejabberd_ctl:register_commands(
|
||||
Host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered),
|
||||
start(_Host) ->
|
||||
ok.
|
||||
|
||||
plain_password_required() ->
|
||||
false.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false | {error, Error}
|
||||
check_password(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
@@ -68,26 +65,35 @@ check_password(User, Server, Password) ->
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:get_password(LServer, Username) of
|
||||
try odbc_queries:get_password(LServer, Username) of
|
||||
{selected, ["password"], [{Password}]} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
Password /= ""; %% Password is correct, and not empty
|
||||
{selected, ["password"], [{_Password2}]} ->
|
||||
false; %% Password is not correct
|
||||
{selected, ["password"], []} ->
|
||||
false; %% Account does not exist
|
||||
{error, _Error} ->
|
||||
false %% Typical error is that table doesn't exist
|
||||
catch
|
||||
_:_ ->
|
||||
false %% Typical error is database not accessible
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password, StreamID, Digest) ->
|
||||
%% @spec (User, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
|
||||
check_password(User, Server, Password, Digest, DigestGen) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
false;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:get_password(LServer, Username) of
|
||||
try odbc_queries:get_password(LServer, Username) of
|
||||
%% Account exists, check if password is valid
|
||||
{selected, ["password"], [{Passwd}]} ->
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
Digest == sha:sha(StreamID ++ Passwd);
|
||||
Digest == DigestGen(Passwd);
|
||||
true ->
|
||||
false
|
||||
end,
|
||||
@@ -96,11 +102,18 @@ check_password(User, Server, Password, StreamID, Digest) ->
|
||||
true ->
|
||||
(Passwd == Password) and (Password /= "")
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
{selected, ["password"], []} ->
|
||||
false; %% Account does not exist
|
||||
{error, _Error} ->
|
||||
false %% Typical error is that table doesn't exist
|
||||
catch
|
||||
_:_ ->
|
||||
false %% Typical error is database not accessible
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% ok | {error, invalid_jid}
|
||||
set_password(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
@@ -109,10 +122,14 @@ set_password(User, Server, Password) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc:escape(Password),
|
||||
LServer = jlib:nameprep(Server),
|
||||
catch odbc_queries:set_password_t(LServer, Username, Pass)
|
||||
case catch odbc_queries:set_password_t(LServer, Username, Pass) of
|
||||
{atomic, ok} -> ok;
|
||||
Other -> {error, Other}
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid}
|
||||
try_register(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
@@ -130,7 +147,11 @@ try_register(User, Server, Password) ->
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
get_vh_registered_users(?MYNAME).
|
||||
Servers = ejabberd_config:get_vh_by_auth_method(odbc),
|
||||
lists:flatmap(
|
||||
fun(Server) ->
|
||||
get_vh_registered_users(Server)
|
||||
end, Servers).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
@@ -198,6 +219,7 @@ get_password_s(User, Server) ->
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
is_user_exists(User, Server) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
@@ -205,14 +227,22 @@ is_user_exists(User, Server) ->
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:get_password(LServer, Username) of
|
||||
try odbc_queries:get_password(LServer, Username) of
|
||||
{selected, ["password"], [{_Password}]} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
true; %% Account exists
|
||||
{selected, ["password"], []} ->
|
||||
false; %% Account does not exist
|
||||
{error, Error} ->
|
||||
{error, Error} %% Typical error is that table doesn't exist
|
||||
catch
|
||||
_:B ->
|
||||
{error, B} %% Typical error is database not accessible
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> ok | error
|
||||
%% @doc Remove user.
|
||||
%% Note: it may return ok even if there was some problem removing the user.
|
||||
remove_user(User, Server) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
@@ -221,10 +251,11 @@ remove_user(User, Server) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
catch odbc_queries:del_user(LServer, Username),
|
||||
ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
|
||||
[User, Server])
|
||||
ok
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed
|
||||
%% @doc Remove user if the provided password is correct.
|
||||
remove_user(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
@@ -238,8 +269,6 @@ remove_user(User, Server, Password) ->
|
||||
LServer, Username, Pass),
|
||||
case Result of
|
||||
{selected, ["password"], [{Password}]} ->
|
||||
ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
|
||||
[User, Server]),
|
||||
ok;
|
||||
{selected, ["password"], []} ->
|
||||
not_exists;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 5 Jul 2007 by Evgeniy Khramtsov <xram@jabber.ru>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -55,12 +55,16 @@ start(_Host) ->
|
||||
set_password(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
check_password(User, Server, Password, _StreamID, _Digest) ->
|
||||
check_password(User, Server, Password, _Digest, _DigestGen) ->
|
||||
check_password(User, Server, Password).
|
||||
|
||||
check_password(User, Host, Password) ->
|
||||
Service = get_pam_service(Host),
|
||||
case catch epam:authenticate(Service, User, Password) of
|
||||
UserInfo = case get_pam_userinfotype(Host) of
|
||||
username -> User;
|
||||
jid -> User++"@"++Host
|
||||
end,
|
||||
case catch epam:authenticate(Service, UserInfo, Password) of
|
||||
true -> true;
|
||||
_ -> false
|
||||
end.
|
||||
@@ -80,9 +84,15 @@ get_password(_User, _Server) ->
|
||||
get_password_s(_User, _Server) ->
|
||||
"".
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
%% TODO: Improve this function to return an error instead of 'false' when connection to PAM failed
|
||||
is_user_exists(User, Host) ->
|
||||
Service = get_pam_service(Host),
|
||||
case catch epam:acct_mgmt(Service, User) of
|
||||
UserInfo = case get_pam_userinfotype(Host) of
|
||||
username -> User;
|
||||
jid -> User++"@"++Host
|
||||
end,
|
||||
case catch epam:acct_mgmt(Service, UserInfo) of
|
||||
true -> true;
|
||||
_ -> false
|
||||
end.
|
||||
@@ -91,7 +101,7 @@ remove_user(_User, _Server) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
remove_user(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
not_allowed.
|
||||
|
||||
plain_password_required() ->
|
||||
true.
|
||||
@@ -104,3 +114,8 @@ get_pam_service(Host) ->
|
||||
undefined -> "ejabberd";
|
||||
Service -> Service
|
||||
end.
|
||||
get_pam_userinfotype(Host) ->
|
||||
case ejabberd_config:get_local_option({pam_userinfotype, Host}) of
|
||||
undefined -> username;
|
||||
Type -> Type
|
||||
end.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
%%% Created : 2 Nov 2007 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -17,7 +17,7 @@
|
||||
%%% 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
|
||||
|
||||
@@ -0,0 +1,592 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : ejabberd_captcha.erl
|
||||
%%% Author : Evgeniy Khramtsov <xramtsov@gmail.com>
|
||||
%%% Purpose : CAPTCHA processing.
|
||||
%%% Created : 26 Apr 2008 by Evgeniy Khramtsov <xramtsov@gmail.com>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2011 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(ejabberd_captcha).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/0]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-export([create_captcha/6, build_captcha_html/2, check_captcha/2,
|
||||
process_reply/1, process/2, is_feature_available/0,
|
||||
create_captcha_x/5, create_captcha_x/6]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
-include("web/ejabberd_http.hrl").
|
||||
|
||||
-define(VFIELD(Type, Var, Value),
|
||||
{xmlelement, "field", [{"type", Type}, {"var", Var}],
|
||||
[{xmlelement, "value", [], [Value]}]}).
|
||||
|
||||
-define(CAPTCHA_TEXT(Lang), translate:translate(Lang, "Enter the text you see")).
|
||||
-define(CAPTCHA_LIFETIME, 120000). % two minutes
|
||||
-define(LIMIT_PERIOD, 60*1000*1000). % one minute
|
||||
|
||||
-record(state, {limits = treap:empty()}).
|
||||
-record(captcha, {id, pid, key, tref, args}).
|
||||
|
||||
-define(T(S),
|
||||
case catch mnesia:transaction(fun() -> S end) of
|
||||
{atomic, Res} ->
|
||||
Res;
|
||||
{_, Reason} ->
|
||||
?ERROR_MSG("mnesia transaction failed: ~p", [Reason]),
|
||||
{error, Reason}
|
||||
end).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
create_captcha(SID, From, To, Lang, Limiter, Args)
|
||||
when is_list(Lang), is_list(SID),
|
||||
is_record(From, jid), is_record(To, jid) ->
|
||||
case create_image(Limiter) of
|
||||
{ok, Type, Key, Image} ->
|
||||
Id = randoms:get_string(),
|
||||
B64Image = jlib:encode_base64(binary_to_list(Image)),
|
||||
JID = jlib:jid_to_string(From),
|
||||
CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
|
||||
Data = {xmlelement, "data",
|
||||
[{"xmlns", ?NS_BOB}, {"cid", CID},
|
||||
{"max-age", "0"}, {"type", Type}],
|
||||
[{xmlcdata, B64Image}]},
|
||||
Captcha =
|
||||
{xmlelement, "captcha", [{"xmlns", ?NS_CAPTCHA}],
|
||||
[{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
|
||||
[?VFIELD("hidden", "FORM_TYPE", {xmlcdata, ?NS_CAPTCHA}),
|
||||
?VFIELD("hidden", "from", {xmlcdata, jlib:jid_to_string(To)}),
|
||||
?VFIELD("hidden", "challenge", {xmlcdata, Id}),
|
||||
?VFIELD("hidden", "sid", {xmlcdata, SID}),
|
||||
{xmlelement, "field", [{"var", "ocr"}, {"label", ?CAPTCHA_TEXT(Lang)}],
|
||||
[{xmlelement, "required", [], []},
|
||||
{xmlelement, "media", [{"xmlns", ?NS_MEDIA}],
|
||||
[{xmlelement, "uri", [{"type", Type}],
|
||||
[{xmlcdata, "cid:" ++ CID}]}]}]}]}]},
|
||||
BodyString1 = translate:translate(Lang, "Your messages to ~s are being blocked. To unblock them, visit ~s"),
|
||||
BodyString = io_lib:format(BodyString1, [JID, get_url(Id)]),
|
||||
Body = {xmlelement, "body", [],
|
||||
[{xmlcdata, BodyString}]},
|
||||
OOB = {xmlelement, "x", [{"xmlns", ?NS_OOB}],
|
||||
[{xmlelement, "url", [], [{xmlcdata, get_url(Id)}]}]},
|
||||
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
|
||||
case ?T(mnesia:write(#captcha{id=Id, pid=self(), key=Key,
|
||||
tref=Tref, args=Args})) of
|
||||
ok ->
|
||||
{ok, Id, [Body, OOB, Captcha, Data]};
|
||||
Err ->
|
||||
{error, Err}
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
create_captcha_x(SID, To, Lang, Limiter, HeadEls) ->
|
||||
create_captcha_x(SID, To, Lang, Limiter, HeadEls, []).
|
||||
|
||||
create_captcha_x(SID, To, Lang, Limiter, HeadEls, TailEls) ->
|
||||
case create_image(Limiter) of
|
||||
{ok, Type, Key, Image} ->
|
||||
Id = randoms:get_string(),
|
||||
B64Image = jlib:encode_base64(binary_to_list(Image)),
|
||||
CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
|
||||
Data = {xmlelement, "data",
|
||||
[{"xmlns", ?NS_BOB}, {"cid", CID},
|
||||
{"max-age", "0"}, {"type", Type}],
|
||||
[{xmlcdata, B64Image}]},
|
||||
HelpTxt = translate:translate(
|
||||
Lang,
|
||||
"If you don't see the CAPTCHA image here, "
|
||||
"visit the web page."),
|
||||
Imageurl = get_url(Id ++ "/image"),
|
||||
Captcha =
|
||||
{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
|
||||
[?VFIELD("hidden", "FORM_TYPE", {xmlcdata, ?NS_CAPTCHA}) | HeadEls] ++
|
||||
[{xmlelement, "field", [{"type", "fixed"}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, HelpTxt}]}]},
|
||||
{xmlelement, "field", [{"type", "hidden"}, {"var", "captchahidden"}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, "workaround-for-psi"}]}]},
|
||||
{xmlelement, "field",
|
||||
[{"type", "text-single"},
|
||||
{"label", translate:translate(Lang, "CAPTCHA web page")},
|
||||
{"var", "url"}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, Imageurl}]}]},
|
||||
?VFIELD("hidden", "from", {xmlcdata, jlib:jid_to_string(To)}),
|
||||
?VFIELD("hidden", "challenge", {xmlcdata, Id}),
|
||||
?VFIELD("hidden", "sid", {xmlcdata, SID}),
|
||||
{xmlelement, "field", [{"var", "ocr"}, {"label", ?CAPTCHA_TEXT(Lang)}],
|
||||
[{xmlelement, "required", [], []},
|
||||
{xmlelement, "media", [{"xmlns", ?NS_MEDIA}],
|
||||
[{xmlelement, "uri", [{"type", Type}],
|
||||
[{xmlcdata, "cid:" ++ CID}]}]}]}] ++ TailEls},
|
||||
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
|
||||
case ?T(mnesia:write(#captcha{id=Id, key=Key, tref=Tref})) of
|
||||
ok ->
|
||||
{ok, [Captcha, Data]};
|
||||
Err ->
|
||||
{error, Err}
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
%% @spec (Id::string(), Lang::string()) -> {FormEl, {ImgEl, TextEl, IdEl, KeyEl}} | captcha_not_found
|
||||
%% where FormEl = xmlelement()
|
||||
%% ImgEl = xmlelement()
|
||||
%% TextEl = xmlelement()
|
||||
%% IdEl = xmlelement()
|
||||
%% KeyEl = xmlelement()
|
||||
build_captcha_html(Id, Lang) ->
|
||||
case mnesia:dirty_read(captcha, Id) of
|
||||
[#captcha{}] ->
|
||||
ImgEl = {xmlelement, "img", [{"src", get_url(Id ++ "/image")}], []},
|
||||
TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
|
||||
IdEl = {xmlelement, "input", [{"type", "hidden"},
|
||||
{"name", "id"},
|
||||
{"value", Id}], []},
|
||||
KeyEl = {xmlelement, "input", [{"type", "text"},
|
||||
{"name", "key"},
|
||||
{"size", "10"}], []},
|
||||
FormEl = {xmlelement, "form", [{"action", get_url(Id)},
|
||||
{"name", "captcha"},
|
||||
{"method", "POST"}],
|
||||
[ImgEl,
|
||||
{xmlelement, "br", [], []},
|
||||
TextEl,
|
||||
{xmlelement, "br", [], []},
|
||||
IdEl,
|
||||
KeyEl,
|
||||
{xmlelement, "br", [], []},
|
||||
{xmlelement, "input", [{"type", "submit"},
|
||||
{"name", "enter"},
|
||||
{"value", "OK"}], []}
|
||||
]},
|
||||
{FormEl, {ImgEl, TextEl, IdEl, KeyEl}};
|
||||
_ ->
|
||||
captcha_not_found
|
||||
end.
|
||||
|
||||
%% @spec (Id::string(), ProvidedKey::string()) -> captcha_valid | captcha_non_valid | captcha_not_found
|
||||
check_captcha(Id, ProvidedKey) ->
|
||||
?T(case mnesia:read(captcha, Id, write) of
|
||||
[#captcha{pid=Pid, args=Args, key=StoredKey, tref=Tref}] ->
|
||||
mnesia:delete({captcha, Id}),
|
||||
erlang:cancel_timer(Tref),
|
||||
if StoredKey == ProvidedKey ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_succeed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
captcha_valid;
|
||||
true ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
captcha_non_valid
|
||||
end;
|
||||
_ ->
|
||||
captcha_not_found
|
||||
end).
|
||||
|
||||
|
||||
process_reply({xmlelement, _, _, _} = El) ->
|
||||
case xml:get_subtag(El, "x") of
|
||||
false ->
|
||||
{error, malformed};
|
||||
Xdata ->
|
||||
Fields = jlib:parse_xdata_submit(Xdata),
|
||||
case catch {proplists:get_value("challenge", Fields),
|
||||
proplists:get_value("ocr", Fields)} of
|
||||
{[Id|_], [OCR|_]} ->
|
||||
?T(case mnesia:read(captcha, Id, write) of
|
||||
[#captcha{pid=Pid, args=Args, key=Key, tref=Tref}] ->
|
||||
mnesia:delete({captcha, Id}),
|
||||
erlang:cancel_timer(Tref),
|
||||
if OCR == Key ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_succeed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
ok;
|
||||
true ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{error, bad_match}
|
||||
end;
|
||||
_ ->
|
||||
{error, not_found}
|
||||
end);
|
||||
_ ->
|
||||
{error, malformed}
|
||||
end
|
||||
end;
|
||||
process_reply(_) ->
|
||||
{error, malformed}.
|
||||
|
||||
|
||||
process(_Handlers, #request{method='GET', lang=Lang, path=[_, Id]}) ->
|
||||
case build_captcha_html(Id, Lang) of
|
||||
{FormEl, _} when is_tuple(FormEl) ->
|
||||
Form =
|
||||
{xmlelement, "div", [{"align", "center"}],
|
||||
[FormEl]},
|
||||
ejabberd_web:make_xhtml([Form]);
|
||||
captcha_not_found ->
|
||||
ejabberd_web:error(not_found)
|
||||
end;
|
||||
|
||||
process(_Handlers, #request{method='GET', path=[_, Id, "image"], ip = IP}) ->
|
||||
{Addr, _Port} = IP,
|
||||
case mnesia:dirty_read(captcha, Id) of
|
||||
[#captcha{key=Key}] ->
|
||||
case create_image(Addr, Key) of
|
||||
{ok, Type, _, Img} ->
|
||||
{200,
|
||||
[{"Content-Type", Type},
|
||||
{"Cache-Control", "no-cache"},
|
||||
{"Last-Modified", httpd_util:rfc1123_date()}],
|
||||
Img};
|
||||
{error, limit} ->
|
||||
ejabberd_web:error(not_allowed);
|
||||
_ ->
|
||||
ejabberd_web:error(not_found)
|
||||
end;
|
||||
_ ->
|
||||
ejabberd_web:error(not_found)
|
||||
end;
|
||||
|
||||
process(_Handlers, #request{method='POST', q=Q, lang=Lang, path=[_, Id]}) ->
|
||||
ProvidedKey = proplists:get_value("key", Q, none),
|
||||
case check_captcha(Id, ProvidedKey) of
|
||||
captcha_valid ->
|
||||
Form =
|
||||
{xmlelement, "p", [],
|
||||
[{xmlcdata,
|
||||
translate:translate(Lang, "The captcha is valid.")
|
||||
}]},
|
||||
ejabberd_web:make_xhtml([Form]);
|
||||
captcha_non_valid ->
|
||||
ejabberd_web:error(not_allowed);
|
||||
captcha_not_found ->
|
||||
ejabberd_web:error(not_found)
|
||||
end;
|
||||
|
||||
process(_Handlers, _Request) ->
|
||||
ejabberd_web:error(not_found).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([]) ->
|
||||
mnesia:create_table(captcha,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, captcha)}]),
|
||||
mnesia:add_table_copy(captcha, node(), ram_copies),
|
||||
check_captcha_setup(),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call({is_limited, Limiter, RateLimit}, _From, State) ->
|
||||
NowPriority = now_priority(),
|
||||
CleanPriority = NowPriority + ?LIMIT_PERIOD,
|
||||
Limits = clean_treap(State#state.limits, CleanPriority),
|
||||
case treap:lookup(Limiter, Limits) of
|
||||
{ok, _, Rate} when Rate >= RateLimit ->
|
||||
{reply, true, State#state{limits = Limits}};
|
||||
{ok, Priority, Rate} ->
|
||||
NewLimits = treap:insert(Limiter, Priority, Rate+1, Limits),
|
||||
{reply, false, State#state{limits = NewLimits}};
|
||||
_ ->
|
||||
NewLimits = treap:insert(Limiter, NowPriority, 1, Limits),
|
||||
{reply, false, State#state{limits = NewLimits}}
|
||||
end;
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, bad_request, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({remove_id, Id}, State) ->
|
||||
?DEBUG("captcha ~p timed out", [Id]),
|
||||
_ = ?T(case mnesia:read(captcha, Id, write) of
|
||||
[#captcha{args=Args, pid=Pid}] ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
mnesia:delete({captcha, Id});
|
||||
_ ->
|
||||
ok
|
||||
end),
|
||||
{noreply, State};
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: create_image() -> {ok, Type, Key, Image} | {error, Reason}
|
||||
%% Type = "image/png" | "image/jpeg" | "image/gif"
|
||||
%% Key = string()
|
||||
%% Image = binary()
|
||||
%% Reason = atom()
|
||||
%%--------------------------------------------------------------------
|
||||
create_image() ->
|
||||
create_image(undefined).
|
||||
|
||||
create_image(Limiter) ->
|
||||
%% Six numbers from 1 to 9.
|
||||
Key = string:substr(randoms:get_string(), 1, 6),
|
||||
create_image(Limiter, Key).
|
||||
|
||||
create_image(Limiter, Key) ->
|
||||
case is_limited(Limiter) of
|
||||
true ->
|
||||
{error, limit};
|
||||
false ->
|
||||
do_create_image(Key)
|
||||
end.
|
||||
|
||||
do_create_image(Key) ->
|
||||
FileName = get_prog_name(),
|
||||
Cmd = lists:flatten(io_lib:format("~s ~s", [FileName, Key])),
|
||||
case cmd(Cmd) of
|
||||
{ok, <<16#89, $P, $N, $G, $\r, $\n, 16#1a, $\n, _/binary>> = Img} ->
|
||||
{ok, "image/png", Key, Img};
|
||||
{ok, <<16#ff, 16#d8, _/binary>> = Img} ->
|
||||
{ok, "image/jpeg", Key, Img};
|
||||
{ok, <<$G, $I, $F, $8, X, $a, _/binary>> = Img} when X==$7; X==$9 ->
|
||||
{ok, "image/gif", Key, Img};
|
||||
{error, enodata = Reason} ->
|
||||
?ERROR_MSG("Failed to process output from \"~s\". "
|
||||
"Maybe ImageMagick's Convert program is not installed.",
|
||||
[Cmd]),
|
||||
{error, Reason};
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("Failed to process an output from \"~s\": ~p",
|
||||
[Cmd, Reason]),
|
||||
{error, Reason};
|
||||
_ ->
|
||||
Reason = malformed_image,
|
||||
?ERROR_MSG("Failed to process an output from \"~s\": ~p",
|
||||
[Cmd, Reason]),
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
get_prog_name() ->
|
||||
case ejabberd_config:get_local_option(captcha_cmd) of
|
||||
FileName when is_list(FileName) ->
|
||||
FileName;
|
||||
Value when (Value == undefined) or (Value == "") ->
|
||||
?DEBUG("The option captcha_cmd is not configured, but some "
|
||||
"module wants to use the CAPTCHA feature.", []),
|
||||
false
|
||||
end.
|
||||
|
||||
get_url(Str) ->
|
||||
CaptchaHost = ejabberd_config:get_local_option(captcha_host),
|
||||
case string:tokens(CaptchaHost, ":") of
|
||||
[Host] ->
|
||||
"http://" ++ Host ++ "/captcha/" ++ Str;
|
||||
["http"++_ = TransferProt, Host] ->
|
||||
TransferProt ++ ":" ++ Host ++ "/captcha/" ++ Str;
|
||||
[Host, PortString] ->
|
||||
TransferProt = atom_to_list(get_transfer_protocol(PortString)),
|
||||
TransferProt ++ "://" ++ Host ++ ":" ++ PortString ++ "/captcha/" ++ Str;
|
||||
[TransferProt, Host, PortString] ->
|
||||
TransferProt ++ ":" ++ Host ++ ":" ++ PortString ++ "/captcha/" ++ Str;
|
||||
_ ->
|
||||
"http://" ++ ?MYNAME ++ "/captcha/" ++ Str
|
||||
end.
|
||||
|
||||
get_transfer_protocol(PortString) ->
|
||||
PortNumber = list_to_integer(PortString),
|
||||
PortListeners = get_port_listeners(PortNumber),
|
||||
get_captcha_transfer_protocol(PortListeners).
|
||||
|
||||
get_port_listeners(PortNumber) ->
|
||||
AllListeners = ejabberd_config:get_local_option(listen),
|
||||
lists:filter(
|
||||
fun({{Port, _Ip, _Netp}, _Module1, _Opts1}) when Port == PortNumber ->
|
||||
true;
|
||||
(_) ->
|
||||
false
|
||||
end,
|
||||
AllListeners).
|
||||
|
||||
get_captcha_transfer_protocol([]) ->
|
||||
throw("The port number mentioned in captcha_host is not "
|
||||
"a ejabberd_http listener with 'captcha' option. "
|
||||
"Change the port number or specify http:// in that option.");
|
||||
get_captcha_transfer_protocol([{{_Port, _Ip, tcp}, ejabberd_http, Opts}
|
||||
| Listeners]) ->
|
||||
case lists:member(captcha, Opts) of
|
||||
true ->
|
||||
case lists:member(tls, Opts) of
|
||||
true ->
|
||||
https;
|
||||
false ->
|
||||
http
|
||||
end;
|
||||
false ->
|
||||
get_captcha_transfer_protocol(Listeners)
|
||||
end;
|
||||
get_captcha_transfer_protocol([_ | Listeners]) ->
|
||||
get_captcha_transfer_protocol(Listeners).
|
||||
|
||||
is_limited(undefined) ->
|
||||
false;
|
||||
is_limited(Limiter) ->
|
||||
case ejabberd_config:get_local_option(captcha_limit) of
|
||||
Int when is_integer(Int), Int > 0 ->
|
||||
case catch gen_server:call(?MODULE, {is_limited, Limiter, Int},
|
||||
5000) of
|
||||
true ->
|
||||
true;
|
||||
false ->
|
||||
false;
|
||||
Err ->
|
||||
?ERROR_MSG("Call failed: ~p", [Err]),
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: cmd(Cmd) -> Data | {error, Reason}
|
||||
%% Cmd = string()
|
||||
%% Data = binary()
|
||||
%% Description: os:cmd/1 replacement
|
||||
%%--------------------------------------------------------------------
|
||||
-define(CMD_TIMEOUT, 5000).
|
||||
-define(MAX_FILE_SIZE, 64*1024).
|
||||
|
||||
cmd(Cmd) ->
|
||||
Port = open_port({spawn, Cmd}, [stream, eof, binary]),
|
||||
TRef = erlang:start_timer(?CMD_TIMEOUT, self(), timeout),
|
||||
recv_data(Port, TRef, <<>>).
|
||||
|
||||
recv_data(Port, TRef, Buf) ->
|
||||
receive
|
||||
{Port, {data, Bytes}} ->
|
||||
NewBuf = <<Buf/binary, Bytes/binary>>,
|
||||
if size(NewBuf) > ?MAX_FILE_SIZE ->
|
||||
return(Port, TRef, {error, efbig});
|
||||
true ->
|
||||
recv_data(Port, TRef, NewBuf)
|
||||
end;
|
||||
{Port, {data, _}} ->
|
||||
return(Port, TRef, {error, efbig});
|
||||
{Port, eof} when Buf /= <<>> ->
|
||||
return(Port, TRef, {ok, Buf});
|
||||
{Port, eof} ->
|
||||
return(Port, TRef, {error, enodata});
|
||||
{timeout, TRef, _} ->
|
||||
return(Port, TRef, {error, timeout})
|
||||
end.
|
||||
|
||||
return(Port, TRef, Result) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
receive
|
||||
{timeout, TRef, _} ->
|
||||
ok
|
||||
after 0 ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
catch port_close(Port),
|
||||
Result.
|
||||
|
||||
is_feature_available() ->
|
||||
case get_prog_name() of
|
||||
Prog when is_list(Prog) -> true;
|
||||
false -> false
|
||||
end.
|
||||
|
||||
check_captcha_setup() ->
|
||||
case is_feature_available() of
|
||||
true ->
|
||||
case create_image() of
|
||||
{ok, _, _, _} ->
|
||||
ok;
|
||||
_Err ->
|
||||
?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, "
|
||||
"but it can't generate images.", []),
|
||||
throw({error, captcha_cmd_enabled_but_fails})
|
||||
end;
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
clean_treap(Treap, CleanPriority) ->
|
||||
case treap:is_empty(Treap) of
|
||||
true ->
|
||||
Treap;
|
||||
false ->
|
||||
{_Key, Priority, _Value} = treap:get_root(Treap),
|
||||
if
|
||||
Priority > CleanPriority ->
|
||||
clean_treap(treap:delete_root(Treap), CleanPriority);
|
||||
true ->
|
||||
Treap
|
||||
end
|
||||
end.
|
||||
|
||||
now_priority() ->
|
||||
{MSec, Sec, USec} = now(),
|
||||
-((MSec*1000000 + Sec)*1000000 + USec).
|
||||
@@ -0,0 +1,112 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_check.erl
|
||||
%%% Author : Mickael Remond <mremond@process-one.net>
|
||||
%%% Purpose : Check ejabberd configuration and
|
||||
%%% Created : 27 Feb 2008 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2011 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(ejabberd_check).
|
||||
|
||||
-export([libs/0, config/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_config.hrl").
|
||||
|
||||
-compile([export_all]).
|
||||
|
||||
%% TODO:
|
||||
%% We want to implement library checking at launch time to issue
|
||||
%% human readable user messages.
|
||||
libs() ->
|
||||
ok.
|
||||
|
||||
%% @doc Consistency check on ejabberd configuration
|
||||
config() ->
|
||||
check_database_modules().
|
||||
|
||||
check_database_modules() ->
|
||||
[check_database_module(M)||M<-get_db_used()].
|
||||
|
||||
check_database_module(odbc) ->
|
||||
check_modules(odbc, [odbc, odbc_app, odbc_sup, ejabberd_odbc, ejabberd_odbc_sup, odbc_queries]);
|
||||
check_database_module(mysql) ->
|
||||
check_modules(mysql, [mysql, mysql_auth, mysql_conn, mysql_recv]);
|
||||
check_database_module(pgsql) ->
|
||||
check_modules(pgsql, [pgsql, pgsql_proto, pgsql_tcp, pgsql_util]).
|
||||
|
||||
%% @doc Issue a critical error and throw an exit if needing module is
|
||||
%% missing.
|
||||
check_modules(DB, Modules) ->
|
||||
case get_missing_modules(Modules) of
|
||||
[] ->
|
||||
ok;
|
||||
MissingModules when is_list(MissingModules) ->
|
||||
?CRITICAL_MSG("ejabberd is configured to use '~p', but the following Erlang modules are not installed: '~p'", [DB, MissingModules]),
|
||||
exit(database_module_missing)
|
||||
end.
|
||||
|
||||
|
||||
%% @doc Return the list of undefined modules
|
||||
get_missing_modules(Modules) ->
|
||||
lists:filter(fun(Module) ->
|
||||
case catch Module:module_info() of
|
||||
{'EXIT', {undef, _}} ->
|
||||
true;
|
||||
_ -> false
|
||||
end
|
||||
end, Modules).
|
||||
|
||||
%% @doc Return the list of databases used
|
||||
get_db_used() ->
|
||||
%% Retrieve domains with a database configured:
|
||||
Domains =
|
||||
ets:match(local_config, #local_config{key={odbc_server, '$1'},
|
||||
value='$2'}),
|
||||
%% Check that odbc is the auth method used for those domains:
|
||||
%% and return the database name
|
||||
DBs = lists:foldr(
|
||||
fun([Domain, DB], Acc) ->
|
||||
case check_odbc_option(
|
||||
ejabberd_config:get_local_option(
|
||||
{auth_method, Domain})) of
|
||||
true -> [get_db_type(DB)|Acc];
|
||||
_ -> Acc
|
||||
end
|
||||
end,
|
||||
[], Domains),
|
||||
lists:usort(DBs).
|
||||
|
||||
%% @doc Depending in the DB definition, return which type of DB this is.
|
||||
%% Note that MSSQL is detected as ODBC.
|
||||
%% @spec (DB) -> mysql | pgsql | odbc
|
||||
get_db_type(DB) when is_tuple(DB) ->
|
||||
element(1, DB);
|
||||
get_db_type(DB) when is_list(DB) ->
|
||||
odbc.
|
||||
|
||||
%% @doc Return true if odbc option is used
|
||||
check_odbc_option(odbc) ->
|
||||
true;
|
||||
check_odbc_option(AuthMethods) when is_list(AuthMethods) ->
|
||||
lists:member(odbc, AuthMethods);
|
||||
check_odbc_option(_) ->
|
||||
false.
|
||||
@@ -0,0 +1,429 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_commands.erl
|
||||
%%% Author : Badlop <badlop@process-one.net>
|
||||
%%% Purpose : Management of ejabberd commands
|
||||
%%% Created : 20 May 2008 by Badlop <badlop@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2011 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
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%% @headerfile "ejabberd_commands.hrl"
|
||||
|
||||
%%% @doc Management of ejabberd commands.
|
||||
%%%
|
||||
%%% An ejabberd command is an abstract function identified by a name,
|
||||
%%% with a defined number and type of calling arguments and type of
|
||||
%%% result, that can be defined in any Erlang module and executed
|
||||
%%% using any valid frontend.
|
||||
%%%
|
||||
%%%
|
||||
%%% == Define a new ejabberd command ==
|
||||
%%%
|
||||
%%% ejabberd commands can be defined and registered in
|
||||
%%% any Erlang module.
|
||||
%%%
|
||||
%%% Some commands are procedures; and their purpose is to perform an
|
||||
%%% action in the server, so the command result is only some result
|
||||
%%% code or result tuple. Other commands are inspectors, and their
|
||||
%%% purpose is to gather some information about the server and return
|
||||
%%% a detailed response: it can be integer, string, atom, tuple, list
|
||||
%%% or a mix of those ones.
|
||||
%%%
|
||||
%%% The arguments and result of an ejabberd command are strictly
|
||||
%%% defined. The number and format of the arguments provided when
|
||||
%%% calling an ejabberd command must match the definition of that
|
||||
%%% command. The format of the result provided by an ejabberd command
|
||||
%%% must be exactly its definition. For example, if a command is said
|
||||
%%% to return an integer, it must always return an integer (except in
|
||||
%%% case of a crash).
|
||||
%%%
|
||||
%%% If you are developing an Erlang module that will run inside
|
||||
%%% ejabberd and you want to provide a new ejabberd command to
|
||||
%%% administer some task related to your module, you only need to:
|
||||
%%% implement a function, define the command, and register it.
|
||||
%%%
|
||||
%%%
|
||||
%%% === Define a new ejabberd command ===
|
||||
%%%
|
||||
%%% An ejabberd command is defined using the Erlang record
|
||||
%%% 'ejabberd_commands'. This record has several elements that you
|
||||
%%% must define. Note that 'tags', 'desc' and 'longdesc' are optional.
|
||||
%%%
|
||||
%%% For example let's define an ejabberd command 'pow' that gets the
|
||||
%%% integers 'base' and 'exponent'. Its result will be an integer
|
||||
%%% 'power':
|
||||
%%%
|
||||
%%% <pre>#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}}</pre>
|
||||
%%%
|
||||
%%%
|
||||
%%% === Implement the function associated to the command ===
|
||||
%%%
|
||||
%%% Now implement a function in your module that matches the arguments
|
||||
%%% and result of the ejabberd command.
|
||||
%%%
|
||||
%%% For example the function calc_power gets two integers Base and
|
||||
%%% Exponent. It calculates the power and rounds to an integer:
|
||||
%%%
|
||||
%%% <pre>calc_power(Base, Exponent) ->
|
||||
%%% PowFloat = math:pow(Base, Exponent),
|
||||
%%% round(PowFloat).</pre>
|
||||
%%%
|
||||
%%% Since this function will be called by ejabberd_commands, it must be exported.
|
||||
%%% Add to your module:
|
||||
%%% <pre>-export([calc_power/2]).</pre>
|
||||
%%%
|
||||
%%% Only some types of result formats are allowed.
|
||||
%%% If the format is defined as 'rescode', then your function must return:
|
||||
%%% ok | true | atom()
|
||||
%%% where the atoms ok and true as considered positive answers,
|
||||
%%% and any other response atom is considered negative.
|
||||
%%%
|
||||
%%% If the format is defined as 'restuple', then the command must return:
|
||||
%%% {rescode(), string()}
|
||||
%%%
|
||||
%%% If the format is defined as '{list, something()}', then the command
|
||||
%%% must return a list of something().
|
||||
%%%
|
||||
%%%
|
||||
%%% === Register the command ===
|
||||
%%%
|
||||
%%% Define this function and put inside the #ejabberd_command you
|
||||
%%% defined in the beginning:
|
||||
%%%
|
||||
%%% <pre>commands() ->
|
||||
%%% [
|
||||
%%%
|
||||
%%% ].</pre>
|
||||
%%%
|
||||
%%% You need to include this header file in order to use the record:
|
||||
%%%
|
||||
%%% <pre>-include("ejabberd_commands.hrl").</pre>
|
||||
%%%
|
||||
%%% When your module is initialized or started, register your commands:
|
||||
%%%
|
||||
%%% <pre>ejabberd_commands:register_commands(commands()),</pre>
|
||||
%%%
|
||||
%%% And when your module is stopped, unregister your commands:
|
||||
%%%
|
||||
%%% <pre>ejabberd_commands:unregister_commands(commands()),</pre>
|
||||
%%%
|
||||
%%% That's all! Now when your module is started, the command will be
|
||||
%%% registered and any frontend can access it. For example:
|
||||
%%%
|
||||
%%% <pre>$ ejabberdctl help pow
|
||||
%%%
|
||||
%%% Command Name: pow
|
||||
%%%
|
||||
%%% Arguments: base::integer
|
||||
%%% exponent::integer
|
||||
%%%
|
||||
%%% Returns: power::integer
|
||||
%%%
|
||||
%%% Tags: test
|
||||
%%%
|
||||
%%% Description: Return the power of base for exponent
|
||||
%%%
|
||||
%%% This is an example command. The formula is:
|
||||
%%% power = base ^ exponent
|
||||
%%%
|
||||
%%% $ ejabberdctl pow 3 4
|
||||
%%% 81
|
||||
%%% </pre>
|
||||
%%%
|
||||
%%%
|
||||
%%% == Execute an ejabberd command ==
|
||||
%%%
|
||||
%%% ejabberd commands are mean to be executed using any valid
|
||||
%%% frontend. An ejabberd commands is implemented in a regular Erlang
|
||||
%%% function, so it is also possible to execute this function in any
|
||||
%%% Erlang module, without dealing with the associated ejabberd
|
||||
%%% commands.
|
||||
%%%
|
||||
%%%
|
||||
%%% == Frontend to ejabberd commands ==
|
||||
%%%
|
||||
%%% Currently there are two frontends to ejabberd commands: the shell
|
||||
%%% script {@link ejabberd_ctl. ejabberdctl}, and the XML-RPC server
|
||||
%%% ejabberd_xmlrpc.
|
||||
%%%
|
||||
%%%
|
||||
%%% === ejabberdctl as a frontend to ejabberd commands ===
|
||||
%%%
|
||||
%%% It is possible to use ejabberdctl to get documentation of any
|
||||
%%% command. But ejabberdctl does not support all the argument types
|
||||
%%% allowed in ejabberd commands, so there are some ejabberd commands
|
||||
%%% that cannot be executed using ejabberdctl.
|
||||
%%%
|
||||
%%% Also note that the ejabberdctl shell administration script also
|
||||
%%% manages ejabberdctl commands, which are unrelated to ejabberd
|
||||
%%% commands and can only be executed using ejabberdctl.
|
||||
%%%
|
||||
%%%
|
||||
%%% === ejabberd_xmlrpc as a frontend to ejabberd commands ===
|
||||
%%%
|
||||
%%% ejabberd_xmlrpc provides an XML-RPC server to execute ejabberd commands.
|
||||
%%% ejabberd_xmlrpc is a contributed module published in ejabberd-modules SVN.
|
||||
%%%
|
||||
%%% Since ejabberd_xmlrpc does not provide any method to get documentation
|
||||
%%% of the ejabberd commands, please use ejabberdctl to know which
|
||||
%%% commands are available, and their usage.
|
||||
%%%
|
||||
%%% The number and format of the arguments provided when calling an
|
||||
%%% ejabberd command must match the definition of that command. Please
|
||||
%%% make sure the XML-RPC call provides the required arguments, with
|
||||
%%% the specified format. The order of the arguments in an XML-RPC
|
||||
%%% call is not important, because all the data is tagged and will be
|
||||
%%% correctly prepared by ejabberd_xmlrpc before executing the ejabberd
|
||||
%%% command.
|
||||
|
||||
%%% TODO: consider this feature:
|
||||
%%% All commands are catched. If an error happens, return the restuple:
|
||||
%%% {error, flattened error string}
|
||||
%%% This means that ecomm call APIs (ejabberd_ctl, ejabberd_xmlrpc) need to allows this.
|
||||
%%% And ejabberd_xmlrpc must be prepared to handle such an unexpected response.
|
||||
|
||||
|
||||
-module(ejabberd_commands).
|
||||
-author('badlop@process-one.net').
|
||||
|
||||
-export([init/0,
|
||||
list_commands/0,
|
||||
get_command_format/1,
|
||||
get_command_definition/1,
|
||||
get_tags_commands/0,
|
||||
register_commands/1,
|
||||
unregister_commands/1,
|
||||
execute_command/2,
|
||||
execute_command/4
|
||||
]).
|
||||
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
|
||||
init() ->
|
||||
ets:new(ejabberd_commands, [named_table, set, public,
|
||||
{keypos, #ejabberd_commands.name}]).
|
||||
|
||||
%% @spec ([ejabberd_commands()]) -> ok
|
||||
%% @doc Register ejabberd commands.
|
||||
%% If a command is already registered, a warning is printed and the old command is preserved.
|
||||
register_commands(Commands) ->
|
||||
lists:foreach(
|
||||
fun(Command) ->
|
||||
case ets:insert_new(ejabberd_commands, Command) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
?DEBUG("This command is already defined:~n~p", [Command])
|
||||
end
|
||||
end,
|
||||
Commands).
|
||||
|
||||
%% @spec ([ejabberd_commands()]) -> ok
|
||||
%% @doc Unregister ejabberd commands.
|
||||
unregister_commands(Commands) ->
|
||||
lists:foreach(
|
||||
fun(Command) ->
|
||||
ets:delete_object(ejabberd_commands, Command)
|
||||
end,
|
||||
Commands).
|
||||
|
||||
%% @spec () -> [{Name::atom(), Args::[aterm()], Desc::string()}]
|
||||
%% @doc Get a list of all the available commands, arguments and description.
|
||||
list_commands() ->
|
||||
Commands = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{name = '$1',
|
||||
args = '$2',
|
||||
desc = '$3',
|
||||
_ = '_'}),
|
||||
[{A, B, C} || [A, B, C] <- Commands].
|
||||
|
||||
%% @spec (Name::atom()) -> {Args::[aterm()], Result::rterm()} | {error, command_unknown}
|
||||
%% @doc Get the format of arguments and result of a command.
|
||||
get_command_format(Name) ->
|
||||
Matched = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{name = Name,
|
||||
args = '$1',
|
||||
result = '$2',
|
||||
_ = '_'}),
|
||||
case Matched of
|
||||
[] ->
|
||||
{error, command_unknown};
|
||||
[[Args, Result]] ->
|
||||
{Args, Result}
|
||||
end.
|
||||
|
||||
%% @spec (Name::atom()) -> ejabberd_commands() | command_not_found
|
||||
%% @doc Get the definition record of a command.
|
||||
get_command_definition(Name) ->
|
||||
case ets:lookup(ejabberd_commands, Name) of
|
||||
[E] -> E;
|
||||
[] -> command_not_found
|
||||
end.
|
||||
|
||||
%% @spec (Name::atom(), Arguments) -> ResultTerm | {error, command_unknown}
|
||||
%% @doc Execute a command.
|
||||
execute_command(Name, Arguments) ->
|
||||
execute_command([], noauth, Name, Arguments).
|
||||
|
||||
%% @spec (AccessCommands, Auth, Name::atom(), Arguments) -> ResultTerm | {error, Error}
|
||||
%% where
|
||||
%% AccessCommands = [{Access, CommandNames, Arguments}]
|
||||
%% Auth = {User::string(), Server::string(), Password::string()} | noauth
|
||||
%% Method = atom()
|
||||
%% Arguments = [any()]
|
||||
%% Error = command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
|
||||
execute_command(AccessCommands, Auth, Name, Arguments) ->
|
||||
case ets:lookup(ejabberd_commands, Name) of
|
||||
[Command] ->
|
||||
try check_access_commands(AccessCommands, Auth, Name, Command, Arguments) of
|
||||
ok -> execute_command2(Command, Arguments)
|
||||
catch
|
||||
{error, Error} -> {error, Error}
|
||||
end;
|
||||
[] -> {error, command_unknown}
|
||||
end.
|
||||
|
||||
execute_command2(Command, Arguments) ->
|
||||
Module = Command#ejabberd_commands.module,
|
||||
Function = Command#ejabberd_commands.function,
|
||||
?DEBUG("Executing command ~p:~p with Args=~p", [Module, Function, Arguments]),
|
||||
apply(Module, Function, Arguments).
|
||||
|
||||
%% @spec () -> [{Tag::string(), [CommandName::string()]}]
|
||||
%% @doc Get all the tags and associated commands.
|
||||
get_tags_commands() ->
|
||||
CommandTags = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{
|
||||
name = '$1',
|
||||
tags = '$2',
|
||||
_ = '_'}),
|
||||
Dict = lists:foldl(
|
||||
fun([CommandNameAtom, CTags], D) ->
|
||||
CommandName = atom_to_list(CommandNameAtom),
|
||||
case CTags of
|
||||
[] ->
|
||||
orddict:append("untagged", CommandName, D);
|
||||
_ ->
|
||||
lists:foldl(
|
||||
fun(TagAtom, DD) ->
|
||||
Tag = atom_to_list(TagAtom),
|
||||
orddict:append(Tag, CommandName, DD)
|
||||
end,
|
||||
D,
|
||||
CTags)
|
||||
end
|
||||
end,
|
||||
orddict:new(),
|
||||
CommandTags),
|
||||
orddict:to_list(Dict).
|
||||
|
||||
|
||||
%% -----------------------------
|
||||
%% Access verification
|
||||
%% -----------------------------
|
||||
|
||||
%% @spec (AccessCommands, Auth, Method, Command, Arguments) -> ok
|
||||
%% where
|
||||
%% AccessCommands = [ {Access, CommandNames, Arguments} ]
|
||||
%% Auth = {User::string(), Server::string(), Password::string()} | noauth
|
||||
%% Method = atom()
|
||||
%% Arguments = [any()]
|
||||
%% @doc Check access is allowed to that command.
|
||||
%% At least one AccessCommand must be satisfied.
|
||||
%% It may throw {error, Error} where:
|
||||
%% Error = account_unprivileged | invalid_account_data
|
||||
check_access_commands([], _Auth, _Method, _Command, _Arguments) ->
|
||||
ok;
|
||||
check_access_commands(AccessCommands, Auth, Method, Command, Arguments) ->
|
||||
AccessCommandsAllowed =
|
||||
lists:filter(
|
||||
fun({Access, Commands, ArgumentRestrictions}) ->
|
||||
case check_access(Access, Auth) of
|
||||
true ->
|
||||
check_access_command(Commands, Command, ArgumentRestrictions,
|
||||
Method, Arguments);
|
||||
false ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
AccessCommands),
|
||||
case AccessCommandsAllowed of
|
||||
[] -> throw({error, account_unprivileged});
|
||||
L when is_list(L) -> ok
|
||||
end.
|
||||
|
||||
check_auth(noauth) ->
|
||||
no_auth_provided;
|
||||
check_auth({User, Server, Password}) ->
|
||||
%% Check the account exists and password is valid
|
||||
AccountPass = ejabberd_auth:get_password_s(User, Server),
|
||||
AccountPassMD5 = get_md5(AccountPass),
|
||||
case Password of
|
||||
AccountPass -> {ok, User, Server};
|
||||
AccountPassMD5 -> {ok, User, Server};
|
||||
_ -> throw({error, invalid_account_data})
|
||||
end.
|
||||
|
||||
get_md5(AccountPass) ->
|
||||
lists:flatten([io_lib:format("~.16B", [X])
|
||||
|| X <- binary_to_list(crypto:md5(AccountPass))]).
|
||||
|
||||
check_access(all, _) ->
|
||||
true;
|
||||
check_access(Access, Auth) ->
|
||||
{ok, User, Server} = check_auth(Auth),
|
||||
%% Check this user has access permission
|
||||
case acl:match_rule(Server, Access, jlib:make_jid(User, Server, "")) of
|
||||
allow -> true;
|
||||
deny -> false
|
||||
end.
|
||||
|
||||
check_access_command(Commands, Command, ArgumentRestrictions, Method, Arguments) ->
|
||||
case Commands==all orelse lists:member(Method, Commands) of
|
||||
true -> check_access_arguments(Command, ArgumentRestrictions, Arguments);
|
||||
false -> false
|
||||
end.
|
||||
|
||||
check_access_arguments(Command, ArgumentRestrictions, Arguments) ->
|
||||
ArgumentsTagged = tag_arguments(Command#ejabberd_commands.args, Arguments),
|
||||
lists:all(
|
||||
fun({ArgName, ArgAllowedValue}) ->
|
||||
%% If the call uses the argument, check the value is acceptable
|
||||
case lists:keysearch(ArgName, 1, ArgumentsTagged) of
|
||||
{value, {ArgName, ArgValue}} -> ArgValue == ArgAllowedValue;
|
||||
false -> true
|
||||
end
|
||||
end, ArgumentRestrictions).
|
||||
|
||||
tag_arguments(ArgsDefs, Args) ->
|
||||
lists:zipwith(
|
||||
fun({ArgName, _ArgType}, ArgValue) ->
|
||||
{ArgName, ArgValue}
|
||||
end,
|
||||
ArgsDefs,
|
||||
Args).
|
||||
@@ -0,0 +1,52 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2011 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(ejabberd_commands, {name, tags = [],
|
||||
desc = "", longdesc = "",
|
||||
module, function,
|
||||
args = [], result = rescode}).
|
||||
|
||||
%% @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.
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 14 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -30,9 +30,21 @@
|
||||
-export([start/0, load_file/1,
|
||||
add_global_option/2, add_local_option/2,
|
||||
get_global_option/1, get_local_option/1]).
|
||||
-export([get_vh_by_auth_method/1]).
|
||||
-export([is_file_readable/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_config.hrl").
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
|
||||
%% @type macro() = {macro_key(), macro_value()}
|
||||
|
||||
%% @type macro_key() = atom().
|
||||
%% The atom must have all characters in uppercase.
|
||||
|
||||
%% @type macro_value() = term().
|
||||
|
||||
|
||||
start() ->
|
||||
mnesia:create_table(config,
|
||||
@@ -44,30 +56,76 @@ start() ->
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, local_config)}]),
|
||||
mnesia:add_table_copy(local_config, node(), ram_copies),
|
||||
Config = case application:get_env(config) of
|
||||
{ok, Path} -> Path;
|
||||
undefined ->
|
||||
case os:getenv("EJABBERD_CONFIG_PATH") of
|
||||
false ->
|
||||
?CONFIG_PATH;
|
||||
Path ->
|
||||
Path
|
||||
end
|
||||
end,
|
||||
load_file(Config).
|
||||
Config = get_ejabberd_config_path(),
|
||||
load_file(Config),
|
||||
%% This start time is used by mod_last:
|
||||
add_local_option(node_start, now()),
|
||||
ok.
|
||||
|
||||
%% @doc Get the filename of the ejabberd configuration file.
|
||||
%% The filename can be specified with: erl -config "/path/to/ejabberd.cfg".
|
||||
%% It can also be specified with the environtment variable EJABBERD_CONFIG_PATH.
|
||||
%% If not specified, the default value 'ejabberd.cfg' is assumed.
|
||||
%% @spec () -> string()
|
||||
get_ejabberd_config_path() ->
|
||||
case application:get_env(config) of
|
||||
{ok, Path} -> Path;
|
||||
undefined ->
|
||||
case os:getenv("EJABBERD_CONFIG_PATH") of
|
||||
false ->
|
||||
?CONFIG_PATH;
|
||||
Path ->
|
||||
Path
|
||||
end
|
||||
end.
|
||||
|
||||
%% @doc Load the ejabberd configuration file.
|
||||
%% It also includes additional configuration files and replaces macros.
|
||||
%% This function will crash if finds some error in the configuration file.
|
||||
%% @spec (File::string()) -> ok
|
||||
load_file(File) ->
|
||||
Terms = get_plain_terms_file(File),
|
||||
State = lists:foldl(fun search_hosts/2, #state{}, Terms),
|
||||
Terms_macros = replace_macros(Terms),
|
||||
Res = lists:foldl(fun process_term/2, State, Terms_macros),
|
||||
set_opts(Res).
|
||||
|
||||
%% @doc Read an ejabberd configuration file and return the terms.
|
||||
%% Input is an absolute or relative path to an ejabberd config file.
|
||||
%% Returns a list of plain terms,
|
||||
%% in which the options 'include_config_file' were parsed
|
||||
%% and the terms in those files were included.
|
||||
%% @spec(string()) -> [term()]
|
||||
get_plain_terms_file(File1) ->
|
||||
File = get_absolute_path(File1),
|
||||
case file:consult(File) of
|
||||
{ok, Terms} ->
|
||||
State = lists:foldl(fun search_hosts/2, #state{}, Terms),
|
||||
Res = lists:foldl(fun process_term/2, State, Terms),
|
||||
set_opts(Res);
|
||||
include_config_files(Terms);
|
||||
{error, {LineNumber, erl_parse, _ParseMessage} = Reason} ->
|
||||
ExitText = describe_config_problem(File, Reason, LineNumber),
|
||||
?ERROR_MSG(ExitText, []),
|
||||
exit_or_halt(ExitText);
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("Can't load config file ~p: ~p", [File, Reason]),
|
||||
exit(File ++ ": " ++ file:format_error(Reason))
|
||||
ExitText = describe_config_problem(File, Reason),
|
||||
?ERROR_MSG(ExitText, []),
|
||||
exit_or_halt(ExitText)
|
||||
end.
|
||||
|
||||
%% @doc Convert configuration filename to absolute path.
|
||||
%% Input is an absolute or relative path to an ejabberd configuration file.
|
||||
%% And returns an absolute path to the configuration file.
|
||||
%% @spec (string()) -> string()
|
||||
get_absolute_path(File) ->
|
||||
case filename:pathtype(File) of
|
||||
absolute ->
|
||||
File;
|
||||
relative ->
|
||||
Config_path = get_ejabberd_config_path(),
|
||||
Config_dir = filename:dirname(Config_path),
|
||||
filename:absname_join(Config_dir, File)
|
||||
end.
|
||||
|
||||
|
||||
search_hosts(Term, State) ->
|
||||
case Term of
|
||||
{host, Host} ->
|
||||
@@ -110,6 +168,192 @@ normalize_hosts([Host|Hosts], PrepHosts) ->
|
||||
normalize_hosts(Hosts, [PrepHost|PrepHosts])
|
||||
end.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% Errors reading the config file
|
||||
|
||||
describe_config_problem(Filename, Reason) ->
|
||||
Text1 = lists:flatten("Problem loading ejabberd config file " ++ Filename),
|
||||
Text2 = lists:flatten(" : " ++ file:format_error(Reason)),
|
||||
ExitText = Text1 ++ Text2,
|
||||
ExitText.
|
||||
|
||||
describe_config_problem(Filename, Reason, LineNumber) ->
|
||||
Text1 = lists:flatten("Problem loading ejabberd config file " ++ Filename),
|
||||
Text2 = lists:flatten(" approximately in the line "
|
||||
++ file:format_error(Reason)),
|
||||
ExitText = Text1 ++ Text2,
|
||||
Lines = get_config_lines(Filename, LineNumber, 10, 3),
|
||||
?ERROR_MSG("The following lines from your configuration file might be"
|
||||
" relevant to the error: ~n~s", [Lines]),
|
||||
ExitText.
|
||||
|
||||
get_config_lines(Filename, TargetNumber, PreContext, PostContext) ->
|
||||
{ok, Fd} = file:open(Filename, [read]),
|
||||
LNumbers = lists:seq(TargetNumber-PreContext, TargetNumber+PostContext),
|
||||
NextL = io:get_line(Fd, no_prompt),
|
||||
R = get_config_lines2(Fd, NextL, 1, LNumbers, []),
|
||||
file:close(Fd),
|
||||
R.
|
||||
|
||||
get_config_lines2(_Fd, eof, _CurrLine, _LNumbers, R) ->
|
||||
lists:reverse(R);
|
||||
get_config_lines2(_Fd, _NewLine, _CurrLine, [], R) ->
|
||||
lists:reverse(R);
|
||||
get_config_lines2(Fd, Data, CurrLine, [NextWanted | LNumbers], R) when is_list(Data) ->
|
||||
NextL = io:get_line(Fd, no_prompt),
|
||||
if
|
||||
CurrLine >= NextWanted ->
|
||||
Line2 = [integer_to_list(CurrLine), ": " | Data],
|
||||
get_config_lines2(Fd, NextL, CurrLine+1, LNumbers, [Line2 | R]);
|
||||
true ->
|
||||
get_config_lines2(Fd, NextL, CurrLine+1, [NextWanted | LNumbers], R)
|
||||
end.
|
||||
|
||||
%% If ejabberd isn't yet running in this node, then halt the node
|
||||
exit_or_halt(ExitText) ->
|
||||
case [Vsn || {ejabberd, _Desc, Vsn} <- application:which_applications()] of
|
||||
[] ->
|
||||
timer:sleep(1000),
|
||||
halt(string:substr(ExitText, 1, 199));
|
||||
[_] ->
|
||||
exit(ExitText)
|
||||
end.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% Support for 'include_config_file'
|
||||
|
||||
%% @doc Include additional configuration files in the list of terms.
|
||||
%% @spec ([term()]) -> [term()]
|
||||
include_config_files(Terms) ->
|
||||
include_config_files(Terms, []).
|
||||
|
||||
include_config_files([], Res) ->
|
||||
Res;
|
||||
include_config_files([{include_config_file, Filename} | Terms], Res) ->
|
||||
include_config_files([{include_config_file, Filename, []} | Terms], Res);
|
||||
include_config_files([{include_config_file, Filename, Options} | Terms], Res) ->
|
||||
Included_terms = get_plain_terms_file(Filename),
|
||||
Disallow = proplists:get_value(disallow, Options, []),
|
||||
Included_terms2 = delete_disallowed(Disallow, Included_terms),
|
||||
Allow_only = proplists:get_value(allow_only, Options, all),
|
||||
Included_terms3 = keep_only_allowed(Allow_only, Included_terms2),
|
||||
include_config_files(Terms, Res ++ Included_terms3);
|
||||
include_config_files([Term | Terms], Res) ->
|
||||
include_config_files(Terms, Res ++ [Term]).
|
||||
|
||||
%% @doc Filter from the list of terms the disallowed.
|
||||
%% Returns a sublist of Terms without the ones which first element is
|
||||
%% included in Disallowed.
|
||||
%% @spec (Disallowed::[atom()], Terms::[term()]) -> [term()]
|
||||
delete_disallowed(Disallowed, Terms) ->
|
||||
lists:foldl(
|
||||
fun(Dis, Ldis) ->
|
||||
delete_disallowed2(Dis, Ldis)
|
||||
end,
|
||||
Terms,
|
||||
Disallowed).
|
||||
|
||||
delete_disallowed2(Disallowed, [H|T]) ->
|
||||
case element(1, H) of
|
||||
Disallowed ->
|
||||
?WARNING_MSG("The option '~p' is disallowed, "
|
||||
"and will not be accepted", [Disallowed]),
|
||||
delete_disallowed2(Disallowed, T);
|
||||
_ ->
|
||||
[H|delete_disallowed2(Disallowed, T)]
|
||||
end;
|
||||
delete_disallowed2(_, []) ->
|
||||
[].
|
||||
|
||||
%% @doc Keep from the list only the allowed terms.
|
||||
%% Returns a sublist of Terms with only the ones which first element is
|
||||
%% included in Allowed.
|
||||
%% @spec (Allowed::[atom()], Terms::[term()]) -> [term()]
|
||||
keep_only_allowed(all, Terms) ->
|
||||
Terms;
|
||||
keep_only_allowed(Allowed, Terms) ->
|
||||
{As, NAs} = lists:partition(
|
||||
fun(Term) ->
|
||||
lists:member(element(1, Term), Allowed)
|
||||
end,
|
||||
Terms),
|
||||
[?WARNING_MSG("This option is not allowed, "
|
||||
"and will not be accepted:~n~p", [NA])
|
||||
|| NA <- NAs],
|
||||
As.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% Support for Macro
|
||||
|
||||
%% @doc Replace the macros with their defined values.
|
||||
%% @spec (Terms::[term()]) -> [term()]
|
||||
replace_macros(Terms) ->
|
||||
{TermsOthers, Macros} = split_terms_macros(Terms),
|
||||
replace(TermsOthers, Macros).
|
||||
|
||||
%% @doc Split Terms into normal terms and macro definitions.
|
||||
%% @spec (Terms) -> {Terms, Macros}
|
||||
%% Terms = [term()]
|
||||
%% Macros = [macro()]
|
||||
split_terms_macros(Terms) ->
|
||||
lists:foldl(
|
||||
fun(Term, {TOs, Ms}) ->
|
||||
case Term of
|
||||
{define_macro, Key, Value} ->
|
||||
case is_atom(Key) and is_all_uppercase(Key) of
|
||||
true ->
|
||||
{TOs, Ms++[{Key, Value}]};
|
||||
false ->
|
||||
exit({macro_not_properly_defined, Term})
|
||||
end;
|
||||
Term ->
|
||||
{TOs ++ [Term], Ms}
|
||||
end
|
||||
end,
|
||||
{[], []},
|
||||
Terms).
|
||||
|
||||
%% @doc Recursively replace in Terms macro usages with the defined value.
|
||||
%% @spec (Terms, Macros) -> Terms
|
||||
%% Terms = [term()]
|
||||
%% Macros = [macro()]
|
||||
replace([], _) ->
|
||||
[];
|
||||
replace([Term|Terms], Macros) ->
|
||||
[replace_term(Term, Macros) | replace(Terms, Macros)].
|
||||
|
||||
replace_term(Key, Macros) when is_atom(Key) ->
|
||||
case is_all_uppercase(Key) of
|
||||
true ->
|
||||
case proplists:get_value(Key, Macros) of
|
||||
undefined -> exit({undefined_macro, Key});
|
||||
Value -> Value
|
||||
end;
|
||||
false ->
|
||||
Key
|
||||
end;
|
||||
replace_term({use_macro, Key, Value}, Macros) ->
|
||||
proplists:get_value(Key, Macros, Value);
|
||||
replace_term(Term, Macros) when is_list(Term) ->
|
||||
replace(Term, Macros);
|
||||
replace_term(Term, Macros) when is_tuple(Term) ->
|
||||
List = tuple_to_list(Term),
|
||||
List2 = replace(List, Macros),
|
||||
list_to_tuple(List2);
|
||||
replace_term(Term, _) ->
|
||||
Term.
|
||||
|
||||
is_all_uppercase(Atom) ->
|
||||
String = erlang:atom_to_list(Atom),
|
||||
lists:all(fun(C) when C >= $a, C =< $z -> false;
|
||||
(_) -> true
|
||||
end, String).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% Process terms
|
||||
|
||||
process_term(Term, State) ->
|
||||
case Term of
|
||||
override_global ->
|
||||
@@ -133,18 +377,42 @@ process_term(Term, State) ->
|
||||
{host_config, Host, Terms} ->
|
||||
lists:foldl(fun(T, S) -> process_host_term(T, Host, S) end,
|
||||
State, Terms);
|
||||
{listen, Val} ->
|
||||
add_option(listen, Val, State);
|
||||
{listen, Listeners} ->
|
||||
Listeners2 =
|
||||
lists:map(
|
||||
fun({PortIP, Module, Opts}) ->
|
||||
{Port, IPT, _, _, Proto, OptsClean} =
|
||||
ejabberd_listener:parse_listener_portip(PortIP, Opts),
|
||||
{{Port, IPT, Proto}, Module, OptsClean}
|
||||
end,
|
||||
Listeners),
|
||||
add_option(listen, Listeners2, State);
|
||||
{language, Val} ->
|
||||
add_option(language, Val, State);
|
||||
{outgoing_s2s_port, Port} ->
|
||||
add_option(outgoing_s2s_port, Port, State);
|
||||
{outgoing_s2s_options, Methods, Timeout} ->
|
||||
add_option(outgoing_s2s_options, {Methods, Timeout}, State);
|
||||
{s2s_dns_options, PropList} ->
|
||||
add_option(s2s_dns_options, PropList, State);
|
||||
{s2s_use_starttls, Port} ->
|
||||
add_option(s2s_use_starttls, Port, State);
|
||||
{s2s_certfile, CertFile} ->
|
||||
add_option(s2s_certfile, CertFile, State);
|
||||
case ejabberd_config:is_file_readable(CertFile) of
|
||||
true -> add_option(s2s_certfile, CertFile, State);
|
||||
false ->
|
||||
ErrorText = "There is a problem in the configuration: "
|
||||
"the specified file is not readable: ",
|
||||
throw({error, ErrorText ++ CertFile})
|
||||
end;
|
||||
{domain_certfile, Domain, CertFile} ->
|
||||
add_option({domain_certfile, Domain}, CertFile, State);
|
||||
case ejabberd_config:is_file_readable(CertFile) of
|
||||
true -> add_option({domain_certfile, Domain}, CertFile, State);
|
||||
false ->
|
||||
ErrorText = "There is a problem in the configuration: "
|
||||
"the specified file is not readable: ",
|
||||
throw({error, ErrorText ++ CertFile})
|
||||
end;
|
||||
{node_type, NodeType} ->
|
||||
add_option(node_type, NodeType, State);
|
||||
{cluster_nodes, Nodes} ->
|
||||
@@ -155,9 +423,23 @@ process_term(Term, State) ->
|
||||
add_option({domain_balancing_component_number, Domain}, N, State);
|
||||
{watchdog_admins, Admins} ->
|
||||
add_option(watchdog_admins, Admins, State);
|
||||
{watchdog_large_heap, LH} ->
|
||||
add_option(watchdog_large_heap, LH, State);
|
||||
{registration_timeout, Timeout} ->
|
||||
add_option(registration_timeout, Timeout, State);
|
||||
{captcha_cmd, Cmd} ->
|
||||
add_option(captcha_cmd, Cmd, State);
|
||||
{captcha_host, Host} ->
|
||||
add_option(captcha_host, Host, State);
|
||||
{captcha_limit, Limit} ->
|
||||
add_option(captcha_limit, Limit, State);
|
||||
{ejabberdctl_access_commands, ACs} ->
|
||||
add_option(ejabberdctl_access_commands, ACs, State);
|
||||
{loglevel, Loglevel} ->
|
||||
ejabberd_loglevel:set(Loglevel),
|
||||
State;
|
||||
{max_fsm_queue, N} ->
|
||||
add_option(max_fsm_queue, N, State);
|
||||
{_Opt, _Val} ->
|
||||
lists:foldl(fun(Host, S) -> process_host_term(Term, Host, S) end,
|
||||
State, State#state.hosts)
|
||||
@@ -181,7 +463,6 @@ process_host_term(Term, Host, State) ->
|
||||
{hosts, _Hosts} ->
|
||||
State;
|
||||
{odbc_server, ODBC_server} ->
|
||||
odbc_modules_found = check_odbc_modules(ODBC_server),
|
||||
add_option({odbc_server, Host}, ODBC_server, State);
|
||||
{Opt, Val} ->
|
||||
add_option({Opt, Host}, Val, State)
|
||||
@@ -211,11 +492,16 @@ add_option(Opt, Val, State) ->
|
||||
end
|
||||
end.
|
||||
|
||||
compact(Opt, Val, [], Os) ->
|
||||
compact({OptName, Host} = Opt, Val, [], Os) ->
|
||||
?WARNING_MSG("The option '~p' is defined for the host ~p using host_config "
|
||||
"before the global '~p' option. This host_config option may get overwritten.", [OptName, Host, OptName]),
|
||||
[#local_config{key = Opt, value = Val}] ++ Os;
|
||||
%% Traverse the list of the options already parsed
|
||||
compact(Opt, Val, [O | Os1], Os2) ->
|
||||
case O#local_config.key of
|
||||
case catch O#local_config.key of
|
||||
%% If the key of a local_config matches the Opt that wants to be added
|
||||
Opt ->
|
||||
%% Then prepend the new value to the list of old values
|
||||
Os2 ++ [#local_config{key = Opt,
|
||||
value = Val++O#local_config.value}
|
||||
] ++ Os1;
|
||||
@@ -303,33 +589,21 @@ get_local_option(Opt) ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
%% Return the list of hosts handled by a given module
|
||||
get_vh_by_auth_method(AuthMethod) ->
|
||||
mnesia:dirty_select(local_config,
|
||||
[{#local_config{key = {auth_method, '$1'},
|
||||
value=AuthMethod},[],['$1']}]).
|
||||
|
||||
check_odbc_modules(ODBC_server) ->
|
||||
case catch check_odbc_modules2(ODBC_server) of
|
||||
{'EXIT', {undef, [{Module, module_info, []} | _]}} ->
|
||||
?CRITICAL_MSG("ejabberd is configured to use ODBC, but the Erlang module '~p' is not installed.", [Module]),
|
||||
odbc_module_not_found;
|
||||
_ -> odbc_modules_found
|
||||
%% @spec (Path::string()) -> true | false
|
||||
is_file_readable(Path) ->
|
||||
case file:read_file_info(Path) of
|
||||
{ok, FileInfo} ->
|
||||
case {FileInfo#file_info.type, FileInfo#file_info.access} of
|
||||
{regular, read} -> true;
|
||||
{regular, read_write} -> true;
|
||||
_ -> false
|
||||
end;
|
||||
{error, _Reason} ->
|
||||
false
|
||||
end.
|
||||
|
||||
check_odbc_modules2(ODBC_server) ->
|
||||
check_modules_exists([ejabberd_odbc, ejabberd_odbc_sup, odbc_queries]),
|
||||
case ODBC_server of
|
||||
{mysql, _Server, _DB, _Username, _Password} ->
|
||||
check_modules_exists([mysql, mysql_auth, mysql_conn, mysql_recv]);
|
||||
|
||||
{mysql, _Server, _Port, _DB, _Username, _Password} ->
|
||||
check_modules_exists([mysql, mysql_auth, mysql_conn, mysql_recv]);
|
||||
|
||||
{pgsql, _Server, _DB, _Username, _Password} ->
|
||||
check_modules_exists([pgsql, pgsql_proto, pgsql_tcp, pgsql_util]);
|
||||
|
||||
{pgsql, _Server, _Port, _DB, _Username, _Password} ->
|
||||
check_modules_exists([pgsql, pgsql_proto, pgsql_tcp, pgsql_util]);
|
||||
|
||||
Server when is_list(Server) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
check_modules_exists(Modules) ->
|
||||
[true = is_list(Module:module_info()) || Module <- Modules].
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -11,7 +11,7 @@
|
||||
%%% 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -11,7 +11,7 @@
|
||||
%%% 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
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 23 Aug 2006 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -53,6 +53,8 @@
|
||||
|
||||
-record(state, {sockmod, socket, receiver}).
|
||||
|
||||
-define(HIBERNATE_TIMEOUT, 90000).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@@ -92,8 +94,9 @@ start(Module, SockMod, Socket, Opts) ->
|
||||
todo
|
||||
end.
|
||||
|
||||
starttls(FsmRef, TLSOpts) ->
|
||||
gen_server:call(FsmRef, {starttls, TLSOpts}),
|
||||
starttls(FsmRef, _TLSOpts) ->
|
||||
%% TODO: Frontend improvements planned by Aleksey
|
||||
%%gen_server:call(FsmRef, {starttls, TLSOpts}),
|
||||
FsmRef.
|
||||
|
||||
starttls(FsmRef, TLSOpts, Data) ->
|
||||
@@ -135,8 +138,10 @@ close(FsmRef) ->
|
||||
sockname(FsmRef) ->
|
||||
gen_server:call(FsmRef, sockname).
|
||||
|
||||
peername(FsmRef) ->
|
||||
gen_server:call(FsmRef, peername).
|
||||
peername(_FsmRef) ->
|
||||
%% TODO: Frontend improvements planned by Aleksey
|
||||
%%gen_server:call(FsmRef, peername).
|
||||
{ok, {{0, 0, 0, 0}, 0}}.
|
||||
|
||||
|
||||
%%====================================================================
|
||||
@@ -153,11 +158,12 @@ peername(FsmRef) ->
|
||||
init([Module, SockMod, Socket, Opts, Receiver]) ->
|
||||
%% TODO: monitor the receiver
|
||||
Node = ejabberd_node_groups:get_closest_node(backend),
|
||||
{SockMod2, Socket2} = check_starttls(SockMod, Socket, Receiver, Opts),
|
||||
{ok, Pid} =
|
||||
rpc:call(Node, Module, start, [{?MODULE, self()}, Opts]),
|
||||
ejabberd_receiver:become_controller(Receiver, Pid),
|
||||
{ok, #state{sockmod = SockMod,
|
||||
socket = Socket,
|
||||
{ok, #state{sockmod = SockMod2,
|
||||
socket = Socket2,
|
||||
receiver = Receiver}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -173,7 +179,8 @@ handle_call({starttls, TLSOpts}, _From, State) ->
|
||||
{ok, TLSSocket} = tls:tcp_to_tls(State#state.socket, TLSOpts),
|
||||
ejabberd_receiver:starttls(State#state.receiver, TLSSocket),
|
||||
Reply = ok,
|
||||
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls}};
|
||||
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call({starttls, TLSOpts, Data}, _From, State) ->
|
||||
{ok, TLSSocket} = tls:tcp_to_tls(State#state.socket, TLSOpts),
|
||||
@@ -181,7 +188,8 @@ handle_call({starttls, TLSOpts, Data}, _From, State) ->
|
||||
catch (State#state.sockmod):send(
|
||||
State#state.socket, Data),
|
||||
Reply = ok,
|
||||
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls}};
|
||||
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(compress, _From, State) ->
|
||||
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
|
||||
@@ -189,7 +197,8 @@ handle_call(compress, _From, State) ->
|
||||
State#state.socket),
|
||||
ejabberd_receiver:compress(State#state.receiver, ZlibSocket),
|
||||
Reply = ok,
|
||||
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib}};
|
||||
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call({compress, Data}, _From, State) ->
|
||||
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
|
||||
@@ -199,35 +208,36 @@ handle_call({compress, Data}, _From, State) ->
|
||||
catch (State#state.sockmod):send(
|
||||
State#state.socket, Data),
|
||||
Reply = ok,
|
||||
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib}};
|
||||
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(reset_stream, _From, State) ->
|
||||
ejabberd_receiver:reset_stream(State#state.receiver),
|
||||
Reply = ok,
|
||||
{reply, Reply, State};
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call({send, Data}, _From, State) ->
|
||||
catch (State#state.sockmod):send(
|
||||
State#state.socket, Data),
|
||||
Reply = ok,
|
||||
{reply, Reply, State};
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call({change_shaper, Shaper}, _From, State) ->
|
||||
ejabberd_receiver:change_shaper(State#state.receiver, Shaper),
|
||||
Reply = ok,
|
||||
{reply, Reply, State};
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(get_sockmod, _From, State) ->
|
||||
Reply = State#state.sockmod,
|
||||
{reply, Reply, State};
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(get_peer_certificate, _From, State) ->
|
||||
Reply = tls:get_peer_certificate(State#state.socket),
|
||||
{reply, Reply, State};
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(get_verify_result, _From, State) ->
|
||||
Reply = tls:get_verify_result(State#state.socket),
|
||||
{reply, Reply, State};
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(close, _From, State) ->
|
||||
ejabberd_receiver:close(State#state.receiver),
|
||||
@@ -243,7 +253,7 @@ handle_call(sockname, _From, State) ->
|
||||
_ ->
|
||||
SockMod:sockname(Socket)
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(peername, _From, State) ->
|
||||
#state{sockmod = SockMod, socket = Socket} = State,
|
||||
@@ -254,11 +264,11 @@ handle_call(peername, _From, State) ->
|
||||
_ ->
|
||||
SockMod:peername(Socket)
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
@@ -267,7 +277,7 @@ handle_call(_Request, _From, State) ->
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
@@ -275,8 +285,11 @@ handle_cast(_Msg, State) ->
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info(timeout, State) ->
|
||||
proc_lib:hibernate(gen_server, enter_loop, [?MODULE, [], State]),
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
@@ -298,3 +311,17 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
check_starttls(SockMod, Socket, Receiver, Opts) ->
|
||||
TLSEnabled = lists:member(tls, Opts),
|
||||
TLSOpts = lists:filter(fun({certfile, _}) -> true;
|
||||
(_) -> false
|
||||
end, Opts),
|
||||
if
|
||||
TLSEnabled ->
|
||||
{ok, TLSSocket} = tls:tcp_to_tls(Socket, TLSOpts),
|
||||
ejabberd_receiver:starttls(Receiver, TLSSocket),
|
||||
{tls, TLSSocket};
|
||||
true ->
|
||||
{SockMod, Socket}
|
||||
end.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 8 Aug 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -31,12 +31,18 @@
|
||||
|
||||
%% External exports
|
||||
-export([start_link/0,
|
||||
add/3,
|
||||
add/4,
|
||||
add_dist/5,
|
||||
delete/3,
|
||||
delete/4,
|
||||
delete_dist/5,
|
||||
run/2,
|
||||
run_fold/3,
|
||||
add/5,
|
||||
add_dist/6,
|
||||
delete/5,
|
||||
delete_dist/6,
|
||||
run/3,
|
||||
run_fold/4]).
|
||||
|
||||
@@ -50,6 +56,9 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
%% Timeout of 5 seconds in calls to distributed hooks
|
||||
-define(TIMEOUT_DISTRIBUTED_HOOK, 5000).
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -58,18 +67,55 @@
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ejabberd_hooks}, ejabberd_hooks, [], []).
|
||||
|
||||
%% @spec (Hook::atom(), Function::function(), Seq::integer()) -> ok
|
||||
%% @doc See add/4.
|
||||
add(Hook, Function, Seq) when is_function(Function) ->
|
||||
add(Hook, global, undefined, Function, Seq).
|
||||
|
||||
add(Hook, Host, Function, Seq) when is_function(Function) ->
|
||||
add(Hook, Host, undefined, Function, Seq);
|
||||
|
||||
%% @spec (Hook::atom(), Module::atom(), Function::atom(), Seq::integer()) -> ok
|
||||
%% @doc Add a module and function to this hook.
|
||||
%% The integer sequence is used to sort the calls: low number is called before high number.
|
||||
add(Hook, Module, Function, Seq) ->
|
||||
add(Hook, global, Module, Function, Seq).
|
||||
|
||||
add(Hook, Host, Module, Function, Seq) ->
|
||||
gen_server:call(ejabberd_hooks, {add, Hook, Host, Module, Function, Seq}).
|
||||
|
||||
add_dist(Hook, Node, Module, Function, Seq) ->
|
||||
gen_server:call(ejabberd_hooks, {add, Hook, global, Node, Module, Function, Seq}).
|
||||
|
||||
add_dist(Hook, Host, Node, Module, Function, Seq) ->
|
||||
gen_server:call(ejabberd_hooks, {add, Hook, Host, Node, Module, Function, Seq}).
|
||||
|
||||
%% @spec (Hook::atom(), Function::function(), Seq::integer()) -> ok
|
||||
%% @doc See del/4.
|
||||
delete(Hook, Function, Seq) when is_function(Function) ->
|
||||
delete(Hook, global, undefined, Function, Seq).
|
||||
|
||||
delete(Hook, Host, Function, Seq) when is_function(Function) ->
|
||||
delete(Hook, Host, undefined, Function, Seq);
|
||||
|
||||
%% @spec (Hook::atom(), Module::atom(), Function::atom(), Seq::integer()) -> ok
|
||||
%% @doc Delete a module and function from this hook.
|
||||
%% It is important to indicate exactly the same information than when the call was added.
|
||||
delete(Hook, Module, Function, Seq) ->
|
||||
delete(Hook, global, Module, Function, Seq).
|
||||
|
||||
delete(Hook, Host, Module, Function, Seq) ->
|
||||
gen_server:call(ejabberd_hooks, {delete, Hook, Host, Module, Function, Seq}).
|
||||
|
||||
delete_dist(Hook, Node, Module, Function, Seq) ->
|
||||
delete_dist(Hook, global, Node, Module, Function, Seq).
|
||||
|
||||
delete_dist(Hook, Host, Node, Module, Function, Seq) ->
|
||||
gen_server:call(ejabberd_hooks, {delete, Hook, Host, Node, Module, Function, Seq}).
|
||||
|
||||
%% @spec (Hook::atom(), Args) -> ok
|
||||
%% @doc Run the calls of this hook in order, don't care about function results.
|
||||
%% If a call returns stop, no more calls are performed.
|
||||
run(Hook, Args) ->
|
||||
run(Hook, global, Args).
|
||||
|
||||
@@ -81,6 +127,12 @@ run(Hook, Host, Args) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%% @spec (Hook::atom(), Val, Args) -> Val | stopped | NewVal
|
||||
%% @doc Run the calls of this hook in order.
|
||||
%% The arguments passed to the function are: [Val | Args].
|
||||
%% The result of a call is used as Val for the next call.
|
||||
%% If a call returns 'stop', no more calls are performed and 'stopped' is returned.
|
||||
%% If a call returns {stopped, NewVal}, no more calls are performed and NewVal is returned.
|
||||
run_fold(Hook, Val, Args) ->
|
||||
run_fold(Hook, global, Val, Args).
|
||||
|
||||
@@ -134,6 +186,24 @@ handle_call({add, Hook, Host, Module, Function, Seq}, _From, State) ->
|
||||
ok
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
handle_call({add, Hook, Host, Node, Module, Function, Seq}, _From, State) ->
|
||||
Reply = case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
El = {Seq, Node, Module, Function},
|
||||
case lists:member(El, Ls) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
NewLs = lists:merge(Ls, [El]),
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs}),
|
||||
ok
|
||||
end;
|
||||
[] ->
|
||||
NewLs = [{Seq, Node, Module, Function}],
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs}),
|
||||
ok
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
handle_call({delete, Hook, Host, Module, Function, Seq}, _From, State) ->
|
||||
Reply = case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
@@ -144,6 +214,16 @@ handle_call({delete, Hook, Host, Module, Function, Seq}, _From, State) ->
|
||||
ok
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
handle_call({delete, Hook, Host, Node, Module, Function, Seq}, _From, State) ->
|
||||
Reply = case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
NewLs = lists:delete({Seq, Node, Module, Function}, Ls),
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs}),
|
||||
ok;
|
||||
[] ->
|
||||
ok
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
@@ -184,8 +264,32 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
|
||||
run1([], _Hook, _Args) ->
|
||||
ok;
|
||||
run1([{_Seq, Node, Module, Function} | Ls], Hook, Args) ->
|
||||
case rpc:call(Node, Module, Function, Args, ?TIMEOUT_DISTRIBUTED_HOOK) of
|
||||
timeout ->
|
||||
?ERROR_MSG("Timeout on RPC to ~p~nrunning hook: ~p",
|
||||
[Node, {Hook, Args}]),
|
||||
run1(Ls, Hook, Args);
|
||||
{badrpc, Reason} ->
|
||||
?ERROR_MSG("Bad RPC error to ~p: ~p~nrunning hook: ~p",
|
||||
[Node, Reason, {Hook, Args}]),
|
||||
run1(Ls, Hook, Args);
|
||||
stop ->
|
||||
?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n"
|
||||
"Stop.", [self(), node(), Node]), % debug code
|
||||
ok;
|
||||
Res ->
|
||||
?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n"
|
||||
"The response is:~n~s", [self(), node(), Node, Res]), % debug code
|
||||
run1(Ls, Hook, Args)
|
||||
end;
|
||||
run1([{_Seq, Module, Function} | Ls], Hook, Args) ->
|
||||
case catch apply(Module, Function, Args) of
|
||||
Res = if is_function(Function) ->
|
||||
catch apply(Function, Args);
|
||||
true ->
|
||||
catch apply(Module, Function, Args)
|
||||
end,
|
||||
case Res of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nrunning hook: ~p",
|
||||
[Reason, {Hook, Args}]),
|
||||
@@ -199,8 +303,34 @@ run1([{_Seq, Module, Function} | Ls], Hook, Args) ->
|
||||
|
||||
run_fold1([], _Hook, Val, _Args) ->
|
||||
Val;
|
||||
run_fold1([{_Seq, Node, Module, Function} | Ls], Hook, Val, Args) ->
|
||||
case rpc:call(Node, Module, Function, [Val | Args], ?TIMEOUT_DISTRIBUTED_HOOK) of
|
||||
{badrpc, Reason} ->
|
||||
?ERROR_MSG("Bad RPC error to ~p: ~p~nrunning hook: ~p",
|
||||
[Node, Reason, {Hook, Args}]),
|
||||
run_fold1(Ls, Hook, Val, Args);
|
||||
timeout ->
|
||||
?ERROR_MSG("Timeout on RPC to ~p~nrunning hook: ~p",
|
||||
[Node, {Hook, Args}]),
|
||||
run_fold1(Ls, Hook, Val, Args);
|
||||
stop ->
|
||||
stopped;
|
||||
{stop, NewVal} ->
|
||||
?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n"
|
||||
"Stop, and the NewVal is:~n~p", [self(), node(), Node, NewVal]), % debug code
|
||||
NewVal;
|
||||
NewVal ->
|
||||
?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n"
|
||||
"The NewVal is:~n~p", [self(), node(), Node, NewVal]), % debug code
|
||||
run_fold1(Ls, Hook, NewVal, Args)
|
||||
end;
|
||||
run_fold1([{_Seq, Module, Function} | Ls], Hook, Val, Args) ->
|
||||
case catch apply(Module, Function, [Val | Args]) of
|
||||
Res = if is_function(Function) ->
|
||||
catch apply(Function, [Val | Args]);
|
||||
true ->
|
||||
catch apply(Module, Function, [Val | Args])
|
||||
end,
|
||||
case Res of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nrunning hook: ~p",
|
||||
[Reason, {Hook, Args}]),
|
||||
@@ -212,6 +342,3 @@ run_fold1([{_Seq, Module, Function} | Ls], Hook, Val, Args) ->
|
||||
NewVal ->
|
||||
run_fold1(Ls, Hook, NewVal, Args)
|
||||
end.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 16 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -29,81 +29,250 @@
|
||||
|
||||
-export([start_link/0, init/1, start/3,
|
||||
init/3,
|
||||
init_ssl/4,
|
||||
start_listeners/0,
|
||||
start_listener/3,
|
||||
stop_listener/1,
|
||||
stop_listeners/0,
|
||||
stop_listener/2,
|
||||
parse_listener_portip/2,
|
||||
add_listener/3,
|
||||
delete_listener/1
|
||||
delete_listener/2
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
%% We do not block on send anymore.
|
||||
-define(TCP_SEND_TIMEOUT, 15000).
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ejabberd_listeners}, ?MODULE, []).
|
||||
|
||||
|
||||
init(_) ->
|
||||
ets:new(listen_sockets, [named_table, public]),
|
||||
bind_tcp_ports(),
|
||||
{ok, {{one_for_one, 10, 1}, []}}.
|
||||
|
||||
bind_tcp_ports() ->
|
||||
case ejabberd_config:get_local_option(listen) of
|
||||
undefined ->
|
||||
ignore;
|
||||
Ls ->
|
||||
{ok, {{one_for_one, 10, 1},
|
||||
lists:map(
|
||||
fun({Port, Module, Opts}) ->
|
||||
{Port,
|
||||
{?MODULE, start, [Port, Module, Opts]},
|
||||
transient,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[?MODULE]}
|
||||
end, Ls)}}
|
||||
lists:foreach(
|
||||
fun({Port, Module, Opts}) ->
|
||||
ModuleRaw = strip_frontend(Module),
|
||||
case ModuleRaw:socket_type() of
|
||||
independent -> ok;
|
||||
_ ->
|
||||
bind_tcp_port(Port, Module, Opts)
|
||||
end
|
||||
end, Ls)
|
||||
end.
|
||||
|
||||
bind_tcp_port(PortIP, Module, RawOpts) ->
|
||||
try check_listener_options(RawOpts) of
|
||||
ok ->
|
||||
{Port, IPT, IPS, IPV, Proto, OptsClean} = parse_listener_portip(PortIP, RawOpts),
|
||||
{_Opts, SockOpts} = prepare_opts(IPT, IPV, OptsClean),
|
||||
case Proto of
|
||||
udp -> ok;
|
||||
_ ->
|
||||
ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS),
|
||||
ets:insert(listen_sockets, {PortIP, ListenSocket})
|
||||
end
|
||||
catch
|
||||
throw:{error, Error} ->
|
||||
?ERROR_MSG(Error, [])
|
||||
end.
|
||||
|
||||
start_listeners() ->
|
||||
case ejabberd_config:get_local_option(listen) of
|
||||
undefined ->
|
||||
ignore;
|
||||
Ls ->
|
||||
Ls2 = lists:map(
|
||||
fun({Port, Module, Opts}) ->
|
||||
case start_listener(Port, Module, Opts) of
|
||||
{ok, _Pid} = R -> R;
|
||||
{error, Error} ->
|
||||
throw(Error)
|
||||
end
|
||||
end, Ls),
|
||||
report_duplicated_portips(Ls),
|
||||
{ok, {{one_for_one, 10, 1}, Ls2}}
|
||||
end.
|
||||
|
||||
report_duplicated_portips(L) ->
|
||||
LKeys = [Port || {Port, _, _} <- L],
|
||||
LNoDupsKeys = proplists:get_keys(L),
|
||||
case LKeys -- LNoDupsKeys of
|
||||
[] -> ok;
|
||||
Dups ->
|
||||
?CRITICAL_MSG("In the ejabberd configuration there are duplicated "
|
||||
"Port number + IP address:~n ~p",
|
||||
[Dups])
|
||||
end.
|
||||
|
||||
start(Port, Module, Opts) ->
|
||||
SSLError = "There is a problem with your ejabberd configuration file: the option 'ssl' for listening sockets is no longer available. To get SSL encryption use the option 'tls'.",
|
||||
case lists:keysearch(ssl, 1, Opts) of
|
||||
{value, {ssl, _SSLOpts}} ->
|
||||
%%{ok, proc_lib:spawn_link(?MODULE, init_ssl,
|
||||
%% [Port, Module, Opts, SSLOpts])};
|
||||
?ERROR_MSG(SSLError, []),
|
||||
{error, SSLError};
|
||||
%% Check if the module is an ejabberd listener or an independent listener
|
||||
ModuleRaw = strip_frontend(Module),
|
||||
case ModuleRaw:socket_type() of
|
||||
independent -> ModuleRaw:start_listener(Port, Opts);
|
||||
_ -> start_dependent(Port, Module, Opts)
|
||||
end.
|
||||
|
||||
%% @spec(Port, Module, Opts) -> {ok, Pid} | {error, ErrorMessage}
|
||||
start_dependent(Port, Module, Opts) ->
|
||||
try check_listener_options(Opts) of
|
||||
ok ->
|
||||
proc_lib:start_link(?MODULE, init, [Port, Module, Opts])
|
||||
catch
|
||||
throw:{error, Error} ->
|
||||
?ERROR_MSG(Error, []),
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
init(PortIP, Module, RawOpts) ->
|
||||
{Port, IPT, IPS, IPV, Proto, OptsClean} = parse_listener_portip(PortIP, RawOpts),
|
||||
{Opts, SockOpts} = prepare_opts(IPT, IPV, OptsClean),
|
||||
if Proto == udp ->
|
||||
init_udp(PortIP, Module, Opts, SockOpts, Port, IPS);
|
||||
true ->
|
||||
init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS)
|
||||
end.
|
||||
|
||||
init_udp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
||||
case gen_udp:open(Port, [binary,
|
||||
{active, false},
|
||||
{reuseaddr, true} |
|
||||
SockOpts]) of
|
||||
{ok, Socket} ->
|
||||
%% Inform my parent that this port was opened succesfully
|
||||
proc_lib:init_ack({ok, self()}),
|
||||
udp_recv(Socket, Module, Opts);
|
||||
{error, Reason} ->
|
||||
socket_error(Reason, PortIP, Module, SockOpts, Port, IPS)
|
||||
end.
|
||||
|
||||
init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
||||
ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS),
|
||||
%% Inform my parent that this port was opened succesfully
|
||||
proc_lib:init_ack({ok, self()}),
|
||||
%% And now start accepting connection attempts
|
||||
accept(ListenSocket, Module, Opts).
|
||||
|
||||
listen_tcp(PortIP, Module, SockOpts, Port, IPS) ->
|
||||
case ets:lookup(listen_sockets, PortIP) of
|
||||
[{PortIP, ListenSocket}] ->
|
||||
?INFO_MSG("Reusing listening port for ~p", [Port]),
|
||||
ets:delete(listen_sockets, Port),
|
||||
ListenSocket;
|
||||
_ ->
|
||||
case lists:member(ssl, Opts) of
|
||||
true ->
|
||||
%%{ok, proc_lib:spawn_link(?MODULE, init_ssl,
|
||||
%% [Port, Module, Opts, []])};
|
||||
?ERROR_MSG(SSLError, []),
|
||||
{error, SSLError};
|
||||
false ->
|
||||
{ok, proc_lib:spawn_link(?MODULE, init,
|
||||
[Port, Module, Opts])}
|
||||
SockOpts2 = try erlang:system_info(otp_release) >= "R13B" of
|
||||
true -> [{send_timeout_close, true} | SockOpts];
|
||||
false -> SockOpts
|
||||
catch
|
||||
_:_ -> []
|
||||
end,
|
||||
Res = gen_tcp:listen(Port, [binary,
|
||||
{packet, 0},
|
||||
{active, false},
|
||||
{reuseaddr, true},
|
||||
{nodelay, true},
|
||||
{send_timeout, ?TCP_SEND_TIMEOUT},
|
||||
{keepalive, true} |
|
||||
SockOpts2]),
|
||||
case Res of
|
||||
{ok, ListenSocket} ->
|
||||
ListenSocket;
|
||||
{error, Reason} ->
|
||||
socket_error(Reason, PortIP, Module, SockOpts, Port, IPS)
|
||||
end
|
||||
end.
|
||||
|
||||
init(Port, Module, Opts) ->
|
||||
%% @spec (PortIP, Opts) -> {Port, IPT, IPS, IPV, OptsClean}
|
||||
%% where
|
||||
%% PortIP = Port | {Port, IPT | IPS}
|
||||
%% Port = integer()
|
||||
%% IPT = tuple()
|
||||
%% IPS = string()
|
||||
%% IPV = inet | inet6
|
||||
%% Opts = [IPV | {ip, IPT} | atom() | tuple()]
|
||||
%% OptsClean = [atom() | tuple()]
|
||||
%% @doc Parse any kind of ejabberd listener specification.
|
||||
%% The parsed options are returned in several formats.
|
||||
%% OptsClean does not include inet/inet6 or ip options.
|
||||
%% Opts can include the options inet6 and {ip, Tuple},
|
||||
%% but they are only used when no IP address was specified in the PortIP.
|
||||
%% The IP version (either IPv4 or IPv6) is inferred from the IP address type,
|
||||
%% so the option inet/inet6 is only used when no IP is specified at all.
|
||||
parse_listener_portip(PortIP, Opts) ->
|
||||
{IPOpt, Opts2} = strip_ip_option(Opts),
|
||||
{IPVOpt, OptsClean} = case lists:member(inet6, Opts2) of
|
||||
true -> {inet6, Opts2 -- [inet6]};
|
||||
false -> {inet, Opts2}
|
||||
end,
|
||||
{Port, IPT, IPS, Proto} =
|
||||
case add_proto(PortIP, Opts) of
|
||||
{P, Prot} ->
|
||||
T = get_ip_tuple(IPOpt, IPVOpt),
|
||||
S = inet_parse:ntoa(T),
|
||||
{P, T, S, Prot};
|
||||
{P, T, Prot} when is_integer(P) and is_tuple(T) ->
|
||||
S = inet_parse:ntoa(T),
|
||||
{P, T, S, Prot};
|
||||
{P, S, Prot} when is_integer(P) and is_list(S) ->
|
||||
[S | _] = string:tokens(S, "/"),
|
||||
{ok, T} = inet_parse:address(S),
|
||||
{P, T, S, Prot}
|
||||
end,
|
||||
IPV = case size(IPT) of
|
||||
4 -> inet;
|
||||
8 -> inet6
|
||||
end,
|
||||
{Port, IPT, IPS, IPV, Proto, OptsClean}.
|
||||
|
||||
prepare_opts(IPT, IPV, OptsClean) ->
|
||||
%% The first inet|inet6 and the last {ip, _} work,
|
||||
%% so overriding those in Opts
|
||||
Opts = [IPV | OptsClean] ++ [{ip, IPT}],
|
||||
SockOpts = lists:filter(fun({ip, _}) -> true;
|
||||
(inet6) -> true;
|
||||
(inet) -> true;
|
||||
({backlog, _}) -> true;
|
||||
(_) -> false
|
||||
end, Opts),
|
||||
{Opts, SockOpts}.
|
||||
|
||||
Res = gen_tcp:listen(Port, [binary,
|
||||
{packet, 0},
|
||||
{active, false},
|
||||
{reuseaddr, true},
|
||||
{nodelay, true},
|
||||
{keepalive, true} |
|
||||
SockOpts]),
|
||||
case Res of
|
||||
{ok, ListenSocket} ->
|
||||
accept(ListenSocket, Module, Opts);
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("Failed to open socket for ~p: ~p",
|
||||
[{Port, Module, Opts}, Reason]),
|
||||
error
|
||||
add_proto(Port, Opts) when is_integer(Port) ->
|
||||
{Port, get_proto(Opts)};
|
||||
add_proto({Port, Proto}, _Opts) when is_atom(Proto) ->
|
||||
{Port, normalize_proto(Proto)};
|
||||
add_proto({Port, Addr}, Opts) ->
|
||||
{Port, Addr, get_proto(Opts)};
|
||||
add_proto({Port, Addr, Proto}, _Opts) ->
|
||||
{Port, Addr, normalize_proto(Proto)}.
|
||||
|
||||
strip_ip_option(Opts) ->
|
||||
{IPL, OptsNoIP} = lists:partition(
|
||||
fun({ip, _}) -> true;
|
||||
(_) -> false
|
||||
end,
|
||||
Opts),
|
||||
case IPL of
|
||||
%% Only the first ip option is considered
|
||||
[{ip, T1} | _] when is_tuple(T1) ->
|
||||
{T1, OptsNoIP};
|
||||
[] ->
|
||||
{no_ip_option, OptsNoIP}
|
||||
end.
|
||||
|
||||
get_ip_tuple(no_ip_option, inet) ->
|
||||
{0, 0, 0, 0};
|
||||
get_ip_tuple(no_ip_option, inet6) ->
|
||||
{0, 0, 0, 0, 0, 0, 0, 0};
|
||||
get_ip_tuple(IPOpt, _IPVOpt) ->
|
||||
IPOpt.
|
||||
|
||||
accept(ListenSocket, Module, Opts) ->
|
||||
case gen_tcp:accept(ListenSocket) of
|
||||
{ok, Socket} ->
|
||||
@@ -114,12 +283,11 @@ accept(ListenSocket, Module, Opts) ->
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
case Module of
|
||||
{frontend, Mod} ->
|
||||
ejabberd_frontend_socket:start(Mod, gen_tcp, Socket, Opts);
|
||||
_ ->
|
||||
ejabberd_socket:start(Module, gen_tcp, Socket, Opts)
|
||||
end,
|
||||
CallMod = case is_frontend(Module) of
|
||||
true -> ejabberd_frontend_socket;
|
||||
false -> ejabberd_socket
|
||||
end,
|
||||
CallMod:start(strip_frontend(Module), gen_tcp, Socket, Opts),
|
||||
accept(ListenSocket, Module, Opts);
|
||||
{error, Reason} ->
|
||||
?INFO_MSG("(~w) Failed TCP accept: ~w",
|
||||
@@ -127,58 +295,59 @@ accept(ListenSocket, Module, Opts) ->
|
||||
accept(ListenSocket, Module, Opts)
|
||||
end.
|
||||
|
||||
|
||||
init_ssl(Port, Module, Opts, SSLOpts) ->
|
||||
SockOpts = lists:filter(fun({ip, _}) -> true;
|
||||
(inet6) -> true;
|
||||
(inet) -> true;
|
||||
({verify, _}) -> true;
|
||||
({depth, _}) -> true;
|
||||
({certfile, _}) -> true;
|
||||
({keyfile, _}) -> true;
|
||||
({password, _}) -> true;
|
||||
({cacertfile, _}) -> true;
|
||||
({ciphers, _}) -> true;
|
||||
(_) -> false
|
||||
end, Opts),
|
||||
Res = ssl:listen(Port, [binary,
|
||||
{packet, 0},
|
||||
{active, false},
|
||||
{nodelay, true} |
|
||||
SockOpts ++ SSLOpts]),
|
||||
case Res of
|
||||
{ok, ListenSocket} ->
|
||||
accept_ssl(ListenSocket, Module, Opts);
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("Failed to open socket for ~p: ~p",
|
||||
[{Port, Module, Opts}, Reason]),
|
||||
error
|
||||
end.
|
||||
|
||||
accept_ssl(ListenSocket, Module, Opts) ->
|
||||
case ssl:accept(ListenSocket, 200) of
|
||||
{ok, Socket} ->
|
||||
case {ssl:sockname(Socket), ssl:peername(Socket)} of
|
||||
{{ok, Addr}, {ok, PAddr}} ->
|
||||
?INFO_MSG("(~w) Accepted SSL connection ~w -> ~w",
|
||||
[Socket, PAddr, Addr]);
|
||||
udp_recv(Socket, Module, Opts) ->
|
||||
case gen_udp:recv(Socket, 0) of
|
||||
{ok, {Addr, Port, Packet}} ->
|
||||
case catch Module:udp_recv(Socket, Addr, Port, Packet, Opts) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("failed to process UDP packet:~n"
|
||||
"** Source: {~p, ~p}~n"
|
||||
"** Reason: ~p~n** Packet: ~p",
|
||||
[Addr, Port, Reason, Packet]);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{ok, Pid} = Module:start({ssl, Socket}, Opts),
|
||||
catch ssl:controlling_process(Socket, Pid),
|
||||
Module:become_controller(Pid),
|
||||
accept_ssl(ListenSocket, Module, Opts);
|
||||
{error, timeout} ->
|
||||
accept_ssl(ListenSocket, Module, Opts);
|
||||
udp_recv(Socket, Module, Opts);
|
||||
{error, Reason} ->
|
||||
?INFO_MSG("(~w) Failed SSL handshake: ~w",
|
||||
[ListenSocket, Reason]),
|
||||
accept_ssl(ListenSocket, Module, Opts)
|
||||
?ERROR_MSG("unexpected UDP error: ~s", [format_error(Reason)]),
|
||||
throw({error, Reason})
|
||||
end.
|
||||
|
||||
|
||||
%% @spec (Port, Module, Opts) -> {ok, Pid} | {error, Error}
|
||||
start_listener(Port, Module, Opts) ->
|
||||
case start_listener2(Port, Module, Opts) of
|
||||
{ok, _Pid} = R -> R;
|
||||
{error, {{'EXIT', {undef, [{M, _F, _A}|_]}}, _} = Error} ->
|
||||
?ERROR_MSG("Error starting the ejabberd listener: ~p.~n"
|
||||
"It could not be loaded or is not an ejabberd listener.~n"
|
||||
"Error: ~p~n", [Module, Error]),
|
||||
{error, {module_not_available, M}};
|
||||
{error, {already_started, Pid}} ->
|
||||
{ok, Pid};
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
%% @spec (Port, Module, Opts) -> {ok, Pid} | {error, Error}
|
||||
start_listener2(Port, Module, Opts) ->
|
||||
%% It is only required to start the supervisor in some cases.
|
||||
%% But it doesn't hurt to attempt to start it for any listener.
|
||||
%% So, it's normal (and harmless) that in most cases this call returns: {error, {already_started, pid()}}
|
||||
start_module_sup(Port, Module),
|
||||
start_listener_sup(Port, Module, Opts).
|
||||
|
||||
start_module_sup(_Port, Module) ->
|
||||
Proc1 = gen_mod:get_module_proc("sup", Module),
|
||||
ChildSpec1 =
|
||||
{Proc1,
|
||||
{ejabberd_tmp_sup, start_link, [Proc1, strip_frontend(Module)]},
|
||||
permanent,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec1).
|
||||
|
||||
start_listener_sup(Port, Module, Opts) ->
|
||||
ChildSpec = {Port,
|
||||
{?MODULE, start, [Port, Module, Opts]},
|
||||
transient,
|
||||
@@ -187,30 +356,167 @@ start_listener(Port, Module, Opts) ->
|
||||
[?MODULE]},
|
||||
supervisor:start_child(ejabberd_listeners, ChildSpec).
|
||||
|
||||
stop_listener(Port) ->
|
||||
supervisor:terminate_child(ejabberd_listeners, Port),
|
||||
supervisor:delete_child(ejabberd_listeners, Port).
|
||||
stop_listeners() ->
|
||||
Ports = ejabberd_config:get_local_option(listen),
|
||||
lists:foreach(
|
||||
fun({PortIpNetp, Module, _Opts}) ->
|
||||
delete_listener(PortIpNetp, Module)
|
||||
end,
|
||||
Ports).
|
||||
|
||||
add_listener(Port, Module, Opts) ->
|
||||
%% @spec (PortIP, Module) -> ok
|
||||
%% where
|
||||
%% PortIP = {Port, IPT | IPS}
|
||||
%% Port = integer()
|
||||
%% IPT = tuple()
|
||||
%% IPS = string()
|
||||
%% Module = atom()
|
||||
stop_listener(PortIP, _Module) ->
|
||||
supervisor:terminate_child(ejabberd_listeners, PortIP),
|
||||
supervisor:delete_child(ejabberd_listeners, PortIP).
|
||||
|
||||
%% @spec (PortIP, Module, Opts) -> {ok, Pid} | {error, Error}
|
||||
%% where
|
||||
%% PortIP = {Port, IPT | IPS}
|
||||
%% Port = integer()
|
||||
%% IPT = tuple()
|
||||
%% IPS = string()
|
||||
%% IPV = inet | inet6
|
||||
%% Module = atom()
|
||||
%% Opts = [IPV | {ip, IPT} | atom() | tuple()]
|
||||
%% @doc Add a listener and store in config if success
|
||||
add_listener(PortIP, Module, Opts) ->
|
||||
{Port, IPT, _, _, Proto, _} = parse_listener_portip(PortIP, Opts),
|
||||
PortIP1 = {Port, IPT, Proto},
|
||||
case start_listener(PortIP1, Module, Opts) of
|
||||
{ok, _Pid} ->
|
||||
Ports = case ejabberd_config:get_local_option(listen) of
|
||||
undefined ->
|
||||
[];
|
||||
Ls ->
|
||||
Ls
|
||||
end,
|
||||
Ports1 = lists:keydelete(PortIP1, 1, Ports),
|
||||
Ports2 = [{PortIP1, Module, Opts} | Ports1],
|
||||
ejabberd_config:add_local_option(listen, Ports2),
|
||||
ok;
|
||||
{error, {already_started, _Pid}} ->
|
||||
{error, {already_started, PortIP}};
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
delete_listener(PortIP, Module) ->
|
||||
delete_listener(PortIP, Module, []).
|
||||
|
||||
%% @spec (PortIP, Module, Opts) -> ok
|
||||
%% where
|
||||
%% PortIP = {Port, IPT | IPS}
|
||||
%% Port = integer()
|
||||
%% IPT = tuple()
|
||||
%% IPS = string()
|
||||
%% Module = atom()
|
||||
%% Opts = [term()]
|
||||
delete_listener(PortIP, Module, Opts) ->
|
||||
{Port, IPT, _, _, Proto, _} = parse_listener_portip(PortIP, Opts),
|
||||
PortIP1 = {Port, IPT, Proto},
|
||||
Ports = case ejabberd_config:get_local_option(listen) of
|
||||
undefined ->
|
||||
[];
|
||||
Ls ->
|
||||
Ls
|
||||
end,
|
||||
Ports1 = lists:keydelete(Port, 1, Ports),
|
||||
Ports2 = [{Port, Module, Opts} | Ports1],
|
||||
ejabberd_config:add_local_option(listen, Ports2),
|
||||
start_listener(Port, Module, Opts).
|
||||
|
||||
delete_listener(Port) ->
|
||||
Ports = case ejabberd_config:get_local_option(listen) of
|
||||
undefined ->
|
||||
[];
|
||||
Ls ->
|
||||
Ls
|
||||
end,
|
||||
Ports1 = lists:keydelete(Port, 1, Ports),
|
||||
Ports1 = lists:keydelete(PortIP1, 1, Ports),
|
||||
ejabberd_config:add_local_option(listen, Ports1),
|
||||
stop_listener(Port).
|
||||
stop_listener(PortIP1, Module).
|
||||
|
||||
is_frontend({frontend, _Module}) -> true;
|
||||
is_frontend(_) -> false.
|
||||
|
||||
%% @doc(FrontMod) -> atom()
|
||||
%% where FrontMod = atom() | {frontend, atom()}
|
||||
strip_frontend({frontend, Module}) -> Module;
|
||||
strip_frontend(Module) when is_atom(Module) -> Module.
|
||||
|
||||
|
||||
%%%
|
||||
%%% Check options
|
||||
%%%
|
||||
|
||||
check_listener_options(Opts) ->
|
||||
case includes_deprecated_ssl_option(Opts) of
|
||||
false -> ok;
|
||||
true ->
|
||||
Error = "There is a problem with your ejabberd configuration file: "
|
||||
"the option 'ssl' for listening sockets is no longer available."
|
||||
" To get SSL encryption use the option 'tls'.",
|
||||
throw({error, Error})
|
||||
end,
|
||||
case certfile_readable(Opts) of
|
||||
true -> ok;
|
||||
{false, Path} ->
|
||||
ErrorText = "There is a problem in the configuration: "
|
||||
"the specified file is not readable: ",
|
||||
throw({error, ErrorText ++ Path})
|
||||
end,
|
||||
ok.
|
||||
|
||||
%% Parse the options of the socket,
|
||||
%% and return if the deprecated option 'ssl' is included
|
||||
%% @spec (Opts) -> true | false
|
||||
includes_deprecated_ssl_option(Opts) ->
|
||||
case lists:keysearch(ssl, 1, Opts) of
|
||||
{value, {ssl, _SSLOpts}} ->
|
||||
true;
|
||||
_ ->
|
||||
lists:member(ssl, Opts)
|
||||
end.
|
||||
|
||||
%% @spec (Opts) -> true | {false, Path::string()}
|
||||
certfile_readable(Opts) ->
|
||||
case proplists:lookup(certfile, Opts) of
|
||||
none -> true;
|
||||
{certfile, Path} ->
|
||||
case ejabberd_config:is_file_readable(Path) of
|
||||
true -> true;
|
||||
false -> {false, Path}
|
||||
end
|
||||
end.
|
||||
|
||||
get_proto(Opts) ->
|
||||
case proplists:get_value(proto, Opts) of
|
||||
undefined ->
|
||||
tcp;
|
||||
Proto ->
|
||||
normalize_proto(Proto)
|
||||
end.
|
||||
|
||||
normalize_proto(tcp) -> tcp;
|
||||
normalize_proto(udp) -> udp;
|
||||
normalize_proto(UnknownProto) ->
|
||||
?WARNING_MSG("There is a problem in the configuration: "
|
||||
"~p is an unknown IP protocol. Using tcp as fallback",
|
||||
[UnknownProto]),
|
||||
tcp.
|
||||
|
||||
socket_error(Reason, PortIP, Module, SockOpts, Port, IPS) ->
|
||||
ReasonT = case Reason of
|
||||
eaddrnotavail ->
|
||||
"IP address not available: " ++ IPS;
|
||||
eaddrinuse ->
|
||||
"IP address and port number already used: "
|
||||
++IPS++" "++integer_to_list(Port);
|
||||
_ ->
|
||||
format_error(Reason)
|
||||
end,
|
||||
?ERROR_MSG("Failed to open socket:~n ~p~nReason: ~s",
|
||||
[{Port, Module, SockOpts}, ReasonT]),
|
||||
throw({Reason, PortIP}).
|
||||
|
||||
format_error(Reason) ->
|
||||
case inet:format_error(Reason) of
|
||||
"unknown POSIX error" ->
|
||||
atom_to_list(Reason);
|
||||
ReasonStr ->
|
||||
ReasonStr
|
||||
end.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 30 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -33,10 +33,15 @@
|
||||
-export([start_link/0]).
|
||||
|
||||
-export([route/3,
|
||||
route_iq/4,
|
||||
route_iq/5,
|
||||
process_iq_reply/3,
|
||||
register_iq_handler/4,
|
||||
register_iq_handler/5,
|
||||
register_iq_response_handler/4,
|
||||
register_iq_response_handler/5,
|
||||
unregister_iq_handler/2,
|
||||
unregister_iq_response_handler/2,
|
||||
refresh_iq_handlers/0,
|
||||
bounce_resource_packet/3
|
||||
]).
|
||||
@@ -50,10 +55,13 @@
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
-record(iq_response, {id, module, function}).
|
||||
-record(iq_response, {id, module, function, timer}).
|
||||
|
||||
-define(IQTABLE, local_iqtable).
|
||||
|
||||
%% This value is used in SIP and Megaco for a transaction lifetime.
|
||||
-define(IQ_TIMEOUT, 32000).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@@ -88,36 +96,24 @@ process_iq(From, To, Packet) ->
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
reply ->
|
||||
process_iq_reply(From, To, Packet);
|
||||
IQReply = jlib:iq_query_or_response_info(Packet),
|
||||
process_iq_reply(From, To, IQReply);
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
ok
|
||||
end.
|
||||
|
||||
process_iq_reply(From, To, Packet) ->
|
||||
IQ = jlib:iq_query_or_response_info(Packet),
|
||||
#iq{id = ID} = IQ,
|
||||
case catch mnesia:dirty_read(iq_response, ID) of
|
||||
[] ->
|
||||
process_iq_reply(From, To, #iq{id = ID} = IQ) ->
|
||||
case get_iq_callback(ID) of
|
||||
{ok, undefined, Function} ->
|
||||
Function(IQ),
|
||||
ok;
|
||||
{ok, Module, Function} ->
|
||||
Module:Function(From, To, IQ),
|
||||
ok;
|
||||
_ ->
|
||||
F = fun() ->
|
||||
case mnesia:read({iq_response, ID}) of
|
||||
[] ->
|
||||
nothing;
|
||||
[#iq_response{module = Module,
|
||||
function = Function}] ->
|
||||
mnesia:delete({iq_response, ID}),
|
||||
{Module, Function}
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, {Module, Function}} ->
|
||||
Module:Function(From, To, IQ);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
nothing
|
||||
end.
|
||||
|
||||
route(From, To, Packet) ->
|
||||
@@ -129,8 +125,35 @@ route(From, To, Packet) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
register_iq_response_handler(Host, ID, Module, Fun) ->
|
||||
ejabberd_local ! {register_iq_response_handler, Host, ID, Module, Fun}.
|
||||
route_iq(From, To, IQ, F) ->
|
||||
route_iq(From, To, IQ, F, undefined).
|
||||
|
||||
route_iq(From, To, #iq{type = Type} = IQ, F, Timeout) when is_function(F) ->
|
||||
Packet = if Type == set; Type == get ->
|
||||
ID = randoms:get_string(),
|
||||
Host = From#jid.lserver,
|
||||
register_iq_response_handler(Host, ID, undefined, F, Timeout),
|
||||
jlib:iq_to_xml(IQ#iq{id = ID});
|
||||
true ->
|
||||
jlib:iq_to_xml(IQ)
|
||||
end,
|
||||
ejabberd_router:route(From, To, Packet).
|
||||
|
||||
register_iq_response_handler(Host, ID, Module, Function) ->
|
||||
register_iq_response_handler(Host, ID, Module, Function, undefined).
|
||||
|
||||
register_iq_response_handler(_Host, ID, Module, Function, Timeout0) ->
|
||||
Timeout = case Timeout0 of
|
||||
undefined ->
|
||||
?IQ_TIMEOUT;
|
||||
N when is_integer(N), N > 0 ->
|
||||
N
|
||||
end,
|
||||
TRef = erlang:start_timer(Timeout, ejabberd_local, ID),
|
||||
mnesia:dirty_write(#iq_response{id = ID,
|
||||
module = Module,
|
||||
function = Function,
|
||||
timer = TRef}).
|
||||
|
||||
register_iq_handler(Host, XMLNS, Module, Fun) ->
|
||||
ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun}.
|
||||
@@ -138,6 +161,10 @@ register_iq_handler(Host, XMLNS, Module, Fun) ->
|
||||
register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
|
||||
ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
|
||||
|
||||
unregister_iq_response_handler(_Host, ID) ->
|
||||
catch get_iq_callback(ID),
|
||||
ok.
|
||||
|
||||
unregister_iq_handler(Host, XMLNS) ->
|
||||
ejabberd_local ! {unregister_iq_handler, Host, XMLNS}.
|
||||
|
||||
@@ -168,9 +195,11 @@ init([]) ->
|
||||
?MODULE, bounce_resource_packet, 100)
|
||||
end, ?MYHOSTS),
|
||||
catch ets:new(?IQTABLE, [named_table, public]),
|
||||
update_table(),
|
||||
mnesia:create_table(iq_response,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, iq_response)}]),
|
||||
mnesia:add_table_copy(iq_response, node(), ram_copies),
|
||||
{ok, #state{}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -210,9 +239,6 @@ handle_info({route, From, To, Packet}, State) ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info({register_iq_response_handler, _Host, ID, Module, Function}, State) ->
|
||||
mnesia:dirty_write(#iq_response{id = ID, module = Module, function = Function}),
|
||||
{noreply, State};
|
||||
handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
|
||||
ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}),
|
||||
catch mod_disco:register_feature(Host, XMLNS),
|
||||
@@ -244,6 +270,9 @@ handle_info(refresh_iq_handlers, State) ->
|
||||
end
|
||||
end, ets:tab2list(?IQTABLE)),
|
||||
{noreply, State};
|
||||
handle_info({timeout, _TRef, ID}, State) ->
|
||||
process_iq_timeout(ID),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
@@ -297,3 +326,52 @@ do_route(From, To, Packet) ->
|
||||
end
|
||||
end.
|
||||
|
||||
update_table() ->
|
||||
case catch mnesia:table_info(iq_response, attributes) of
|
||||
[id, module, function] ->
|
||||
mnesia:delete_table(iq_response);
|
||||
[id, module, function, timer] ->
|
||||
ok;
|
||||
{'EXIT', _} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
get_iq_callback(ID) ->
|
||||
case mnesia:dirty_read(iq_response, ID) of
|
||||
[#iq_response{module = Module, timer = TRef,
|
||||
function = Function}] ->
|
||||
cancel_timer(TRef),
|
||||
mnesia:dirty_delete(iq_response, ID),
|
||||
{ok, Module, Function};
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
process_iq_timeout(ID) ->
|
||||
spawn(fun process_iq_timeout/0) ! ID.
|
||||
|
||||
process_iq_timeout() ->
|
||||
receive
|
||||
ID ->
|
||||
case get_iq_callback(ID) of
|
||||
{ok, undefined, Function} ->
|
||||
Function(timeout);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
after 5000 ->
|
||||
ok
|
||||
end.
|
||||
|
||||
cancel_timer(TRef) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
receive
|
||||
{timeout, TRef, _} ->
|
||||
ok
|
||||
after 0 ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 23 Oct 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
%% gen_event callbacks
|
||||
-export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2,
|
||||
code_change/3, reopen_log/0]).
|
||||
code_change/3, reopen_log/0, rotate_log/1]).
|
||||
|
||||
-record(state, {fd, file}).
|
||||
|
||||
@@ -117,7 +117,7 @@ reopen_log() ->
|
||||
write_event(Fd, {Time, {error, _GL, {Pid, Format, Args}}}) ->
|
||||
T = write_time(Time),
|
||||
case catch io_lib:format(add_node(Format,Pid), Args) of
|
||||
S when list(S) ->
|
||||
S when is_list(S) ->
|
||||
file:write(Fd, io_lib:format(T ++ S, []));
|
||||
_ ->
|
||||
F = add_node("ERROR: ~p - ~p~n", Pid),
|
||||
@@ -126,7 +126,7 @@ write_event(Fd, {Time, {error, _GL, {Pid, Format, Args}}}) ->
|
||||
write_event(Fd, {Time, {emulator, _GL, Chars}}) ->
|
||||
T = write_time(Time),
|
||||
case catch io_lib:format(Chars, []) of
|
||||
S when list(S) ->
|
||||
S when is_list(S) ->
|
||||
file:write(Fd, io_lib:format(T ++ S, []));
|
||||
_ ->
|
||||
file:write(Fd, io_lib:format(T ++ "ERROR: ~p ~n", [Chars]))
|
||||
@@ -145,7 +145,20 @@ write_event(Fd, {Time, {info_report, _GL, {Pid, std_info, Rep}}}) ->
|
||||
write_event(Fd, {Time, {info_msg, _GL, {Pid, Format, Args}}}) ->
|
||||
T = write_time(Time, "INFO REPORT"),
|
||||
case catch io_lib:format(add_node(Format,Pid), Args) of
|
||||
S when list(S) ->
|
||||
S when is_list(S) ->
|
||||
file:write(Fd, io_lib:format(T ++ S, []));
|
||||
_ ->
|
||||
F = add_node("ERROR: ~p - ~p~n", Pid),
|
||||
file:write(Fd, io_lib:format(T ++ F, [Format,Args]))
|
||||
end;
|
||||
write_event(Fd, {Time, {warning_report, _GL, {Pid, std_warning, Rep}}}) ->
|
||||
T = write_time(Time, "WARNING REPORT"),
|
||||
S = format_report(Rep),
|
||||
file:write(Fd, io_lib:format(T ++ S ++ add_node("", Pid), []));
|
||||
write_event(Fd, {Time, {warning_msg, _GL, {Pid, Format, Args}}}) ->
|
||||
T = write_time(Time, "WARNING REPORT"),
|
||||
case catch io_lib:format(add_node(Format,Pid), Args) of
|
||||
S when is_list(S) ->
|
||||
file:write(Fd, io_lib:format(T ++ S, []));
|
||||
_ ->
|
||||
F = add_node("ERROR: ~p - ~p~n", Pid),
|
||||
@@ -154,7 +167,7 @@ write_event(Fd, {Time, {info_msg, _GL, {Pid, Format, Args}}}) ->
|
||||
write_event(_, _) ->
|
||||
ok.
|
||||
|
||||
format_report(Rep) when list(Rep) ->
|
||||
format_report(Rep) when is_list(Rep) ->
|
||||
case string_p(Rep) of
|
||||
true ->
|
||||
io_lib:format("~s~n",[Rep]);
|
||||
@@ -171,7 +184,7 @@ format_rep([Other|Rep]) ->
|
||||
format_rep(_) ->
|
||||
[].
|
||||
|
||||
add_node(X, Pid) when atom(X) ->
|
||||
add_node(X, Pid) when is_atom(X) ->
|
||||
add_node(atom_to_list(X), Pid);
|
||||
add_node(X, Pid) when node(Pid) /= node() ->
|
||||
lists:concat([X,"** at node ",node(Pid)," **~n"]);
|
||||
@@ -183,7 +196,7 @@ string_p([]) ->
|
||||
string_p(Term) ->
|
||||
string_p1(Term).
|
||||
|
||||
string_p1([H|T]) when integer(H), H >= $\s, H < 255 ->
|
||||
string_p1([H|T]) when is_integer(H), H >= $\s, H < 255 ->
|
||||
string_p1(T);
|
||||
string_p1([$\n|T]) -> string_p1(T);
|
||||
string_p1([$\r|T]) -> string_p1(T);
|
||||
@@ -192,7 +205,7 @@ string_p1([$\v|T]) -> string_p1(T);
|
||||
string_p1([$\b|T]) -> string_p1(T);
|
||||
string_p1([$\f|T]) -> string_p1(T);
|
||||
string_p1([$\e|T]) -> string_p1(T);
|
||||
string_p1([H|T]) when list(H) ->
|
||||
string_p1([H|T]) when is_list(H) ->
|
||||
case string_p1(H) of
|
||||
true -> string_p1(T);
|
||||
_ -> false
|
||||
@@ -206,10 +219,11 @@ write_time({{Y,Mo,D},{H,Mi,S}}, Type) ->
|
||||
io_lib:format("~n=~s==== ~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w ===~n",
|
||||
[Type, Y, Mo, D, H, Mi, S]).
|
||||
|
||||
%% Rename the log file if it the filename exists
|
||||
%% @doc Rename the log file if exists, to "*-old.log".
|
||||
%% This is needed in systems when the file must be closed before rotation (Windows).
|
||||
%% On most Unix-like system, the file can be renamed from the command line and
|
||||
%%the log can directly be reopened.
|
||||
%% the log can directly be reopened.
|
||||
%% @spec (Filename::string()) -> ok
|
||||
rotate_log(Filename) ->
|
||||
case file:read_file_info(Filename) of
|
||||
{ok, _FileInfo} ->
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
%%% Created : 29 Nov 2006 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -20,7 +20,7 @@
|
||||
%%% 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
|
||||
@@ -31,64 +31,119 @@
|
||||
-module(ejabberd_loglevel).
|
||||
-author('mickael.remond@process-one.net').
|
||||
|
||||
-export([set/1]).
|
||||
-export([set/1,
|
||||
get/0,
|
||||
set_custom/2,
|
||||
clear_custom/0,
|
||||
clear_custom/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-define(LOGMODULE, "error_logger").
|
||||
|
||||
%% Error levels:
|
||||
%% 0 -> No log
|
||||
%% 1 -> Critical
|
||||
%% 2 -> Error
|
||||
%% 3 -> Warning
|
||||
%% 4 -> Info
|
||||
%% 5 -> Debug
|
||||
set(Loglevel) when is_integer(Loglevel) ->
|
||||
Forms = compile_string(?LOGMODULE, ejabberd_logger_src(Loglevel)),
|
||||
load_logger(Forms, ?LOGMODULE, Loglevel);
|
||||
set(_) ->
|
||||
exit("Loglevel must be an integer").
|
||||
|
||||
%% --------------------------------------------------------------
|
||||
%% Compile a string into a module and returns the binary
|
||||
compile_string(Mod, Str) ->
|
||||
Fname = Mod ++ ".erl",
|
||||
{ok, Fd} = open_ram_file(Fname),
|
||||
file:write(Fd, Str),
|
||||
file:position(Fd, 0),
|
||||
case epp_dodger:parse(Fd) of
|
||||
{ok, Tree} ->
|
||||
Forms = revert_tree(Tree),
|
||||
close_ram_file(Fd),
|
||||
Forms;
|
||||
Error ->
|
||||
close_ram_file(Fd),
|
||||
Error
|
||||
-record(loglevel, {ordinal,
|
||||
name,
|
||||
description,
|
||||
function = no_log,
|
||||
event_type = no_log,
|
||||
msg_prefix = no_log}).
|
||||
|
||||
-define(LOG_LEVELS,
|
||||
[#loglevel{ordinal = 0, name = no_log, description = "No log"},
|
||||
#loglevel{ordinal = 1, name = critical, description = "Critical",
|
||||
function = critical_msg, event_type = error, msg_prefix = "C"},
|
||||
#loglevel{ordinal = 2, name = error, description = "Error",
|
||||
function = error_msg, event_type = error, msg_prefix = "E"},
|
||||
#loglevel{ordinal = 3, name = warning, description = "Warning",
|
||||
function = warning_msg, event_type = warning_msg, msg_prefix = "W"},
|
||||
#loglevel{ordinal = 4, name = info, description = "Info",
|
||||
function = info_msg, event_type = info_msg, msg_prefix = "I"},
|
||||
#loglevel{ordinal = 5, name = debug, description = "Debug",
|
||||
function = debug_msg, event_type = info_msg, msg_prefix = "D"}]).
|
||||
|
||||
%% @type level() = integer() | atom().
|
||||
|
||||
%% @spec () -> {DefaultLevelOrdinal::integer(), [{Module::atom(), LevelOrdinal::integer()}]}
|
||||
%% @doc Get the default and all custom levels
|
||||
get() ->
|
||||
{DefaultLevel, _CustomLevels} = ejabberd_logger:get(),
|
||||
case lists:keysearch(DefaultLevel, #loglevel.ordinal, ?LOG_LEVELS) of
|
||||
{value, Result = #loglevel{}} ->
|
||||
{Result#loglevel.ordinal, Result#loglevel.name, Result#loglevel.description};
|
||||
_ ->
|
||||
erlang:error({no_such_loglevel, DefaultLevel})
|
||||
end.
|
||||
|
||||
open_ram_file(Fname) ->
|
||||
ram_file_io_server:start(self(), Fname, [read,write]).
|
||||
|
||||
close_ram_file(Fd) ->
|
||||
file:close(Fd).
|
||||
%% @spec (DefaultLevel::level() | {DefaultLevel::level(), [{Module::atom(), Level::level()}]}) ->
|
||||
%% {module, ejabberd_logger}
|
||||
%% @doc Set the default and all custom levels
|
||||
set(DefaultLevel) when is_atom(DefaultLevel) orelse is_integer(DefaultLevel) ->
|
||||
set({DefaultLevel, []});
|
||||
set({DefaultLevel, CustomLevels}) when is_list(CustomLevels) ->
|
||||
DefaultInt = level_to_integer(DefaultLevel),
|
||||
CustomInts = [level_to_integer(C) || C <- CustomLevels],
|
||||
Loglevel = {DefaultInt, CustomInts},
|
||||
try
|
||||
{Mod,Code} = dynamic_compile:from_string(ejabberd_logger_src(Loglevel)),
|
||||
code:load_binary(Mod, ?LOGMODULE ++ ".erl", Code)
|
||||
catch
|
||||
Type:Error -> ?CRITICAL_MSG("Error compiling logger (~p): ~p~n", [Type, Error])
|
||||
end;
|
||||
set(_) ->
|
||||
exit("Invalid loglevel format").
|
||||
|
||||
revert_tree(Tree) ->
|
||||
[erl_syntax:revert(T) || T <- Tree].
|
||||
%% @spec (Module::atom(), CustomLevel::level()) -> ok
|
||||
%% @doc Set a custom level
|
||||
set_custom(Module, Level) ->
|
||||
{DefaultLevel, CustomLevels} = ejabberd_logger:get(),
|
||||
case lists:keysearch(Module, 1, CustomLevels) of
|
||||
{value, {Module, Level}} ->
|
||||
ok;
|
||||
{value, _} ->
|
||||
set({DefaultLevel, lists:keyreplace(Module, 1, CustomLevels, {Module, Level})});
|
||||
_ ->
|
||||
set({DefaultLevel, [{Module, Level} | CustomLevels]})
|
||||
end.
|
||||
|
||||
load_logger(Forms, Mod, Loglevel) ->
|
||||
Fname = Mod ++ ".erl",
|
||||
case compile:forms(Forms, [binary, {d,'LOGLEVEL',Loglevel}]) of
|
||||
{ok, M, Bin} ->
|
||||
code:load_binary(M, Fname, Bin);
|
||||
Error ->
|
||||
io:format("Error ~p~n", [Error])
|
||||
%% @spec () -> ok
|
||||
%% @doc Clear all custom levels
|
||||
clear_custom() ->
|
||||
{DefaultLevel, _CustomLevels} = ejabberd_logger:get(),
|
||||
set({DefaultLevel, []}).
|
||||
|
||||
%% @spec (Module::atom()) -> ok
|
||||
%% @doc Clear a custom level
|
||||
clear_custom(Module) ->
|
||||
{DefaultLevel, CustomLevels} = ejabberd_logger:get(),
|
||||
case lists:keysearch(Module, 1, CustomLevels) of
|
||||
{value, _} ->
|
||||
set({DefaultLevel, lists:keydelete(Module, 1, CustomLevels)});
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
level_to_integer(Level) when is_integer(Level) ->
|
||||
Level;
|
||||
level_to_integer({Module, Level}) ->
|
||||
{Module, level_to_integer(Level)};
|
||||
level_to_integer(Level) ->
|
||||
case lists:keysearch(Level, #loglevel.name, ?LOG_LEVELS) of
|
||||
{value, #loglevel{ordinal = Int}} -> Int;
|
||||
_ -> erlang:error({no_such_loglevel, Level})
|
||||
end.
|
||||
|
||||
%% --------------------------------------------------------------
|
||||
%% Code of the ejabberd logger, dynamically compiled and loaded
|
||||
%% This allows to dynamically change log level while keeping a
|
||||
%% very efficient code.
|
||||
%% very efficient code.
|
||||
ejabberd_logger_src(Loglevel) ->
|
||||
L = integer_to_list(Loglevel),
|
||||
lists:flatten([header_src(),
|
||||
get_src(Loglevel),
|
||||
[log_src(Loglevel, LevelSpec) || LevelSpec <- ?LOG_LEVELS],
|
||||
notify_src()]).
|
||||
|
||||
header_src() ->
|
||||
"-module(ejabberd_logger).
|
||||
-author('mickael.remond@process-one.net').
|
||||
|
||||
@@ -96,41 +151,44 @@ ejabberd_logger_src(Loglevel) ->
|
||||
info_msg/4,
|
||||
warning_msg/4,
|
||||
error_msg/4,
|
||||
critical_msg/4]).
|
||||
critical_msg/4,
|
||||
get/0]).
|
||||
".
|
||||
|
||||
%% Helper functions
|
||||
debug_msg(Module, Line, Format, Args) when " ++ L ++ " >= 5 ->
|
||||
notify(info_msg,
|
||||
\"D(~p:~p:~p) : \"++Format++\"~n\",
|
||||
[self(), Module, Line]++Args);
|
||||
debug_msg(_,_,_,_) -> ok.
|
||||
get_src(Loglevel) ->
|
||||
io_lib:format("get() -> ~w.
|
||||
", [Loglevel]).
|
||||
|
||||
info_msg(Module, Line, Format, Args) when " ++ L ++ " >= 4 ->
|
||||
notify(info_msg,
|
||||
\"I(~p:~p:~p) : \"++Format++\"~n\",
|
||||
[self(), Module, Line]++Args);
|
||||
info_msg(_,_,_,_) -> ok.
|
||||
log_src(_Loglevel, #loglevel{function = no_log}) ->
|
||||
[];
|
||||
log_src({DefaultLevel, [{Module, Level} | Tail]}, Spec = #loglevel{ordinal = MinLevel})
|
||||
when Level < MinLevel andalso MinLevel =< DefaultLevel ->
|
||||
[atom_to_list(Spec#loglevel.function), "(", atom_to_list(Module), ", _, _, _) -> ok;
|
||||
", log_src({DefaultLevel, Tail}, Spec)];
|
||||
log_src({DefaultLevel, [{Module, Level} | Tail]}, Spec = #loglevel{ordinal = MinLevel})
|
||||
when DefaultLevel < MinLevel andalso MinLevel =< Level ->
|
||||
[atom_to_list(Spec#loglevel.function), "(", atom_to_list(Module), " = Module, Line, Format, Args) ->",
|
||||
log_notify_src(Spec), ";
|
||||
", log_src({DefaultLevel, Tail}, Spec)];
|
||||
log_src({DefaultLevel, [_Head | Tail]}, Spec = #loglevel{}) ->
|
||||
log_src({DefaultLevel, Tail}, Spec);
|
||||
log_src({DefaultLevel, []}, Spec = #loglevel{ordinal = MinLevel})
|
||||
when DefaultLevel < MinLevel ->
|
||||
[atom_to_list(Spec#loglevel.function), "(_, _, _, _) -> ok.
|
||||
"];
|
||||
log_src({_DefaultLevel, []}, Spec = #loglevel{}) ->
|
||||
[atom_to_list(Spec#loglevel.function), "(Module, Line, Format, Args) ->",
|
||||
log_notify_src(Spec), ".
|
||||
"].
|
||||
|
||||
warning_msg(Module, Line, Format, Args) when " ++ L ++ " >= 3 ->
|
||||
notify(error,
|
||||
\"W(~p:~p:~p) : \"++Format++\"~n\",
|
||||
[self(), Module, Line]++Args);
|
||||
warning_msg(_,_,_,_) -> ok.
|
||||
|
||||
error_msg(Module, Line, Format, Args) when " ++ L ++ " >= 2 ->
|
||||
notify(error,
|
||||
\"E(~p:~p:~p) : \"++Format++\"~n\",
|
||||
[self(), Module, Line]++Args);
|
||||
error_msg(_,_,_,_) -> ok.
|
||||
|
||||
critical_msg(Module, Line, Format, Args) when " ++ L ++ " >= 1 ->
|
||||
notify(error,
|
||||
\"C(~p:~p:~p) : \"++Format++\"~n\",
|
||||
[self(), Module, Line]++Args);
|
||||
critical_msg(_,_,_,_) -> ok.
|
||||
log_notify_src(Spec = #loglevel{}) ->
|
||||
["notify(", atom_to_list(Spec#loglevel.event_type), ",
|
||||
\"", Spec#loglevel.msg_prefix, "(~p:~p:~p) : \"++Format++\"~n\",
|
||||
[self(), Module, Line | Args])"].
|
||||
|
||||
notify_src() ->
|
||||
%% Distribute the message to the Erlang error logger
|
||||
notify(Type, Format, Args) ->
|
||||
"notify(Type, Format, Args) ->
|
||||
LoggerMsg = {Type, group_leader(), {self(), Format, Args}},
|
||||
gen_event:notify(error_logger, LoggerMsg).
|
||||
".
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 1 Nov 2006 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -40,6 +40,12 @@
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-ifdef(SSL40).
|
||||
-define(PG2, pg2).
|
||||
-else.
|
||||
-define(PG2, pg2_backport).
|
||||
-endif.
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
%%====================================================================
|
||||
@@ -54,20 +60,20 @@ start_link() ->
|
||||
|
||||
join(Name) ->
|
||||
PG = {?MODULE, Name},
|
||||
pg2:create(PG),
|
||||
pg2:join(PG, whereis(?MODULE)).
|
||||
?PG2:create(PG),
|
||||
?PG2:join(PG, whereis(?MODULE)).
|
||||
|
||||
leave(Name) ->
|
||||
PG = {?MODULE, Name},
|
||||
pg2:leave(PG, whereis(?MODULE)).
|
||||
?PG2:leave(PG, whereis(?MODULE)).
|
||||
|
||||
get_members(Name) ->
|
||||
PG = {?MODULE, Name},
|
||||
[node(P) || P <- pg2:get_members(PG)].
|
||||
[node(P) || P <- ?PG2:get_members(PG)].
|
||||
|
||||
get_closest_node(Name) ->
|
||||
PG = {?MODULE, Name},
|
||||
node(pg2:get_closest_pid(PG)).
|
||||
node(?PG2:get_closest_pid(PG)).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
|
||||
@@ -0,0 +1,705 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_piefxis.erl
|
||||
%%% Author : Pablo Polvorin, Vidal Santiago Martinez
|
||||
%%% Purpose : XEP-0227: Portable Import/Export Format for XMPP-IM Servers
|
||||
%%% Created : 17 Jul 2008 by Pablo Polvorin <pablo.polvorin@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2011 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
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%% Not implemented:
|
||||
%%% - write mod_piefxis with ejabberdctl commands
|
||||
%%% - Export from mod_offline_odbc.erl
|
||||
%%% - Export from mod_private_odbc.erl
|
||||
%%% - XEP-227: 6. Security Considerations
|
||||
%%% - Other schemas of XInclude are not tested, and may not be imported correctly.
|
||||
%%% - If a host has many users, split that host in XML files with 50 users each.
|
||||
|
||||
%%%% Headers
|
||||
|
||||
-module(ejabberd_piefxis).
|
||||
|
||||
-export([import_file/1, export_server/1, export_host/2]).
|
||||
|
||||
-record(parsing_state, {parser, host, dir}).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
%%-include_lib("exmpp/include/exmpp.hrl").
|
||||
%%-include_lib("exmpp/include/exmpp_client.hrl").
|
||||
%% Copied from exmpp header files:
|
||||
-define(NS_ROSTER, "jabber:iq:roster").
|
||||
-define(NS_VCARD, "vcard-temp").
|
||||
-record(xmlcdata, {
|
||||
cdata = <<>>
|
||||
}).
|
||||
-record(xmlattr, {
|
||||
ns = undefined,
|
||||
name,
|
||||
value
|
||||
}).
|
||||
-record(xmlel, {
|
||||
ns = undefined,
|
||||
declared_ns = [],
|
||||
name,
|
||||
attrs = [],
|
||||
children = []
|
||||
}).
|
||||
-record(iq, {
|
||||
kind,
|
||||
type,
|
||||
id,
|
||||
ns,
|
||||
payload,
|
||||
error,
|
||||
lang,
|
||||
iq_ns
|
||||
}).
|
||||
-record(xmlendtag, {
|
||||
ns = undefined,
|
||||
name
|
||||
}).
|
||||
|
||||
|
||||
%% Copied from mod_private.erl
|
||||
-record(private_storage, {usns, xml}).
|
||||
|
||||
%%-define(ERROR_MSG(M,Args),io:format(M,Args)).
|
||||
%%-define(INFO_MSG(M,Args),ok).
|
||||
|
||||
-define(CHUNK_SIZE,1024*20). %20k
|
||||
|
||||
-define(BTL, binary_to_list).
|
||||
-define(LTB, list_to_binary).
|
||||
|
||||
-define(NS_XINCLUDE, 'http://www.w3.org/2001/XInclude').
|
||||
|
||||
%%%==================================
|
||||
|
||||
%%%% Import file
|
||||
|
||||
import_file(FileName) ->
|
||||
_ = #xmlattr{}, %% this stupid line is only to prevent compilation warning about "recod xmlattr is unused"
|
||||
import_file(FileName, 2).
|
||||
|
||||
import_file(FileName, RootDepth) ->
|
||||
try_start_exmpp(),
|
||||
Dir = filename:dirname(FileName),
|
||||
{ok, IO} = try_open_file(FileName),
|
||||
Parser = exmpp_xml:start_parser([{max_size,infinity},
|
||||
{root_depth, RootDepth},
|
||||
{emit_endtag,true}]),
|
||||
read_chunks(IO, #parsing_state{parser=Parser, dir=Dir}),
|
||||
file:close(IO),
|
||||
exmpp_xml:stop_parser(Parser).
|
||||
|
||||
try_start_exmpp() ->
|
||||
try exmpp:start()
|
||||
catch
|
||||
error:{already_started, exmpp} -> ok;
|
||||
error:undef -> throw({error, exmpp_not_installed})
|
||||
end.
|
||||
|
||||
try_open_file(FileName) ->
|
||||
case file:open(FileName,[read,binary]) of
|
||||
{ok, IO} -> {ok, IO};
|
||||
{error, enoent} -> throw({error, {file_not_found, FileName}})
|
||||
end.
|
||||
|
||||
%%File could be large.. we read it in chunks
|
||||
read_chunks(IO,State) ->
|
||||
case file:read(IO,?CHUNK_SIZE) of
|
||||
{ok,Chunk} ->
|
||||
NewState = process_chunk(Chunk,State),
|
||||
read_chunks(IO,NewState);
|
||||
eof ->
|
||||
ok
|
||||
end.
|
||||
|
||||
process_chunk(Chunk,S =#parsing_state{parser=Parser}) ->
|
||||
case exmpp_xml:parse(Parser,Chunk) of
|
||||
continue ->
|
||||
S;
|
||||
XMLElements ->
|
||||
process_elements(XMLElements,S)
|
||||
end.
|
||||
|
||||
%%%==================================
|
||||
%%%% Process Elements
|
||||
|
||||
process_elements(Elements,State) ->
|
||||
lists:foldl(fun process_element/2,State,Elements).
|
||||
|
||||
%%%==================================
|
||||
%%%% Process Element
|
||||
|
||||
process_element(El=#xmlel{name=user, ns=_XMLNS},
|
||||
State=#parsing_state{host=Host}) ->
|
||||
case add_user(El,Host) of
|
||||
ok -> ok;
|
||||
{error, _Other} -> error
|
||||
end,
|
||||
State;
|
||||
|
||||
process_element(H=#xmlel{name=host},State) ->
|
||||
State#parsing_state{host=?BTL(exmpp_xml:get_attribute(H,"jid",none))};
|
||||
|
||||
process_element(#xmlel{name='server-data'},State) ->
|
||||
State;
|
||||
|
||||
process_element(El=#xmlel{name=include, ns=?NS_XINCLUDE}, State=#parsing_state{dir=Dir}) ->
|
||||
case exmpp_xml:get_attribute(El, href, none) of
|
||||
none ->
|
||||
ok;
|
||||
HrefB ->
|
||||
Href = binary_to_list(HrefB),
|
||||
%%?INFO_MSG("Parse also this file: ~n~p", [Href]),
|
||||
FileName = filename:join([Dir, Href]),
|
||||
import_file(FileName, 1),
|
||||
Href
|
||||
end,
|
||||
State;
|
||||
|
||||
process_element(#xmlcdata{cdata = _CData},State) ->
|
||||
State;
|
||||
|
||||
process_element(#xmlendtag{ns = _NS, name='server-data'},State) ->
|
||||
State;
|
||||
|
||||
process_element(#xmlendtag{ns = _NS, name=_Name},State) ->
|
||||
State;
|
||||
|
||||
process_element(El,State) ->
|
||||
io:format("Warning!: unknown element found: ~p ~n",[El]),
|
||||
State.
|
||||
|
||||
%%%==================================
|
||||
%%%% Add user
|
||||
|
||||
add_user(El, Domain) ->
|
||||
User = exmpp_xml:get_attribute(El,name,none),
|
||||
Password = exmpp_xml:get_attribute(El,password,none),
|
||||
add_user(El, Domain, User, Password).
|
||||
|
||||
%% @spec (El::xmlel(), Domain::string(), User::binary(), Password::binary() | none)
|
||||
%% -> ok | {error, ErrorText::string()}
|
||||
%% @doc Add a new user to the database.
|
||||
%% If user already exists, it will be only updated.
|
||||
add_user(El, Domain, UserBinary, none) ->
|
||||
User = ?BTL(UserBinary),
|
||||
io:format("Account ~s@~s will not be created, updating it...~n",
|
||||
[User, Domain]),
|
||||
io:format(""),
|
||||
populate_user_with_elements(El, Domain, User),
|
||||
ok;
|
||||
add_user(El, Domain, UserBinary, PasswordBinary) ->
|
||||
User = ?BTL(UserBinary),
|
||||
Password = ?BTL(PasswordBinary),
|
||||
case create_user(User,Password,Domain) of
|
||||
ok ->
|
||||
populate_user_with_elements(El, Domain, User),
|
||||
ok;
|
||||
{atomic, exists} ->
|
||||
io:format("Account ~s@~s already exists, updating it...~n",
|
||||
[User, Domain]),
|
||||
io:format(""),
|
||||
populate_user_with_elements(El, Domain, User),
|
||||
ok;
|
||||
{error, Other} ->
|
||||
?ERROR_MSG("Error adding user ~s@~s: ~p~n", [User, Domain, Other]),
|
||||
{error, Other}
|
||||
end.
|
||||
|
||||
populate_user_with_elements(El, Domain, User) ->
|
||||
exmpp_xml:foreach(
|
||||
fun (_,Child) ->
|
||||
populate_user(User,Domain,Child)
|
||||
end,
|
||||
El).
|
||||
|
||||
%% @spec (User::string(), Password::string(), Domain::string())
|
||||
%% -> ok | {atomic, exists} | {error, not_allowed}
|
||||
%% @doc Create a new user
|
||||
create_user(User,Password,Domain) ->
|
||||
case ejabberd_auth:try_register(User,Domain,Password) of
|
||||
{atomic,ok} -> ok;
|
||||
{atomic, exists} -> {atomic, exists};
|
||||
{error, not_allowed} -> {error, not_allowed};
|
||||
Other -> {error, Other}
|
||||
end.
|
||||
|
||||
%%%==================================
|
||||
%%%% Populate user
|
||||
|
||||
%% @spec (User::string(), Domain::string(), El::xml())
|
||||
%% -> ok | {error, not_found}
|
||||
%%
|
||||
%% @doc Add a new user from a XML file with a roster list.
|
||||
%%
|
||||
%% Example of a file:
|
||||
%% ```
|
||||
%% <?xml version='1.0' encoding='UTF-8'?>
|
||||
%% <server-data xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'>
|
||||
%% <host jid='localhost'>
|
||||
%% <user name='juliet' password='s3crEt'>
|
||||
%% <query xmlns='jabber:iq:roster'>
|
||||
%% <item jid='romeo@montague.net'
|
||||
%% name='Romeo'
|
||||
%% subscription='both'>
|
||||
%% <group>Friends</group>
|
||||
%% </item>
|
||||
%% </query>
|
||||
%% </user>
|
||||
%% </host>
|
||||
%% </server-data>
|
||||
%% '''
|
||||
|
||||
populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) ->
|
||||
io:format("Trying to add/update roster list...",[]),
|
||||
case loaded_module(Domain,[mod_roster_odbc,mod_roster]) of
|
||||
{ok, M} ->
|
||||
case M:set_items(User, Domain, exmpp_xml:xmlel_to_xmlelement(El)) of
|
||||
{atomic, ok} ->
|
||||
io:format(" DONE.~n",[]),
|
||||
ok;
|
||||
_ ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("Error trying to add a new user: ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
E -> io:format(" ERROR: ~p~n",[E]),
|
||||
?ERROR_MSG("No modules loaded [mod_roster, mod_roster_odbc] ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
|
||||
|
||||
%% @spec User = String with the user name
|
||||
%% Domain = String with a domain name
|
||||
%% El = Sub XML element with vCard tags values
|
||||
%% @ret ok | {error, not_found}
|
||||
%% @doc Read vcards from the XML and send it to the server
|
||||
%%
|
||||
%% Example:
|
||||
%% ```
|
||||
%% <?xml version='1.0' encoding='UTF-8'?>
|
||||
%% <server-data xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'>
|
||||
%% <host jid='localhost'>
|
||||
%% <user name='admin' password='s3crEt'>
|
||||
%% <vCard xmlns='vcard-temp'>
|
||||
%% <FN>Admin</FN>
|
||||
%% </vCard>
|
||||
%% </user>
|
||||
%% </host>
|
||||
%% </server-data>
|
||||
%% '''
|
||||
|
||||
populate_user(User,Domain,El=#xmlel{name='vCard', ns='vcard-temp'}) ->
|
||||
io:format("Trying to add/update vCards...",[]),
|
||||
case loaded_module(Domain,[mod_vcard,mod_vcard_odbc]) of
|
||||
{ok, M} -> FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)),
|
||||
IQ = iq_to_old_iq(#iq{type = set, payload = El}),
|
||||
case M:process_sm_iq(FullUser, FullUser , IQ) of
|
||||
{error,_Err} ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("Error processing vcard ~s : ~p ~n",
|
||||
[exmpp_xml:document_to_list(El), _Err]);
|
||||
_ ->
|
||||
io:format(" DONE.~n",[]), ok
|
||||
end;
|
||||
_ ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("No modules loaded [mod_vcard, mod_vcard_odbc] ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
|
||||
%% @spec User = String with the user name
|
||||
%% Domain = String with a domain name
|
||||
%% El = Sub XML element with offline messages values
|
||||
%% @ret ok | {error, not_found}
|
||||
%% @doc Read off-line message from the XML and send it to the server
|
||||
|
||||
populate_user(User,Domain,El=#xmlel{name='offline-messages'}) ->
|
||||
io:format("Trying to add/update offline-messages...",[]),
|
||||
case loaded_module(Domain, [mod_offline, mod_offline_odbc]) of
|
||||
{ok, M} ->
|
||||
ok = exmpp_xml:foreach(
|
||||
fun (_Element, {xmlcdata, _}) ->
|
||||
ok;
|
||||
(_Element, Child) ->
|
||||
From = exmpp_xml:get_attribute(Child,from,none),
|
||||
FullFrom = jid_to_old_jid(exmpp_jid:parse(From)),
|
||||
FullUser = jid_to_old_jid(exmpp_jid:make(User,
|
||||
Domain)),
|
||||
OldChild = exmpp_xml:xmlel_to_xmlelement(Child),
|
||||
_R = M:store_packet(FullFrom, FullUser, OldChild)
|
||||
end, El), io:format(" DONE.~n",[]);
|
||||
_ ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("No modules loaded [mod_offline, mod_offline_odbc] ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
|
||||
%% @spec User = String with the user name
|
||||
%% Domain = String with a domain name
|
||||
%% El = Sub XML element with private storage values
|
||||
%% @ret ok | {error, not_found}
|
||||
%% @doc Private storage parsing
|
||||
|
||||
populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:private'}) ->
|
||||
io:format("Trying to add/update private storage...",[]),
|
||||
case loaded_module(Domain,[mod_private_odbc,mod_private]) of
|
||||
{ok, M} ->
|
||||
FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)),
|
||||
IQ = iq_to_old_iq(#iq{type = set,
|
||||
ns = 'jabber:iq:private',
|
||||
kind = request,
|
||||
iq_ns = 'jabberd:client',
|
||||
payload = El}),
|
||||
case M:process_sm_iq(FullUser, FullUser, IQ ) of
|
||||
{error, _Err} ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("Error processing private storage ~s : ~p ~n",
|
||||
[exmpp_xml:document_to_list(El), _Err]);
|
||||
_ -> io:format(" DONE.~n",[]), ok
|
||||
end;
|
||||
_ ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("No modules loaded [mod_private, mod_private_odbc] ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
|
||||
populate_user(_User, _Domain, #xmlcdata{cdata = _CData}) ->
|
||||
ok;
|
||||
|
||||
populate_user(_User, _Domain, _El) ->
|
||||
ok.
|
||||
|
||||
%%%==================================
|
||||
%%%% Utilities
|
||||
|
||||
loaded_module(Domain,Options) ->
|
||||
LoadedModules = gen_mod:loaded_modules(Domain),
|
||||
case lists:filter(fun(Module) ->
|
||||
lists:member(Module, LoadedModules)
|
||||
end, Options) of
|
||||
[M|_] -> {ok, M};
|
||||
[] -> {error,not_found}
|
||||
end.
|
||||
|
||||
jid_to_old_jid(Jid) ->
|
||||
{jid, to_list(exmpp_jid:node_as_list(Jid)),
|
||||
to_list(exmpp_jid:domain_as_list(Jid)),
|
||||
to_list(exmpp_jid:resource_as_list(Jid)),
|
||||
to_list(exmpp_jid:prep_node_as_list(Jid)),
|
||||
to_list(exmpp_jid:prep_domain_as_list(Jid)),
|
||||
to_list(exmpp_jid:prep_resource_as_list(Jid))}.
|
||||
|
||||
iq_to_old_iq(#iq{id = ID, type = Type, lang = Lang, ns= NS, payload = El }) ->
|
||||
{iq, to_list(ID), Type, to_list(NS), to_list(Lang),
|
||||
exmpp_xml:xmlel_to_xmlelement(El)}.
|
||||
|
||||
to_list(L) when is_list(L) -> L;
|
||||
to_list(B) when is_binary(B) -> binary_to_list(B);
|
||||
to_list(undefined) -> "";
|
||||
to_list(B) when is_atom(B) -> atom_to_list(B).
|
||||
|
||||
%%%==================================
|
||||
|
||||
%%%% Export hosts
|
||||
|
||||
%% @spec (Dir::string(), Hosts::[string()]) -> ok
|
||||
export_hosts(Dir, Hosts) ->
|
||||
try_start_exmpp(),
|
||||
|
||||
FnT = make_filename_template(),
|
||||
DFn = make_main_basefilename(Dir, FnT),
|
||||
|
||||
{ok, Fd} = file_open(DFn),
|
||||
print(Fd, make_piefxis_xml_head()),
|
||||
print(Fd, make_piefxis_server_head()),
|
||||
|
||||
FilesAndHosts = [{make_host_filename(FnT, Host), Host} || Host <- Hosts],
|
||||
[print(Fd, make_xinclude(FnH)) || {FnH, _Host} <- FilesAndHosts],
|
||||
|
||||
print(Fd, make_piefxis_server_tail()),
|
||||
print(Fd, make_piefxis_xml_tail()),
|
||||
file_close(Fd),
|
||||
|
||||
[export_host(Dir, FnH, Host) || {FnH, Host} <- FilesAndHosts],
|
||||
|
||||
ok.
|
||||
|
||||
%%%==================================
|
||||
%%%% Export server
|
||||
|
||||
%% @spec (Dir::string()) -> ok
|
||||
export_server(Dir) ->
|
||||
Hosts = ?MYHOSTS,
|
||||
export_hosts(Dir, Hosts).
|
||||
|
||||
%%%==================================
|
||||
%%%% Export host
|
||||
|
||||
%% @spec (Dir::string(), Host::string()) -> ok
|
||||
export_host(Dir, Host) ->
|
||||
Hosts = [Host],
|
||||
export_hosts(Dir, Hosts).
|
||||
|
||||
%% @spec (Dir::string(), Fn::string(), Host::string()) -> ok
|
||||
export_host(Dir, FnH, Host) ->
|
||||
|
||||
DFn = make_host_basefilename(Dir, FnH),
|
||||
|
||||
{ok, Fd} = file_open(DFn),
|
||||
print(Fd, make_piefxis_xml_head()),
|
||||
print(Fd, make_piefxis_host_head(Host)),
|
||||
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
[export_user(Fd, Username, Host) || {Username, _Host} <- Users],
|
||||
timer:sleep(500), % Delay to ensure ERROR_MSG are displayed in the shell
|
||||
|
||||
print(Fd, make_piefxis_host_tail()),
|
||||
print(Fd, make_piefxis_xml_tail()),
|
||||
file_close(Fd).
|
||||
|
||||
%%%==================================
|
||||
%%%% PIEFXIS formatting
|
||||
|
||||
%% @spec () -> string()
|
||||
make_piefxis_xml_head() ->
|
||||
"<?xml version='1.0' encoding='UTF-8'?>".
|
||||
|
||||
%% @spec () -> string()
|
||||
make_piefxis_xml_tail() ->
|
||||
"".
|
||||
|
||||
%% @spec () -> string()
|
||||
make_piefxis_server_head() ->
|
||||
"<server-data"
|
||||
" xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'"
|
||||
" xmlns:xi='http://www.w3.org/2001/XInclude'>".
|
||||
|
||||
%% @spec () -> string()
|
||||
make_piefxis_server_tail() ->
|
||||
"</server-data>".
|
||||
|
||||
%% @spec (Host::string()) -> string()
|
||||
make_piefxis_host_head(Host) ->
|
||||
NSString =
|
||||
" xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'"
|
||||
" xmlns:xi='http://www.w3.org/2001/XInclude'",
|
||||
io_lib:format("<host~s jid='~s'>", [NSString, Host]).
|
||||
|
||||
%% @spec () -> string()
|
||||
make_piefxis_host_tail() ->
|
||||
"</host>".
|
||||
|
||||
%% @spec (Fn::string()) -> string()
|
||||
make_xinclude(Fn) ->
|
||||
Base = filename:basename(Fn),
|
||||
io_lib:format("<xi:include href='~s'/>", [Base]).
|
||||
|
||||
%%%==================================
|
||||
%%%% Export user
|
||||
|
||||
%% @spec (Fd, Username::string(), Host::string()) -> ok
|
||||
%% @doc Extract user information and print it.
|
||||
export_user(Fd, Username, Host) ->
|
||||
try extract_user(Username, Host) of
|
||||
UserString ->
|
||||
print(Fd, UserString)
|
||||
catch
|
||||
E1:E2 ->
|
||||
?ERROR_MSG("The account ~s@~s is not exported because a problem "
|
||||
"was found in it:~n~p: ~p", [Username, Host, E1, E2])
|
||||
end.
|
||||
|
||||
%% @spec (Username::string(), Host::string()) -> string()
|
||||
extract_user(Username, Host) ->
|
||||
Password = ejabberd_auth:get_password_s(Username, Host),
|
||||
UserInfo = [extract_user_info(InfoName, Username, Host) || InfoName <- [roster, offline, private, vcard]],
|
||||
UserInfoString = lists:flatten(UserInfo),
|
||||
io_lib:format("<user name='~s' password='~s'>~s</user>", [Username, Password, UserInfoString]).
|
||||
|
||||
%% @spec (InfoName::atom(), Username::string(), Host::string()) -> string()
|
||||
extract_user_info(roster, Username, Host) ->
|
||||
case loaded_module(Host,[mod_roster_odbc,mod_roster]) of
|
||||
{ok, M} ->
|
||||
From = To = jlib:make_jid(Username, Host, ""),
|
||||
SubelGet = {xmlelement, "query", [{"xmlns",?NS_ROSTER}], []},
|
||||
%%IQGet = #iq{type=get, xmlns=?NS_ROSTER, payload=SubelGet}, % this is for 3.0.0 version
|
||||
IQGet = {iq, "", get, ?NS_ROSTER, "" , SubelGet},
|
||||
Res = M:process_local_iq(From, To, IQGet),
|
||||
%%[El] = Res#iq.payload, % this is for 3.0.0 version
|
||||
{iq, _, result, _, _, Els} = Res,
|
||||
case Els of
|
||||
[El] -> exmpp_xml:document_to_list(El);
|
||||
[] -> ""
|
||||
end;
|
||||
_E ->
|
||||
""
|
||||
end;
|
||||
|
||||
extract_user_info(offline, Username, Host) ->
|
||||
case loaded_module(Host,[mod_offline,mod_offline_odbc]) of
|
||||
{ok, mod_offline} ->
|
||||
Els = mnesia_pop_offline_messages([], Username, Host),
|
||||
case Els of
|
||||
[] -> "";
|
||||
Els ->
|
||||
OfEl = {xmlelement, "offline-messages", [], Els},
|
||||
exmpp_xml:document_to_list(OfEl)
|
||||
end;
|
||||
{ok, mod_offline_odbc} ->
|
||||
"";
|
||||
_E ->
|
||||
""
|
||||
end;
|
||||
|
||||
extract_user_info(private, Username, Host) ->
|
||||
case loaded_module(Host,[mod_private,mod_private_odbc]) of
|
||||
{ok, mod_private} ->
|
||||
get_user_private_mnesia(Username, Host);
|
||||
{ok, mod_private_odbc} ->
|
||||
"";
|
||||
_E ->
|
||||
""
|
||||
end;
|
||||
|
||||
extract_user_info(vcard, Username, Host) ->
|
||||
case loaded_module(Host,[mod_vcard, mod_vcard_odbc, mod_vcard_odbc]) of
|
||||
{ok, M} ->
|
||||
From = To = jlib:make_jid(Username, Host, ""),
|
||||
SubelGet = {xmlelement, "vCard", [{"xmlns",?NS_VCARD}], []},
|
||||
%%IQGet = #iq{type=get, xmlns=?NS_VCARD, payload=SubelGet}, % this is for 3.0.0 version
|
||||
IQGet = {iq, "", get, ?NS_VCARD, "" , SubelGet},
|
||||
Res = M:process_sm_iq(From, To, IQGet),
|
||||
%%[El] = Res#iq.payload, % this is for 3.0.0 version
|
||||
{iq, _, result, _, _, Els} = Res,
|
||||
case Els of
|
||||
[El] -> exmpp_xml:document_to_list(El);
|
||||
[] -> ""
|
||||
end;
|
||||
_E ->
|
||||
""
|
||||
end.
|
||||
|
||||
%%%==================================
|
||||
%%%% Interface with ejabberd offline storage
|
||||
|
||||
%% Copied from mod_offline.erl and customized
|
||||
-record(offline_msg, {us, timestamp, expire, from, to, packet}).
|
||||
mnesia_pop_offline_messages(Ls, User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
Rs = mnesia:wread({offline_msg, US}),
|
||||
%%mnesia:delete({offline_msg, US}),
|
||||
Rs
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, Rs} ->
|
||||
TS = now(),
|
||||
Ls ++ lists:map(
|
||||
fun(R) ->
|
||||
{xmlelement, Name, Attrs, Els} = R#offline_msg.packet,
|
||||
FromString = jlib:jid_to_string(R#offline_msg.from),
|
||||
Attrs2 = lists:keystore("from", 1, Attrs, {"from", FromString}),
|
||||
Attrs3 = lists:keystore("xmlns", 1, Attrs2, {"xmlns", "jabber:client"}),
|
||||
{xmlelement, Name, Attrs3,
|
||||
Els ++
|
||||
[jlib:timestamp_to_xml(
|
||||
calendar:now_to_universal_time(
|
||||
R#offline_msg.timestamp))]}
|
||||
end,
|
||||
lists:filter(
|
||||
fun(R) ->
|
||||
case R#offline_msg.expire of
|
||||
never ->
|
||||
true;
|
||||
TimeStamp ->
|
||||
TS < TimeStamp
|
||||
end
|
||||
end,
|
||||
lists:keysort(#offline_msg.timestamp, Rs)));
|
||||
_ ->
|
||||
Ls
|
||||
end.
|
||||
|
||||
%%%==================================
|
||||
%%%% Interface with ejabberd private storage
|
||||
|
||||
get_user_private_mnesia(Username, Host) ->
|
||||
ListNsEl = mnesia:dirty_select(private_storage,
|
||||
[{#private_storage{usns={Username, Host, '$1'}, xml = '$2'},
|
||||
[], ['$$']}]),
|
||||
Els = [exmpp_xml:document_to_list(El) || [_Ns, El] <- ListNsEl],
|
||||
case lists:flatten(Els) of
|
||||
"" -> "";
|
||||
ElsString ->
|
||||
io_lib:format("<query xmlns='jabber:iq:private'>~s</query>", [ElsString])
|
||||
end.
|
||||
|
||||
%%%==================================
|
||||
%%%% Disk file access
|
||||
|
||||
%% @spec () -> string()
|
||||
make_filename_template() ->
|
||||
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(),
|
||||
lists:flatten(
|
||||
io_lib:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w",
|
||||
[Year, Month, Day, Hour, Minute, Second])).
|
||||
|
||||
%% @spec (Dir::string(), FnT::string()) -> string()
|
||||
make_main_basefilename(Dir, FnT) ->
|
||||
Filename2 = filename:flatten([FnT, ".xml"]),
|
||||
filename:join([Dir, Filename2]).
|
||||
|
||||
%% @spec (FnT::string(), Host::string()) -> FnH::string()
|
||||
%% @doc Make the filename for the host.
|
||||
%% Example: ``("20080804-231550", "jabber.example.org") -> "20080804-231550_jabber_example_org.xml"''
|
||||
make_host_filename(FnT, Host) ->
|
||||
Host2 = string:join(string:tokens(Host, "."), "_"),
|
||||
filename:flatten([FnT, "_", Host2, ".xml"]).
|
||||
|
||||
make_host_basefilename(Dir, FnT) ->
|
||||
filename:join([Dir, FnT]).
|
||||
|
||||
%% @spec (Fn::string()) -> {ok, Fd}
|
||||
file_open(Fn) ->
|
||||
file:open(Fn, [write]).
|
||||
|
||||
%% @spec (Fd) -> ok
|
||||
file_close(Fd) ->
|
||||
file:close(Fd).
|
||||
|
||||
%% @spec (Fd, String::string()) -> ok
|
||||
print(Fd, String) ->
|
||||
io:format(Fd, String, []).
|
||||
|
||||
%%%==================================
|
||||
|
||||
%%% vim: set filetype=erlang tabstop=8 foldmarker=%%%%,%%%= foldmethod=marker:
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 31 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -52,14 +52,21 @@ start_hosts() ->
|
||||
|
||||
%% Start the ODBC module on the given host
|
||||
start_odbc(Host) ->
|
||||
Supervisor_name = gen_mod:get_module_proc(Host, ejabberd_odbc_sup),
|
||||
ChildSpec =
|
||||
{gen_mod:get_module_proc(Host, ejabberd_odbc_sup),
|
||||
{Supervisor_name,
|
||||
{ejabberd_odbc_sup, start_link, [Host]},
|
||||
temporary,
|
||||
transient,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_odbc_sup]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
case supervisor:start_child(ejabberd_sup, ChildSpec) of
|
||||
{ok, _PID} ->
|
||||
ok;
|
||||
_Error ->
|
||||
?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying...~n", [Supervisor_name, _Error]),
|
||||
start_odbc(Host)
|
||||
end.
|
||||
|
||||
%% Returns true if we have configured odbc_server for the given host
|
||||
needs_odbc(Host) ->
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 10 Nov 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -54,6 +54,8 @@
|
||||
xml_stream_state,
|
||||
timeout}).
|
||||
|
||||
-define(HIBERNATE_TIMEOUT, 90000).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@@ -134,19 +136,14 @@ handle_call({starttls, TLSSocket}, _From,
|
||||
#state{xml_stream_state = XMLStreamState,
|
||||
c2s_pid = C2SPid,
|
||||
max_stanza_size = MaxStanzaSize} = State) ->
|
||||
if
|
||||
XMLStreamState /= undefined ->
|
||||
xml_stream:close(XMLStreamState);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
close_stream(XMLStreamState),
|
||||
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
|
||||
NewState = State#state{socket = TLSSocket,
|
||||
sock_mod = tls,
|
||||
xml_stream_state = NewXMLStreamState},
|
||||
case tls:recv_data(TLSSocket, "") of
|
||||
{ok, TLSData} ->
|
||||
{reply, ok, process_data(TLSData, NewState)};
|
||||
{reply, ok, process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
|
||||
{error, _Reason} ->
|
||||
{stop, normal, ok, NewState}
|
||||
end;
|
||||
@@ -154,14 +151,14 @@ handle_call({compress, ZlibSocket}, _From,
|
||||
#state{xml_stream_state = XMLStreamState,
|
||||
c2s_pid = C2SPid,
|
||||
max_stanza_size = MaxStanzaSize} = State) ->
|
||||
xml_stream:close(XMLStreamState),
|
||||
close_stream(XMLStreamState),
|
||||
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
|
||||
NewState = State#state{socket = ZlibSocket,
|
||||
sock_mod = ejabberd_zlib,
|
||||
xml_stream_state = NewXMLStreamState},
|
||||
case ejabberd_zlib:recv_data(ZlibSocket, "") of
|
||||
{ok, ZlibData} ->
|
||||
{reply, ok, process_data(ZlibData, NewState)};
|
||||
{reply, ok, process_data(ZlibData, NewState), ?HIBERNATE_TIMEOUT};
|
||||
{error, _Reason} ->
|
||||
{stop, normal, ok, NewState}
|
||||
end;
|
||||
@@ -169,20 +166,21 @@ handle_call(reset_stream, _From,
|
||||
#state{xml_stream_state = XMLStreamState,
|
||||
c2s_pid = C2SPid,
|
||||
max_stanza_size = MaxStanzaSize} = State) ->
|
||||
xml_stream:close(XMLStreamState),
|
||||
close_stream(XMLStreamState),
|
||||
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
|
||||
Reply = ok,
|
||||
{reply, Reply, State#state{xml_stream_state = NewXMLStreamState}};
|
||||
{reply, Reply, State#state{xml_stream_state = NewXMLStreamState},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
handle_call({become_controller, C2SPid}, _From, State) ->
|
||||
XMLStreamState = xml_stream:new(C2SPid, State#state.max_stanza_size),
|
||||
NewState = State#state{c2s_pid = C2SPid,
|
||||
xml_stream_state = XMLStreamState},
|
||||
activate_socket(NewState),
|
||||
Reply = ok,
|
||||
{reply, Reply, NewState};
|
||||
{reply, Reply, NewState, ?HIBERNATE_TIMEOUT};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
@@ -192,11 +190,11 @@ handle_call(_Request, _From, State) ->
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast({change_shaper, Shaper}, State) ->
|
||||
NewShaperState = shaper:new(Shaper),
|
||||
{noreply, State#state{shaper_state = NewShaperState}};
|
||||
{noreply, State#state{shaper_state = NewShaperState}, ?HIBERNATE_TIMEOUT};
|
||||
handle_cast(close, State) ->
|
||||
{stop, normal, State};
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
@@ -207,24 +205,26 @@ handle_cast(_Msg, State) ->
|
||||
handle_info({Tag, _TCPSocket, Data},
|
||||
#state{socket = Socket,
|
||||
sock_mod = SockMod} = State)
|
||||
when (Tag == tcp) or (Tag == ssl) ->
|
||||
when (Tag == tcp) or (Tag == ssl) or (Tag == ejabberd_xml) ->
|
||||
case SockMod of
|
||||
tls ->
|
||||
case tls:recv_data(Socket, Data) of
|
||||
{ok, TLSData} ->
|
||||
{noreply, process_data(TLSData, State)};
|
||||
{noreply, process_data(TLSData, State),
|
||||
?HIBERNATE_TIMEOUT};
|
||||
{error, _Reason} ->
|
||||
{stop, normal, State}
|
||||
end;
|
||||
ejabberd_zlib ->
|
||||
case ejabberd_zlib:recv_data(Socket, Data) of
|
||||
{ok, ZlibData} ->
|
||||
{noreply, process_data(ZlibData, State)};
|
||||
{noreply, process_data(ZlibData, State),
|
||||
?HIBERNATE_TIMEOUT};
|
||||
{error, _Reason} ->
|
||||
{stop, normal, State}
|
||||
end;
|
||||
_ ->
|
||||
{noreply, process_data(Data, State)}
|
||||
{noreply, process_data(Data, State), ?HIBERNATE_TIMEOUT}
|
||||
end;
|
||||
handle_info({Tag, _TCPSocket}, State)
|
||||
when (Tag == tcp_closed) or (Tag == ssl_closed) ->
|
||||
@@ -233,15 +233,18 @@ handle_info({Tag, _TCPSocket, Reason}, State)
|
||||
when (Tag == tcp_error) or (Tag == ssl_error) ->
|
||||
case Reason of
|
||||
timeout ->
|
||||
{noreply, State};
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT};
|
||||
_ ->
|
||||
{stop, normal, State}
|
||||
end;
|
||||
handle_info({timeout, _Ref, activate}, State) ->
|
||||
activate_socket(State),
|
||||
{noreply, State};
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT};
|
||||
handle_info(timeout, State) ->
|
||||
proc_lib:hibernate(gen_server, enter_loop, [?MODULE, [], State]),
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
@@ -252,7 +255,7 @@ handle_info(_Info, State) ->
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, #state{xml_stream_state = XMLStreamState,
|
||||
c2s_pid = C2SPid} = State) ->
|
||||
xml_stream:close(XMLStreamState),
|
||||
close_stream(XMLStreamState),
|
||||
if
|
||||
C2SPid /= undefined ->
|
||||
gen_fsm:send_event(C2SPid, closed);
|
||||
@@ -291,13 +294,35 @@ activate_socket(#state{socket = Socket,
|
||||
ok
|
||||
end.
|
||||
|
||||
%% Data processing for connectors directly generating xmlelement in
|
||||
%% Erlang data structure.
|
||||
%% WARNING: Shaper does not work with Erlang data structure.
|
||||
process_data([], State) ->
|
||||
activate_socket(State),
|
||||
State;
|
||||
process_data([Element|Els], #state{c2s_pid = C2SPid} = State)
|
||||
when element(1, Element) == xmlelement;
|
||||
element(1, Element) == xmlstreamstart;
|
||||
element(1, Element) == xmlstreamelement;
|
||||
element(1, Element) == xmlstreamend ->
|
||||
if
|
||||
C2SPid == undefined ->
|
||||
State;
|
||||
true ->
|
||||
catch gen_fsm:send_event(C2SPid, element_wrapper(Element)),
|
||||
process_data(Els, State)
|
||||
end;
|
||||
%% Data processing for connectors receivind data as string.
|
||||
process_data(Data,
|
||||
#state{xml_stream_state = XMLStreamState,
|
||||
shaper_state = ShaperState} = State) ->
|
||||
shaper_state = ShaperState,
|
||||
c2s_pid = C2SPid} = State) ->
|
||||
?DEBUG("Received XML on stream = ~p", [binary_to_list(Data)]),
|
||||
XMLStreamState1 = xml_stream:parse(XMLStreamState, Data),
|
||||
{NewShaperState, Pause} = shaper:update(ShaperState, size(Data)),
|
||||
if
|
||||
C2SPid == undefined ->
|
||||
ok;
|
||||
Pause > 0 ->
|
||||
erlang:start_timer(Pause, self(), activate);
|
||||
true ->
|
||||
@@ -306,3 +331,17 @@ process_data(Data,
|
||||
State#state{xml_stream_state = XMLStreamState1,
|
||||
shaper_state = NewShaperState}.
|
||||
|
||||
%% Element coming from XML parser are wrapped inside xmlstreamelement
|
||||
%% When we receive directly xmlelement tuple (from a socket module
|
||||
%% speaking directly Erlang XML), we wrap it inside the same
|
||||
%% xmlstreamelement coming from the XML parser.
|
||||
element_wrapper(XMLElement)
|
||||
when element(1, XMLElement) == xmlelement ->
|
||||
{xmlstreamelement, XMLElement};
|
||||
element_wrapper(Element) ->
|
||||
Element.
|
||||
|
||||
close_stream(undefined) ->
|
||||
ok;
|
||||
close_stream(XMLStreamState) ->
|
||||
xml_stream:close(XMLStreamState).
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 27 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
%% API
|
||||
-export([route/3,
|
||||
route_error/4,
|
||||
register_route/1,
|
||||
register_route/2,
|
||||
register_routes/1,
|
||||
@@ -72,6 +73,17 @@ route(From, To, Packet) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%% Route the error packet only if the originating packet is not an error itself.
|
||||
%% RFC3920 9.3.1
|
||||
route_error(From, To, ErrPacket, OrigPacket) ->
|
||||
{xmlelement, _Name, Attrs, _Els} = OrigPacket,
|
||||
case "error" == xml:get_attr_s("type", Attrs) of
|
||||
false ->
|
||||
route(From, To, ErrPacket);
|
||||
true ->
|
||||
ok
|
||||
end.
|
||||
|
||||
register_route(Domain) ->
|
||||
register_route(Domain, undefined).
|
||||
|
||||
@@ -91,7 +103,7 @@ register_route(Domain, LocalHint) ->
|
||||
mnesia:transaction(F);
|
||||
N ->
|
||||
F = fun() ->
|
||||
case mnesia:read({route, LDomain}) of
|
||||
case mnesia:wread({route, LDomain}) of
|
||||
[] ->
|
||||
mnesia:write(
|
||||
#route{domain = LDomain,
|
||||
@@ -150,9 +162,9 @@ unregister_route(Domain) ->
|
||||
mnesia:transaction(F);
|
||||
_ ->
|
||||
F = fun() ->
|
||||
case mnesia:match(#route{domain = LDomain,
|
||||
pid = Pid,
|
||||
_ = '_'}) of
|
||||
case mnesia:match_object(#route{domain=LDomain,
|
||||
pid = Pid,
|
||||
_ = '_'}) of
|
||||
[R] ->
|
||||
I = R#route.local_hint,
|
||||
mnesia:write(
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 7 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -34,20 +34,25 @@
|
||||
route/3,
|
||||
have_connection/1,
|
||||
has_key/2,
|
||||
get_connections_pids/1,
|
||||
try_register/1,
|
||||
remove_connection/3,
|
||||
find_connection/2,
|
||||
dirty_get_connections/0,
|
||||
allow_host/2,
|
||||
ctl_process/2
|
||||
incoming_s2s_number/0,
|
||||
outgoing_s2s_number/0
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
%% ejabberd API
|
||||
-export([get_info_s2s_connections/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd_ctl.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
|
||||
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1).
|
||||
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1).
|
||||
@@ -108,6 +113,14 @@ has_key(FromTo, Key) ->
|
||||
true
|
||||
end.
|
||||
|
||||
get_connections_pids(FromTo) ->
|
||||
case catch mnesia:dirty_read(s2s, FromTo) of
|
||||
L when is_list(L) ->
|
||||
[Connection#s2s.pid || Connection <- L];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
try_register(FromTo) ->
|
||||
Key = randoms:get_string(),
|
||||
MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo),
|
||||
@@ -155,10 +168,7 @@ init([]) ->
|
||||
{attributes, record_info(fields, s2s)}]),
|
||||
mnesia:add_table_copy(s2s, node(), ram_copies),
|
||||
mnesia:subscribe(system),
|
||||
ejabberd_ctl:register_commands(
|
||||
[{"incoming-s2s-number", "print number of incoming s2s connections on the node"},
|
||||
{"outgoing-s2s-number", "print number of outgoing s2s connections on the node"}],
|
||||
?MODULE, ctl_process),
|
||||
ejabberd_commands:register_commands(commands()),
|
||||
{ok, #state{}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -212,6 +222,7 @@ handle_info(_Info, State) ->
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ejabberd_commands:unregister_commands(commands()),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -235,18 +246,23 @@ clean_table_from_bad_node(Node) ->
|
||||
mnesia:delete_object(E)
|
||||
end, Es)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
mnesia:async_dirty(F).
|
||||
|
||||
do_route(From, To, Packet) ->
|
||||
?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
|
||||
[From, To, Packet, 8]),
|
||||
case find_connection(From, To) of
|
||||
{atomic, Pid} when pid(Pid) ->
|
||||
{atomic, Pid} when is_pid(Pid) ->
|
||||
?DEBUG("sending to process ~p~n", [Pid]),
|
||||
{xmlelement, Name, Attrs, Els} = Packet,
|
||||
NewAttrs = jlib:replace_from_to_attrs(jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To),
|
||||
Attrs),
|
||||
#jid{lserver = MyServer} = From,
|
||||
ejabberd_hooks:run(
|
||||
s2s_send_packet,
|
||||
MyServer,
|
||||
[From, To, Packet]),
|
||||
send_element(Pid, {xmlelement, Name, NewAttrs, Els}),
|
||||
ok;
|
||||
{aborted, _Reason} ->
|
||||
@@ -393,36 +409,54 @@ needed_connections_number(Ls, MaxS2SConnectionsNumber,
|
||||
is_service(From, To) ->
|
||||
LFromDomain = From#jid.lserver,
|
||||
case ejabberd_config:get_local_option({route_subdomains, LFromDomain}) of
|
||||
s2s -> % bypass RFC 3920 10.3
|
||||
false;
|
||||
_ ->
|
||||
LDstDomain = To#jid.lserver,
|
||||
P = fun(Domain) -> is_subdomain(LDstDomain, Domain) end,
|
||||
lists:any(P, ?MYHOSTS)
|
||||
s2s -> % bypass RFC 3920 10.3
|
||||
false;
|
||||
_ ->
|
||||
Hosts = ?MYHOSTS,
|
||||
P = fun(ParentDomain) -> lists:member(ParentDomain, Hosts) end,
|
||||
lists:any(P, parent_domains(To#jid.lserver))
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: is_subdomain(Domain1, Domain2) -> true | false
|
||||
%% Description: Return true if Domain1 (a string representing an
|
||||
%% internet domain name) is a subdomain (or the same domain) of
|
||||
%% Domain2
|
||||
%% --------------------------------------------------------------------
|
||||
is_subdomain(Domain1, Domain2) ->
|
||||
lists:suffix(string:tokens(Domain2, "."), string:tokens(Domain1, ".")).
|
||||
parent_domains(Domain) ->
|
||||
lists:foldl(
|
||||
fun(Label, []) ->
|
||||
[Label];
|
||||
(Label, [Head | Tail]) ->
|
||||
[Label ++ "." ++ Head, Head | Tail]
|
||||
end, [], lists:reverse(string:tokens(Domain, "."))).
|
||||
|
||||
send_element(Pid, El) ->
|
||||
Pid ! {send_element, El}.
|
||||
|
||||
ctl_process(_Val, ["incoming-s2s-number"]) ->
|
||||
N = length(supervisor:which_children(ejabberd_s2s_in_sup)),
|
||||
io:format("~p~n", [N]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(_Val, ["outgoing-s2s-number"]) ->
|
||||
N = length(supervisor:which_children(ejabberd_s2s_out_sup)),
|
||||
io:format("~p~n", [N]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(Val, _Args) ->
|
||||
Val.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% ejabberd commands
|
||||
|
||||
commands() ->
|
||||
[
|
||||
#ejabberd_commands{name = incoming_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
desc = "Number of incoming s2s connections on the node",
|
||||
module = ?MODULE, function = incoming_s2s_number,
|
||||
args = [],
|
||||
result = {s2s_incoming, integer}},
|
||||
#ejabberd_commands{name = outgoing_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
desc = "Number of outgoing s2s connections on the node",
|
||||
module = ?MODULE, function = outgoing_s2s_number,
|
||||
args = [],
|
||||
result = {s2s_outgoing, integer}}
|
||||
].
|
||||
|
||||
incoming_s2s_number() ->
|
||||
length(supervisor:which_children(ejabberd_s2s_in_sup)).
|
||||
|
||||
outgoing_s2s_number() ->
|
||||
length(supervisor:which_children(ejabberd_s2s_out_sup)).
|
||||
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Update Mnesia tables
|
||||
|
||||
update_tables() ->
|
||||
case catch mnesia:table_info(s2s, type) of
|
||||
@@ -452,13 +486,59 @@ update_tables() ->
|
||||
|
||||
%% Check if host is in blacklist or white list
|
||||
allow_host(MyServer, S2SHost) ->
|
||||
case ejabberd_config:get_local_option({{s2s_host, S2SHost},MyServer}) of
|
||||
deny -> false;
|
||||
allow -> true;
|
||||
_ ->
|
||||
case ejabberd_config:get_local_option({s2s_default_policy, MyServer}) of
|
||||
deny -> false;
|
||||
allow -> true;
|
||||
_ -> true %% The default s2s policy is allow
|
||||
end
|
||||
Hosts = ?MYHOSTS,
|
||||
case lists:dropwhile(
|
||||
fun(ParentDomain) ->
|
||||
not lists:member(ParentDomain, Hosts)
|
||||
end, parent_domains(MyServer)) of
|
||||
[MyHost|_] ->
|
||||
allow_host1(MyHost, S2SHost);
|
||||
[] ->
|
||||
allow_host1(MyServer, S2SHost)
|
||||
end.
|
||||
|
||||
allow_host1(MyHost, S2SHost) ->
|
||||
case ejabberd_config:get_local_option({{s2s_host, S2SHost}, MyHost}) of
|
||||
deny -> false;
|
||||
allow -> true;
|
||||
_ ->
|
||||
case ejabberd_config:get_local_option({s2s_default_policy, MyHost}) of
|
||||
deny -> false;
|
||||
_ ->
|
||||
case ejabberd_hooks:run_fold(s2s_allow_host, MyHost,
|
||||
allow, [MyHost, S2SHost]) of
|
||||
deny -> false;
|
||||
allow -> true;
|
||||
_ -> true
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
%% Get information about S2S connections of the specified type.
|
||||
%% @spec (Type) -> [Info]
|
||||
%% where Type = in | out
|
||||
%% Info = [{InfoName::atom(), InfoValue::any()}]
|
||||
get_info_s2s_connections(Type) ->
|
||||
ChildType = case Type of
|
||||
in -> ejabberd_s2s_in_sup;
|
||||
out -> ejabberd_s2s_out_sup
|
||||
end,
|
||||
Connections = supervisor:which_children(ChildType),
|
||||
get_s2s_info(Connections,Type).
|
||||
|
||||
get_s2s_info(Connections,Type)->
|
||||
complete_s2s_info(Connections,Type,[]).
|
||||
complete_s2s_info([],_,Result)->
|
||||
Result;
|
||||
complete_s2s_info([Connection|T],Type,Result)->
|
||||
{_,PID,_,_}=Connection,
|
||||
State = get_s2s_state(PID),
|
||||
complete_s2s_info(T,Type,[State|Result]).
|
||||
|
||||
get_s2s_state(S2sPid)->
|
||||
Infos = case gen_fsm:sync_send_all_state_event(S2sPid,get_state_infos) of
|
||||
{state_infos, Is} -> [{status, open} | Is];
|
||||
{noproc,_} -> [{status, closed}]; %% Connection closed
|
||||
{badrpc,_} -> [{status, error}]
|
||||
end,
|
||||
[{s2s_pid, S2sPid} | Infos].
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -48,8 +48,22 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-ifdef(SSL40).
|
||||
-include_lib("public_key/include/public_key.hrl").
|
||||
-define(PKIXEXPLICIT, 'OTP-PUB-KEY').
|
||||
-define(PKIXIMPLICIT, 'OTP-PUB-KEY').
|
||||
-else.
|
||||
-ifdef(SSL39).
|
||||
-include_lib("ssl/include/ssl_pkix.hrl").
|
||||
-define(PKIXEXPLICIT, 'OTP-PKIX').
|
||||
-define(PKIXIMPLICIT, 'OTP-PKIX').
|
||||
-else.
|
||||
-include_lib("ssl/include/PKIX1Explicit88.hrl").
|
||||
-include_lib("ssl/include/PKIX1Implicit88.hrl").
|
||||
-define(PKIXEXPLICIT, 'PKIX1Explicit88').
|
||||
-define(PKIXIMPLICIT, 'PKIX1Implicit88').
|
||||
-endif.
|
||||
-endif.
|
||||
-include("XmppAddr.hrl").
|
||||
|
||||
-define(DICT, dict).
|
||||
@@ -60,7 +74,10 @@
|
||||
shaper,
|
||||
tls = false,
|
||||
tls_enabled = false,
|
||||
tls_required = false,
|
||||
tls_certverify = false,
|
||||
tls_options = [],
|
||||
server,
|
||||
authenticated = false,
|
||||
auth_domain,
|
||||
connections = ?DICT:new(),
|
||||
@@ -136,12 +153,16 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
{value, {_, S}} -> S;
|
||||
_ -> none
|
||||
end,
|
||||
StartTLS = case ejabberd_config:get_local_option(s2s_use_starttls) of
|
||||
undefined ->
|
||||
false;
|
||||
UseStartTLS ->
|
||||
UseStartTLS
|
||||
end,
|
||||
{StartTLS, TLSRequired, TLSCertverify} = case ejabberd_config:get_local_option(s2s_use_starttls) of
|
||||
UseTls when (UseTls==undefined) or (UseTls==false) ->
|
||||
{false, false, false};
|
||||
UseTls when (UseTls==true) or (UseTls==optional) ->
|
||||
{true, false, false};
|
||||
required ->
|
||||
{true, true, false};
|
||||
required_trusted ->
|
||||
{true, true, true}
|
||||
end,
|
||||
TLSOpts = case ejabberd_config:get_local_option(s2s_certfile) of
|
||||
undefined ->
|
||||
[];
|
||||
@@ -156,6 +177,8 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
shaper = Shaper,
|
||||
tls = StartTLS,
|
||||
tls_enabled = false,
|
||||
tls_required = TLSRequired,
|
||||
tls_certverify = TLSCertverify,
|
||||
tls_options = TLSOpts,
|
||||
timer = Timer}}.
|
||||
|
||||
@@ -169,8 +192,9 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
case {xml:get_attr_s("xmlns", Attrs),
|
||||
xml:get_attr_s("xmlns:db", Attrs),
|
||||
xml:get_attr_s("to", Attrs),
|
||||
xml:get_attr_s("version", Attrs) == "1.0"} of
|
||||
{"jabber:server", _, true} when
|
||||
{"jabber:server", _, Server, true} when
|
||||
StateData#state.tls and (not StateData#state.authenticated) ->
|
||||
send_text(StateData, ?STREAM_HEADER(" version='1.0'")),
|
||||
SASL =
|
||||
@@ -178,16 +202,18 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
StateData#state.tls_enabled ->
|
||||
case (StateData#state.sockmod):get_peer_certificate(
|
||||
StateData#state.socket) of
|
||||
{ok, _Cert} ->
|
||||
case (StateData#state.sockmod):get_verify_result(
|
||||
StateData#state.socket) of
|
||||
{ok, Cert} ->
|
||||
case (StateData#state.sockmod):get_verify_result(StateData#state.socket) of
|
||||
0 ->
|
||||
[{xmlelement, "mechanisms",
|
||||
[{"xmlns", ?NS_SASL}],
|
||||
[{xmlelement, "mechanism", [],
|
||||
[{xmlcdata, "EXTERNAL"}]}]}];
|
||||
_ ->
|
||||
[]
|
||||
CertVerifyRes ->
|
||||
case StateData#state.tls_certverify of
|
||||
true -> {error_cert_verif, CertVerifyRes, Cert};
|
||||
false -> []
|
||||
end
|
||||
end;
|
||||
error ->
|
||||
[]
|
||||
@@ -198,21 +224,44 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
StartTLS = if
|
||||
StateData#state.tls_enabled ->
|
||||
[];
|
||||
true ->
|
||||
[{xmlelement, "starttls",
|
||||
[{"xmlns", ?NS_TLS}], []}]
|
||||
(not StateData#state.tls_enabled) and (not StateData#state.tls_required) ->
|
||||
[{xmlelement, "starttls", [{"xmlns", ?NS_TLS}], []}];
|
||||
(not StateData#state.tls_enabled) and StateData#state.tls_required ->
|
||||
[{xmlelement, "starttls", [{"xmlns", ?NS_TLS}],
|
||||
[{xmlelement, "required", [], []}]
|
||||
}]
|
||||
end,
|
||||
send_element(StateData,
|
||||
{xmlelement, "stream:features", [],
|
||||
SASL ++ StartTLS}),
|
||||
{next_state, wait_for_feature_request, StateData};
|
||||
{"jabber:server", _, true} when
|
||||
case SASL of
|
||||
{error_cert_verif, CertVerifyResult, Certificate} ->
|
||||
CertError = tls:get_cert_verify_string(CertVerifyResult, Certificate),
|
||||
RemoteServer = xml:get_attr_s("from", Attrs),
|
||||
?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)", [StateData#state.server, RemoteServer, CertError]),
|
||||
send_text(StateData, xml:element_to_string(?SERRT_POLICY_VIOLATION("en", CertError))),
|
||||
{atomic, Pid} = ejabberd_s2s:find_connection(jlib:make_jid("", Server, ""), jlib:make_jid("", RemoteServer, "")),
|
||||
ejabberd_s2s_out:stop_connection(Pid),
|
||||
|
||||
{stop, normal, StateData};
|
||||
_ ->
|
||||
send_element(StateData,
|
||||
{xmlelement, "stream:features", [],
|
||||
SASL ++ StartTLS ++
|
||||
ejabberd_hooks:run_fold(
|
||||
s2s_stream_features,
|
||||
Server,
|
||||
[], [Server])}),
|
||||
{next_state, wait_for_feature_request, StateData#state{server = Server}}
|
||||
end;
|
||||
{"jabber:server", _, Server, true} when
|
||||
StateData#state.authenticated ->
|
||||
send_text(StateData, ?STREAM_HEADER(" version='1.0'")),
|
||||
send_element(StateData,
|
||||
{xmlelement, "stream:features", [], []}),
|
||||
{xmlelement, "stream:features", [],
|
||||
ejabberd_hooks:run_fold(
|
||||
s2s_stream_features,
|
||||
Server,
|
||||
[], [Server])}),
|
||||
{next_state, stream_established, StateData};
|
||||
{"jabber:server", "jabber:server:dialback", _} ->
|
||||
{"jabber:server", "jabber:server:dialback", _Server, _} ->
|
||||
send_text(StateData, ?STREAM_HEADER("")),
|
||||
{next_state, stream_established, StateData};
|
||||
_ ->
|
||||
@@ -243,15 +292,26 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
|
||||
SockMod == gen_tcp ->
|
||||
?DEBUG("starttls", []),
|
||||
Socket = StateData#state.socket,
|
||||
TLSOpts = StateData#state.tls_options,
|
||||
TLSOpts = case ejabberd_config:get_local_option(
|
||||
{domain_certfile,
|
||||
StateData#state.server}) of
|
||||
undefined ->
|
||||
StateData#state.tls_options;
|
||||
CertFile ->
|
||||
[{certfile, CertFile} |
|
||||
lists:keydelete(
|
||||
certfile, 1,
|
||||
StateData#state.tls_options)]
|
||||
end,
|
||||
TLSSocket = (StateData#state.sockmod):starttls(
|
||||
Socket, TLSOpts,
|
||||
xml:element_to_string(
|
||||
xml:element_to_binary(
|
||||
{xmlelement, "proceed", [{"xmlns", ?NS_TLS}], []})),
|
||||
{next_state, wait_for_stream,
|
||||
StateData#state{socket = TLSSocket,
|
||||
streamid = new_id(),
|
||||
tls_enabled = true
|
||||
tls_enabled = true,
|
||||
tls_options = TLSOpts
|
||||
}};
|
||||
{?NS_SASL, "auth"} when TLSEnabled ->
|
||||
Mech = xml:get_attr_s("mechanism", Attrs),
|
||||
@@ -341,10 +401,11 @@ stream_established({xmlstreamelement, El}, StateData) ->
|
||||
LFrom = jlib:nameprep(From),
|
||||
%% Checks if the from domain is allowed and if the to
|
||||
%% domain is handled by this server:
|
||||
case {ejabberd_s2s:allow_host(To, From),
|
||||
case {ejabberd_s2s:allow_host(LTo, LFrom),
|
||||
lists:member(LTo, ejabberd_router:dirty_get_all_domains())} of
|
||||
{true, true} ->
|
||||
ejabberd_s2s_out:start(To, From,
|
||||
ejabberd_s2s_out:terminate_if_waiting_delay(LTo, LFrom),
|
||||
ejabberd_s2s_out:start(LTo, LFrom,
|
||||
{verify, self(),
|
||||
Key, StateData#state.streamid}),
|
||||
Conns = ?DICT:store({LFrom, LTo}, wait_for_verification,
|
||||
@@ -403,6 +464,10 @@ stream_established({xmlstreamelement, El}, StateData) ->
|
||||
if ((Name == "iq") or
|
||||
(Name == "message") or
|
||||
(Name == "presence")) ->
|
||||
ejabberd_hooks:run(
|
||||
s2s_receive_packet,
|
||||
LTo,
|
||||
[From, To, NewEl]),
|
||||
ejabberd_router:route(
|
||||
From, To, NewEl);
|
||||
true ->
|
||||
@@ -418,6 +483,10 @@ stream_established({xmlstreamelement, El}, StateData) ->
|
||||
if ((Name == "iq") or
|
||||
(Name == "message") or
|
||||
(Name == "presence")) ->
|
||||
ejabberd_hooks:run(
|
||||
s2s_receive_packet,
|
||||
LTo,
|
||||
[From, To, NewEl]),
|
||||
ejabberd_router:route(
|
||||
From, To, NewEl);
|
||||
true ->
|
||||
@@ -501,6 +570,44 @@ stream_established(closed, StateData) ->
|
||||
%%----------------------------------------------------------------------
|
||||
handle_event(_Event, StateName, StateData) ->
|
||||
{next_state, StateName, StateData}.
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_sync_event/4
|
||||
%% Returns: The associated StateData for this connection
|
||||
%% {reply, Reply, NextStateName, NextStateData}
|
||||
%% Reply = {state_infos, [{InfoName::atom(), InfoValue::any()]
|
||||
%%----------------------------------------------------------------------
|
||||
handle_sync_event(get_state_infos, _From, StateName, StateData) ->
|
||||
SockMod = StateData#state.sockmod,
|
||||
{Addr,Port} = try SockMod:peername(StateData#state.socket) of
|
||||
{ok, {A,P}} -> {A,P};
|
||||
{error, _} -> {unknown,unknown}
|
||||
catch
|
||||
_:_ -> {unknown,unknown}
|
||||
end,
|
||||
Domains = case StateData#state.authenticated of
|
||||
true ->
|
||||
[StateData#state.auth_domain];
|
||||
false ->
|
||||
Connections = StateData#state.connections,
|
||||
[D || {{D, _}, established} <-
|
||||
dict:to_list(Connections)]
|
||||
end,
|
||||
Infos = [
|
||||
{direction, in},
|
||||
{statename, StateName},
|
||||
{addr, Addr},
|
||||
{port, Port},
|
||||
{streamid, StateData#state.streamid},
|
||||
{tls, StateData#state.tls},
|
||||
{tls_enabled, StateData#state.tls_enabled},
|
||||
{tls_options, StateData#state.tls_options},
|
||||
{authenticated, StateData#state.authenticated},
|
||||
{shaper, StateData#state.shaper},
|
||||
{sockmod, SockMod},
|
||||
{domains, Domains}
|
||||
],
|
||||
Reply = {state_infos, Infos},
|
||||
{reply,Reply,StateName,StateData};
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_sync_event/4
|
||||
@@ -554,7 +661,7 @@ send_text(StateData, Text) ->
|
||||
(StateData#state.sockmod):send(StateData#state.socket, Text).
|
||||
|
||||
send_element(StateData, El) ->
|
||||
send_text(StateData, xml:element_to_string(El)).
|
||||
send_text(StateData, xml:element_to_binary(El)).
|
||||
|
||||
|
||||
change_shaper(StateData, Host, JID) ->
|
||||
@@ -599,7 +706,7 @@ get_cert_domains(Cert) ->
|
||||
lists:flatmap(
|
||||
fun(#'AttributeTypeAndValue'{type = ?'id-at-commonName',
|
||||
value = Val}) ->
|
||||
case 'PKIX1Explicit88':decode('X520CommonName', Val) of
|
||||
case ?PKIXEXPLICIT:decode('X520CommonName', Val) of
|
||||
{ok, {_, D1}} ->
|
||||
D = if
|
||||
is_list(D1) -> D1;
|
||||
@@ -633,7 +740,7 @@ get_cert_domains(Cert) ->
|
||||
is_binary(Val) -> Val;
|
||||
true -> Val
|
||||
end,
|
||||
case 'PKIX1Implicit88':decode('SubjectAltName', BVal) of
|
||||
case ?PKIXIMPLICIT:decode('SubjectAltName', BVal) of
|
||||
{ok, SANs} ->
|
||||
lists:flatmap(
|
||||
fun({otherName,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -33,6 +33,7 @@
|
||||
-export([start/3,
|
||||
start_link/3,
|
||||
start_connection/1,
|
||||
terminate_if_waiting_delay/2,
|
||||
stop_connection/1]).
|
||||
|
||||
%% p1_fsm callbacks (same as gen_fsm)
|
||||
@@ -43,6 +44,7 @@
|
||||
wait_for_features/2,
|
||||
wait_for_auth_result/2,
|
||||
wait_for_starttls_proceed/2,
|
||||
relay_to_bridge/2,
|
||||
reopen_socket/2,
|
||||
wait_before_retry/2,
|
||||
stream_established/2,
|
||||
@@ -50,8 +52,10 @@
|
||||
handle_sync_event/4,
|
||||
handle_info/3,
|
||||
terminate/3,
|
||||
print_state/1,
|
||||
code_change/4,
|
||||
test_get_addr_port/1]).
|
||||
test_get_addr_port/1,
|
||||
get_addr_port/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
@@ -62,12 +66,14 @@
|
||||
tls = false,
|
||||
tls_required = false,
|
||||
tls_enabled = false,
|
||||
tls_options = [],
|
||||
tls_options = [connect],
|
||||
authenticated = false,
|
||||
db_enabled = true,
|
||||
try_auth = true,
|
||||
myname, server, queue,
|
||||
delay_to_retry = undefined_delay,
|
||||
new = false, verify = false,
|
||||
bridge,
|
||||
timer}).
|
||||
|
||||
%%-define(DBGFSM, true).
|
||||
@@ -81,16 +87,20 @@
|
||||
%% Module start with or without supervisor:
|
||||
-ifdef(NO_TRANSIENT_SUPERVISORS).
|
||||
-define(SUPERVISOR_START, p1_fsm:start(ejabberd_s2s_out, [From, Host, Type],
|
||||
?FSMLIMITS ++ ?FSMOPTS)).
|
||||
fsm_limit_opts() ++ ?FSMOPTS)).
|
||||
-else.
|
||||
-define(SUPERVISOR_START, supervisor:start_child(ejabberd_s2s_out_sup,
|
||||
[From, Host, Type])).
|
||||
-endif.
|
||||
|
||||
%% Only change this value if you now what your are doing:
|
||||
-define(FSMLIMITS,[]).
|
||||
%% -define(FSMLIMITS, [{max_queue, 2000}]).
|
||||
-define(FSMTIMEOUT, 5000).
|
||||
-define(FSMTIMEOUT, 30000).
|
||||
|
||||
%% We do not block on send anymore.
|
||||
-define(TCP_SEND_TIMEOUT, 15000).
|
||||
|
||||
%% Maximum delay to wait before retrying to connect after a failed attempt.
|
||||
%% Specified in miliseconds. Default value is 5 minutes.
|
||||
-define(MAX_RETRY_DELAY, 300000).
|
||||
|
||||
-define(STREAM_HEADER,
|
||||
"<?xml version='1.0'?>"
|
||||
@@ -98,6 +108,7 @@
|
||||
"xmlns:stream='http://etherx.jabber.org/streams' "
|
||||
"xmlns='jabber:server' "
|
||||
"xmlns:db='jabber:server:dialback' "
|
||||
"from='~s' "
|
||||
"to='~s'~s>"
|
||||
).
|
||||
|
||||
@@ -112,6 +123,8 @@
|
||||
-define(INVALID_XML_ERR,
|
||||
xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)).
|
||||
|
||||
-define(SOCKET_DEFAULT_RESULT, {error, badarg}).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -120,13 +133,13 @@ start(From, Host, Type) ->
|
||||
|
||||
start_link(From, Host, Type) ->
|
||||
p1_fsm:start_link(ejabberd_s2s_out, [From, Host, Type],
|
||||
?FSMLIMITS ++ ?FSMOPTS).
|
||||
fsm_limit_opts() ++ ?FSMOPTS).
|
||||
|
||||
start_connection(Pid) ->
|
||||
p1_fsm:send_event(Pid, init).
|
||||
|
||||
stop_connection(Pid) ->
|
||||
p1_fsm:send_event(Pid, stop).
|
||||
p1_fsm:send_event(Pid, closed).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Callback functions from p1_fsm
|
||||
@@ -142,16 +155,18 @@ stop_connection(Pid) ->
|
||||
init([From, Server, Type]) ->
|
||||
process_flag(trap_exit, true),
|
||||
?DEBUG("started: ~p", [{From, Server, Type}]),
|
||||
TLS = case ejabberd_config:get_local_option(s2s_use_starttls) of
|
||||
undefined ->
|
||||
false;
|
||||
UseStartTLS ->
|
||||
UseStartTLS
|
||||
{TLS, TLSRequired} = case ejabberd_config:get_local_option(s2s_use_starttls) of
|
||||
UseTls when (UseTls==undefined) or (UseTls==false) ->
|
||||
{false, false};
|
||||
UseTls when (UseTls==true) or (UseTls==optional) ->
|
||||
{true, false};
|
||||
UseTls when (UseTls==required) or (UseTls==required_trusted) ->
|
||||
{true, true}
|
||||
end,
|
||||
UseV10 = TLS,
|
||||
TLSOpts = case ejabberd_config:get_local_option(s2s_certfile) of
|
||||
undefined ->
|
||||
[];
|
||||
[connect];
|
||||
CertFile ->
|
||||
[{certfile, CertFile}, connect]
|
||||
end,
|
||||
@@ -165,6 +180,7 @@ init([From, Server, Type]) ->
|
||||
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
|
||||
{ok, open_socket, #state{use_v10 = UseV10,
|
||||
tls = TLS,
|
||||
tls_required = TLSRequired,
|
||||
tls_options = TLSOpts,
|
||||
queue = queue:new(),
|
||||
myname = From,
|
||||
@@ -182,7 +198,8 @@ init([From, Server, Type]) ->
|
||||
open_socket(init, StateData) ->
|
||||
log_s2s_out(StateData#state.new,
|
||||
StateData#state.myname,
|
||||
StateData#state.server),
|
||||
StateData#state.server,
|
||||
StateData#state.tls),
|
||||
?DEBUG("open_socket: ~p", [{StateData#state.myname,
|
||||
StateData#state.server,
|
||||
StateData#state.new,
|
||||
@@ -199,7 +216,7 @@ open_socket(init, StateData) ->
|
||||
_ ->
|
||||
open_socket1(Addr, Port)
|
||||
end
|
||||
end, {error, badarg}, AddrList) of
|
||||
end, ?SOCKET_DEFAULT_RESULT, AddrList) of
|
||||
{ok, Socket} ->
|
||||
Version = if
|
||||
StateData#state.use_v10 ->
|
||||
@@ -211,16 +228,27 @@ open_socket(init, StateData) ->
|
||||
tls_enabled = false,
|
||||
streamid = new_id()},
|
||||
send_text(NewStateData, io_lib:format(?STREAM_HEADER,
|
||||
[StateData#state.server,
|
||||
[StateData#state.myname, StateData#state.server,
|
||||
Version])),
|
||||
{next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT};
|
||||
{error, _Reason} ->
|
||||
?INFO_MSG("s2s connection: ~s -> ~s (remote server not found)",
|
||||
[StateData#state.myname, StateData#state.server]),
|
||||
wait_before_reconnect(StateData, 300000)
|
||||
%%{stop, normal, StateData}
|
||||
case ejabberd_hooks:run_fold(find_s2s_bridge,
|
||||
undefined,
|
||||
[StateData#state.myname,
|
||||
StateData#state.server]) of
|
||||
{Mod, Fun, Type} ->
|
||||
?INFO_MSG("found a bridge to ~s for: ~s -> ~s",
|
||||
[Type, StateData#state.myname,
|
||||
StateData#state.server]),
|
||||
NewStateData = StateData#state{bridge={Mod, Fun}},
|
||||
{next_state, relay_to_bridge, NewStateData};
|
||||
_ ->
|
||||
wait_before_reconnect(StateData)
|
||||
end
|
||||
end;
|
||||
open_socket(stop, StateData) ->
|
||||
open_socket(closed, StateData) ->
|
||||
?INFO_MSG("s2s connection: ~s -> ~s (stopped in open socket)",
|
||||
[StateData#state.myname, StateData#state.server]),
|
||||
{stop, normal, StateData};
|
||||
@@ -232,34 +260,47 @@ open_socket(_, StateData) ->
|
||||
{next_state, open_socket, StateData}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
open_socket1(Addr, Port) ->
|
||||
?DEBUG("s2s_out: connecting to ~s:~p~n", [Addr, Port]),
|
||||
Res = case catch ejabberd_socket:connect(
|
||||
Addr, Port,
|
||||
[binary, {packet, 0},
|
||||
{active, false}]) of
|
||||
{ok, _Socket} = R -> R;
|
||||
{error, Reason1} ->
|
||||
?DEBUG("s2s_out: connect return ~p~n", [Reason1]),
|
||||
catch ejabberd_socket:connect(
|
||||
Addr, Port,
|
||||
[binary, {packet, 0},
|
||||
{active, false}, inet6]);
|
||||
{'EXIT', Reason1} ->
|
||||
?DEBUG("s2s_out: connect crashed ~p~n", [Reason1]),
|
||||
catch ejabberd_socket:connect(
|
||||
Addr, Port,
|
||||
[binary, {packet, 0},
|
||||
{active, false}, inet6])
|
||||
end,
|
||||
case Res of
|
||||
{ok, Socket} ->
|
||||
{ok, Socket};
|
||||
{error, Reason} ->
|
||||
?DEBUG("s2s_out: inet6 connect return ~p~n", [Reason]),
|
||||
{error, Reason};
|
||||
%% IPv4
|
||||
open_socket1({_,_,_,_} = Addr, Port) ->
|
||||
open_socket2(inet, Addr, Port);
|
||||
|
||||
%% IPv6
|
||||
open_socket1({_,_,_,_,_,_,_,_} = Addr, Port) ->
|
||||
open_socket2(inet6, Addr, Port);
|
||||
|
||||
%% Hostname
|
||||
open_socket1(Host, Port) ->
|
||||
lists:foldl(fun(_Family, {ok, _Socket} = R) ->
|
||||
R;
|
||||
(Family, _) ->
|
||||
Addrs = get_addrs(Host, Family),
|
||||
lists:foldl(fun(_Addr, {ok, _Socket} = R) ->
|
||||
R;
|
||||
(Addr, _) ->
|
||||
open_socket1(Addr, Port)
|
||||
end, ?SOCKET_DEFAULT_RESULT, Addrs)
|
||||
end, ?SOCKET_DEFAULT_RESULT, outgoing_s2s_families()).
|
||||
|
||||
open_socket2(Type, Addr, Port) ->
|
||||
?DEBUG("s2s_out: connecting to ~p:~p~n", [Addr, Port]),
|
||||
Timeout = outgoing_s2s_timeout(),
|
||||
SockOpts = try erlang:system_info(otp_release) >= "R13B" of
|
||||
true -> [{send_timeout_close, true}];
|
||||
false -> []
|
||||
catch
|
||||
_:_ -> []
|
||||
end,
|
||||
case (catch ejabberd_socket:connect(Addr, Port,
|
||||
[binary, {packet, 0},
|
||||
{send_timeout, ?TCP_SEND_TIMEOUT},
|
||||
{active, false}, Type | SockOpts],
|
||||
Timeout)) of
|
||||
{ok, _Socket} = R -> R;
|
||||
{error, Reason} = R ->
|
||||
?DEBUG("s2s_out: connect return ~p~n", [Reason]),
|
||||
R;
|
||||
{'EXIT', Reason} ->
|
||||
?DEBUG("s2s_out: inet6 connect crashed ~p~n", [Reason]),
|
||||
?DEBUG("s2s_out: connect crashed ~p~n", [Reason]),
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
@@ -275,12 +316,18 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
{"jabber:server", "jabber:server:dialback", true} when
|
||||
StateData#state.use_v10 ->
|
||||
{next_state, wait_for_features, StateData, ?FSMTIMEOUT};
|
||||
%% Clause added to handle Tigase's workaround for an old ejabberd bug:
|
||||
{"jabber:server", "jabber:server:dialback", true} when
|
||||
not StateData#state.use_v10 ->
|
||||
send_db_request(StateData);
|
||||
{"jabber:server", "", true} when StateData#state.use_v10 ->
|
||||
{next_state, wait_for_features, StateData#state{db_enabled = false}, ?FSMTIMEOUT};
|
||||
_ ->
|
||||
{NSProvided, DB, _} ->
|
||||
send_text(StateData, ?INVALID_NAMESPACE_ERR),
|
||||
?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid namespace)",
|
||||
[StateData#state.myname, StateData#state.server]),
|
||||
?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid namespace).~n"
|
||||
"Namespace provided: ~p~nNamespace expected: \"jabber:server\"~n"
|
||||
"xmlns:db provided: ~p~nAll attributes: ~p",
|
||||
[StateData#state.myname, StateData#state.server, NSProvided, DB, Attrs]),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
|
||||
@@ -291,6 +338,11 @@ wait_for_stream({xmlstreamerror, _}, StateData) ->
|
||||
[StateData#state.myname, StateData#state.server]),
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_stream({xmlstreamend,_Name}, StateData) ->
|
||||
?INFO_MSG("Closing s2s connection: ~s -> ~s (xmlstreamend)",
|
||||
[StateData#state.myname, StateData#state.server]),
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_stream(timeout, StateData) ->
|
||||
?INFO_MSG("Closing s2s connection: ~s -> ~s (timeout in wait_for_stream)",
|
||||
[StateData#state.myname, StateData#state.server]),
|
||||
@@ -307,13 +359,21 @@ wait_for_validation({xmlstreamelement, El}, StateData) ->
|
||||
case is_verify_res(El) of
|
||||
{result, To, From, Id, Type} ->
|
||||
?DEBUG("recv result: ~p", [{From, To, Id, Type}]),
|
||||
case Type of
|
||||
"valid" ->
|
||||
case {Type, StateData#state.tls_enabled, StateData#state.tls_required} of
|
||||
{"valid", Enabled, Required} when (Enabled==true) or (Required==false) ->
|
||||
send_queue(StateData, StateData#state.queue),
|
||||
?INFO_MSG("Connection established: ~s -> ~s",
|
||||
[StateData#state.myname, StateData#state.server]),
|
||||
?INFO_MSG("Connection established: ~s -> ~s with TLS=~p",
|
||||
[StateData#state.myname, StateData#state.server, StateData#state.tls_enabled]),
|
||||
ejabberd_hooks:run(s2s_connect_hook,
|
||||
[StateData#state.myname,
|
||||
StateData#state.server]),
|
||||
{next_state, stream_established,
|
||||
StateData#state{queue = queue:new()}};
|
||||
{"valid", Enabled, Required} when (Enabled==false) and (Required==true) ->
|
||||
%% TODO: bounce packets
|
||||
?INFO_MSG("Closing s2s connection: ~s -> ~s (TLS is required but unavailable)",
|
||||
[StateData#state.myname, StateData#state.server]),
|
||||
{stop, normal, StateData};
|
||||
_ ->
|
||||
%% TODO: bounce packets
|
||||
?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid dialback key)",
|
||||
@@ -366,6 +426,14 @@ wait_for_validation({xmlstreamerror, _}, StateData) ->
|
||||
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_validation(timeout, #state{verify = {VPid, VKey, SID}} = StateData)
|
||||
when is_pid(VPid) and is_list(VKey) and is_list(SID) ->
|
||||
%% This is an auxiliary s2s connection for dialback.
|
||||
%% This timeout is normal and doesn't represent a problem.
|
||||
?DEBUG("wait_for_validation: ~s -> ~s (timeout in verify connection)",
|
||||
[StateData#state.myname, StateData#state.server]),
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_validation(timeout, StateData) ->
|
||||
?INFO_MSG("wait_for_validation: ~s -> ~s (connect timeout)",
|
||||
[StateData#state.myname, StateData#state.server]),
|
||||
@@ -420,6 +488,9 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
|
||||
send_queue(StateData, StateData#state.queue),
|
||||
?INFO_MSG("Connection established: ~s -> ~s",
|
||||
[StateData#state.myname, StateData#state.server]),
|
||||
ejabberd_hooks:run(s2s_connect_hook,
|
||||
[StateData#state.myname,
|
||||
StateData#state.server]),
|
||||
{next_state, stream_established,
|
||||
StateData#state{queue = queue:new()}};
|
||||
SASLEXT and StateData#state.try_auth and
|
||||
@@ -495,7 +566,7 @@ wait_for_auth_result({xmlstreamelement, El}, StateData) ->
|
||||
ejabberd_socket:reset_stream(StateData#state.socket),
|
||||
send_text(StateData,
|
||||
io_lib:format(?STREAM_HEADER,
|
||||
[StateData#state.server,
|
||||
[StateData#state.myname, StateData#state.server,
|
||||
" version='1.0'"])),
|
||||
{next_state, wait_for_stream,
|
||||
StateData#state{streamid = new_id(),
|
||||
@@ -563,7 +634,7 @@ wait_for_starttls_proceed({xmlstreamelement, El}, StateData) ->
|
||||
Socket = StateData#state.socket,
|
||||
TLSOpts = case ejabberd_config:get_local_option(
|
||||
{domain_certfile,
|
||||
StateData#state.server}) of
|
||||
StateData#state.myname}) of
|
||||
undefined ->
|
||||
StateData#state.tls_options;
|
||||
CertFile ->
|
||||
@@ -575,11 +646,12 @@ wait_for_starttls_proceed({xmlstreamelement, El}, StateData) ->
|
||||
TLSSocket = ejabberd_socket:starttls(Socket, TLSOpts),
|
||||
NewStateData = StateData#state{socket = TLSSocket,
|
||||
streamid = new_id(),
|
||||
tls_enabled = true
|
||||
tls_enabled = true,
|
||||
tls_options = TLSOpts
|
||||
},
|
||||
send_text(NewStateData,
|
||||
io_lib:format(?STREAM_HEADER,
|
||||
[StateData#state.server,
|
||||
[StateData#state.myname, StateData#state.server,
|
||||
" version='1.0'"])),
|
||||
{next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT};
|
||||
_ ->
|
||||
@@ -632,6 +704,15 @@ reopen_socket(closed, StateData) ->
|
||||
wait_before_retry(_Event, StateData) ->
|
||||
{next_state, wait_before_retry, StateData, ?FSMTIMEOUT}.
|
||||
|
||||
relay_to_bridge(stop, StateData) ->
|
||||
wait_before_reconnect(StateData);
|
||||
relay_to_bridge(closed, StateData) ->
|
||||
?INFO_MSG("relay to bridge: ~s -> ~s (closed)",
|
||||
[StateData#state.myname, StateData#state.server]),
|
||||
{stop, normal, StateData};
|
||||
relay_to_bridge(_Event, StateData) ->
|
||||
{next_state, relay_to_bridge, StateData}.
|
||||
|
||||
stream_established({xmlstreamelement, El}, StateData) ->
|
||||
?DEBUG("s2S stream established", []),
|
||||
case is_verify_res(El) of
|
||||
@@ -660,7 +741,7 @@ stream_established({xmlstreamelement, El}, StateData) ->
|
||||
{next_state, stream_established, StateData};
|
||||
|
||||
stream_established({xmlstreamend, _Name}, StateData) ->
|
||||
?INFO_MSG("stream established: ~s -> ~s (xmlstreamend)",
|
||||
?INFO_MSG("Connection closed in stream established: ~s -> ~s (xmlstreamend)",
|
||||
[StateData#state.myname, StateData#state.server]),
|
||||
{stop, normal, StateData};
|
||||
|
||||
@@ -705,6 +786,42 @@ stream_established(closed, StateData) ->
|
||||
handle_event(_Event, StateName, StateData) ->
|
||||
{next_state, StateName, StateData, get_timeout_interval(StateName)}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_sync_event/4
|
||||
%% Returns: The associated StateData for this connection
|
||||
%% {reply, Reply, NextStateName, NextStateData}
|
||||
%% Reply = {state_infos, [{InfoName::atom(), InfoValue::any()]
|
||||
%%----------------------------------------------------------------------
|
||||
handle_sync_event(get_state_infos, _From, StateName, StateData) ->
|
||||
{Addr,Port} = try ejabberd_socket:peername(StateData#state.socket) of
|
||||
{ok, {A,P}} -> {A,P};
|
||||
{error, _} -> {unknown,unknown}
|
||||
catch
|
||||
_:_ ->
|
||||
{unknown,unknown}
|
||||
end,
|
||||
Infos = [
|
||||
{direction, out},
|
||||
{statename, StateName},
|
||||
{addr, Addr},
|
||||
{port, Port},
|
||||
{streamid, StateData#state.streamid},
|
||||
{use_v10, StateData#state.use_v10},
|
||||
{tls, StateData#state.tls},
|
||||
{tls_required, StateData#state.tls_required},
|
||||
{tls_enabled, StateData#state.tls_enabled},
|
||||
{tls_options, StateData#state.tls_options},
|
||||
{authenticated, StateData#state.authenticated},
|
||||
{db_enabled, StateData#state.db_enabled},
|
||||
{try_auth, StateData#state.try_auth},
|
||||
{myname, StateData#state.myname},
|
||||
{server, StateData#state.server},
|
||||
{delay_to_retry, StateData#state.delay_to_retry},
|
||||
{verify, StateData#state.verify}
|
||||
],
|
||||
Reply = {state_infos, Infos},
|
||||
{reply,Reply,StateName,StateData};
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_sync_event/4
|
||||
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||
@@ -746,6 +863,19 @@ handle_info({send_element, El}, StateName, StateData) ->
|
||||
wait_before_retry ->
|
||||
bounce_element(El, ?ERR_REMOTE_SERVER_NOT_FOUND),
|
||||
{next_state, StateName, StateData};
|
||||
relay_to_bridge ->
|
||||
%% In this state we relay all outbound messages
|
||||
%% to a foreign protocol bridge such as SMTP, SIP, etc.
|
||||
{Mod, Fun} = StateData#state.bridge,
|
||||
?DEBUG("relaying stanza via ~p:~p/1", [Mod, Fun]),
|
||||
case catch Mod:Fun(El) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("Error while relaying to bridge: ~p", [Reason]),
|
||||
bounce_element(El, ?ERR_INTERNAL_SERVER_ERROR),
|
||||
wait_before_reconnect(StateData);
|
||||
_ ->
|
||||
{next_state, StateName, StateData}
|
||||
end;
|
||||
_ ->
|
||||
Q = queue:in(El, StateData#state.queue),
|
||||
{next_state, StateName, StateData#state{queue = Q},
|
||||
@@ -762,6 +892,12 @@ handle_info({timeout, Timer, _}, _StateName,
|
||||
?INFO_MSG("Closing connection with ~s: timeout", [StateData#state.server]),
|
||||
{stop, normal, StateData};
|
||||
|
||||
handle_info(terminate_if_waiting_before_retry, wait_before_retry, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
|
||||
handle_info(terminate_if_waiting_before_retry, StateName, StateData) ->
|
||||
{next_state, StateName, StateData, get_timeout_interval(StateName)};
|
||||
|
||||
handle_info(_, StateName, StateData) ->
|
||||
{next_state, StateName, StateData, get_timeout_interval(StateName)}.
|
||||
|
||||
@@ -790,6 +926,14 @@ terminate(Reason, StateName, StateData) ->
|
||||
end,
|
||||
ok.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: print_state/1
|
||||
%% Purpose: Prepare the state to be printed on error log
|
||||
%% Returns: State to print
|
||||
%%----------------------------------------------------------------------
|
||||
print_state(State) ->
|
||||
State.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -798,7 +942,7 @@ send_text(StateData, Text) ->
|
||||
ejabberd_socket:send(StateData#state.socket, Text).
|
||||
|
||||
send_element(StateData, El) ->
|
||||
send_text(StateData, xml:element_to_string(El)).
|
||||
send_text(StateData, xml:element_to_binary(El)).
|
||||
|
||||
send_queue(StateData, Q) ->
|
||||
case queue:out(Q) of
|
||||
@@ -867,30 +1011,36 @@ send_db_request(StateData) ->
|
||||
Key ->
|
||||
Key
|
||||
end,
|
||||
case New of
|
||||
false ->
|
||||
ok;
|
||||
Key1 ->
|
||||
send_element(StateData,
|
||||
{xmlelement,
|
||||
"db:result",
|
||||
[{"from", StateData#state.myname},
|
||||
{"to", Server}],
|
||||
[{xmlcdata, Key1}]})
|
||||
end,
|
||||
case StateData#state.verify of
|
||||
false ->
|
||||
ok;
|
||||
{_Pid, Key2, SID} ->
|
||||
send_element(StateData,
|
||||
{xmlelement,
|
||||
"db:verify",
|
||||
[{"from", StateData#state.myname},
|
||||
{"to", StateData#state.server},
|
||||
{"id", SID}],
|
||||
[{xmlcdata, Key2}]})
|
||||
end,
|
||||
{next_state, wait_for_validation, StateData#state{new = New}, ?FSMTIMEOUT*6}.
|
||||
NewStateData = StateData#state{new = New},
|
||||
try
|
||||
case New of
|
||||
false ->
|
||||
ok;
|
||||
Key1 ->
|
||||
send_element(StateData,
|
||||
{xmlelement,
|
||||
"db:result",
|
||||
[{"from", StateData#state.myname},
|
||||
{"to", Server}],
|
||||
[{xmlcdata, Key1}]})
|
||||
end,
|
||||
case StateData#state.verify of
|
||||
false ->
|
||||
ok;
|
||||
{_Pid, Key2, SID} ->
|
||||
send_element(StateData,
|
||||
{xmlelement,
|
||||
"db:verify",
|
||||
[{"from", StateData#state.myname},
|
||||
{"to", StateData#state.server},
|
||||
{"id", SID}],
|
||||
[{xmlcdata, Key2}]})
|
||||
end,
|
||||
{next_state, wait_for_validation, NewStateData, ?FSMTIMEOUT*6}
|
||||
catch
|
||||
_:_ ->
|
||||
{stop, normal, NewStateData}
|
||||
end.
|
||||
|
||||
|
||||
is_verify_res({xmlelement, Name, Attrs, _Els}) when Name == "db:result" ->
|
||||
@@ -915,22 +1065,17 @@ is_verify_res(_) ->
|
||||
-include_lib("kernel/include/inet.hrl").
|
||||
|
||||
get_addr_port(Server) ->
|
||||
Res = case inet_res:getbyname("_xmpp-server._tcp." ++ Server, srv) of
|
||||
{error, _Reason} ->
|
||||
inet_res:getbyname("_jabber._tcp." ++ Server, srv);
|
||||
{ok, _HEnt} = R -> R
|
||||
end,
|
||||
Res = srv_lookup(Server),
|
||||
case Res of
|
||||
{error, Reason} ->
|
||||
?DEBUG("srv lookup of '~s' failed: ~p~n", [Server, Reason]),
|
||||
[{Server, ejabberd_config:get_local_option(outgoing_s2s_port)}];
|
||||
[{Server, outgoing_s2s_port()}];
|
||||
{ok, HEnt} ->
|
||||
?DEBUG("srv lookup of '~s': ~p~n",
|
||||
[Server, HEnt#hostent.h_addr_list]),
|
||||
case HEnt#hostent.h_addr_list of
|
||||
[] ->
|
||||
[{Server,
|
||||
ejabberd_config:get_local_option(outgoing_s2s_port)}];
|
||||
[{Server, outgoing_s2s_port()}];
|
||||
AddrList ->
|
||||
%% Probabilities are not exactly proportional to weights
|
||||
%% for simplicity (higher weigths are overvalued)
|
||||
@@ -944,9 +1089,8 @@ get_addr_port(Server) ->
|
||||
end,
|
||||
{Priority * 65536 - N, Host, Port}
|
||||
end, AddrList)) of
|
||||
{'EXIT', _Reasn} ->
|
||||
[{Server,
|
||||
ejabberd_config:get_local_option(outgoing_s2s_port)}];
|
||||
{'EXIT', _Reason} ->
|
||||
[{Server, outgoing_s2s_port()}];
|
||||
SortedList ->
|
||||
List = lists:map(
|
||||
fun({_, Host, Port}) ->
|
||||
@@ -958,6 +1102,36 @@ get_addr_port(Server) ->
|
||||
end
|
||||
end.
|
||||
|
||||
srv_lookup(Server) ->
|
||||
Options = case ejabberd_config:get_local_option(s2s_dns_options) of
|
||||
L when is_list(L) -> L;
|
||||
_ -> []
|
||||
end,
|
||||
TimeoutMs = timer:seconds(proplists:get_value(timeout, Options, 10)),
|
||||
Retries = proplists:get_value(retries, Options, 2),
|
||||
srv_lookup(Server, TimeoutMs, Retries).
|
||||
|
||||
%% XXX - this behaviour is suboptimal in the case that the domain
|
||||
%% has a "_xmpp-server._tcp." but not a "_jabber._tcp." record and
|
||||
%% we don't get a DNS reply for the "_xmpp-server._tcp." lookup. In this
|
||||
%% case we'll give up when we get the "_jabber._tcp." nxdomain reply.
|
||||
srv_lookup(_Server, _Timeout, Retries) when Retries < 1 ->
|
||||
{error, timeout};
|
||||
srv_lookup(Server, Timeout, Retries) ->
|
||||
case inet_res:getbyname("_xmpp-server._tcp." ++ Server, srv, Timeout) of
|
||||
{error, _Reason} ->
|
||||
case inet_res:getbyname("_jabber._tcp." ++ Server, srv, Timeout) of
|
||||
{error, timeout} ->
|
||||
?ERROR_MSG("The DNS servers~n ~p~ntimed out on request"
|
||||
" for ~p IN SRV."
|
||||
" You should check your DNS configuration.",
|
||||
[inet_db:res_option(nameserver), Server]),
|
||||
srv_lookup(Server, Timeout, Retries - 1);
|
||||
R -> R
|
||||
end;
|
||||
{ok, _HEnt} = R -> R
|
||||
end.
|
||||
|
||||
test_get_addr_port(Server) ->
|
||||
lists:foldl(
|
||||
fun(_, Acc) ->
|
||||
@@ -970,15 +1144,69 @@ test_get_addr_port(Server) ->
|
||||
end
|
||||
end, [], lists:seq(1, 100000)).
|
||||
|
||||
get_addrs(Host, Family) ->
|
||||
Type = case Family of
|
||||
inet4 -> inet;
|
||||
ipv4 -> inet;
|
||||
inet6 -> inet6;
|
||||
ipv6 -> inet6
|
||||
end,
|
||||
case inet:gethostbyname(Host, Type) of
|
||||
{ok, #hostent{h_addr_list = Addrs}} ->
|
||||
?DEBUG("~s of ~s resolved to: ~p~n", [Type, Host, Addrs]),
|
||||
Addrs;
|
||||
{error, Reason} ->
|
||||
?DEBUG("~s lookup of '~s' failed: ~p~n", [Type, Host, Reason]),
|
||||
[]
|
||||
end.
|
||||
|
||||
|
||||
outgoing_s2s_port() ->
|
||||
case ejabberd_config:get_local_option(outgoing_s2s_port) of
|
||||
Port when is_integer(Port) ->
|
||||
Port;
|
||||
undefined ->
|
||||
5269
|
||||
end.
|
||||
|
||||
outgoing_s2s_families() ->
|
||||
case ejabberd_config:get_local_option(outgoing_s2s_options) of
|
||||
{Families, _} when is_list(Families) ->
|
||||
Families;
|
||||
undefined ->
|
||||
%% DISCUSSION: Why prefer IPv4 first?
|
||||
%%
|
||||
%% IPv4 connectivity will be available for everyone for
|
||||
%% many years to come. So, there's absolutely no benefit
|
||||
%% in preferring IPv6 connections which are flaky at best
|
||||
%% nowadays.
|
||||
%%
|
||||
%% On the other hand content providers hesitate putting up
|
||||
%% AAAA records for their sites due to the mentioned
|
||||
%% quality of current IPv6 connectivity. Making IPv6 the a
|
||||
%% `fallback' may avoid these problems elegantly.
|
||||
[ipv4, ipv6]
|
||||
end.
|
||||
|
||||
outgoing_s2s_timeout() ->
|
||||
case ejabberd_config:get_local_option(outgoing_s2s_options) of
|
||||
{_, Timeout} when is_integer(Timeout) ->
|
||||
Timeout;
|
||||
{_, infinity} ->
|
||||
infinity;
|
||||
undefined ->
|
||||
%% 10 seconds
|
||||
10000
|
||||
end.
|
||||
|
||||
%% Human readable S2S logging: Log only new outgoing connections as INFO
|
||||
%% Do not log dialback
|
||||
log_s2s_out(false, _, _) -> ok;
|
||||
log_s2s_out(false, _, _, _) -> ok;
|
||||
%% Log new outgoing connections:
|
||||
log_s2s_out(_, Myname, Server) ->
|
||||
?INFO_MSG("Trying to open s2s connection: ~s -> ~s",[Myname, Server]).
|
||||
log_s2s_out(_, Myname, Server, Tls) ->
|
||||
?INFO_MSG("Trying to open s2s connection: ~s -> ~s with TLS=~p", [Myname, Server, Tls]).
|
||||
|
||||
%% Calcultate timeout depending on which state we are in:
|
||||
%% Calculate timeout depending on which state we are in:
|
||||
%% Can return integer > 0 | infinity
|
||||
get_timeout_interval(StateName) ->
|
||||
case StateName of
|
||||
@@ -994,11 +1222,53 @@ get_timeout_interval(StateName) ->
|
||||
|
||||
%% This function is intended to be called at the end of a state
|
||||
%% function that want to wait for a reconnect delay before stopping.
|
||||
wait_before_reconnect(StateData, Delay) ->
|
||||
wait_before_reconnect(StateData) ->
|
||||
%% bounce queue manage by process and Erlang message queue
|
||||
bounce_queue(StateData#state.queue, ?ERR_REMOTE_SERVER_NOT_FOUND),
|
||||
bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND),
|
||||
cancel_timer(StateData#state.timer),
|
||||
Delay = case StateData#state.delay_to_retry of
|
||||
undefined_delay ->
|
||||
%% The initial delay is random between 1 and 15 seconds
|
||||
%% Return a random integer between 1000 and 15000
|
||||
{_, _, MicroSecs} = now(),
|
||||
(MicroSecs rem 14000) + 1000;
|
||||
D1 ->
|
||||
%% Duplicate the delay with each successive failed
|
||||
%% reconnection attempt, but don't exceed the max
|
||||
lists:min([D1 * 2, get_max_retry_delay()])
|
||||
end,
|
||||
Timer = erlang:start_timer(Delay, self(), []),
|
||||
{next_state, wait_before_retry, StateData#state{timer=Timer,
|
||||
delay_to_retry = Delay,
|
||||
queue = queue:new()}}.
|
||||
|
||||
%% @doc Get the maximum allowed delay for retry to reconnect (in miliseconds).
|
||||
%% The default value is 5 minutes.
|
||||
%% The option {s2s_max_retry_delay, Seconds} can be used (in seconds).
|
||||
%% @spec () -> integer()
|
||||
get_max_retry_delay() ->
|
||||
case ejabberd_config:get_local_option(s2s_max_retry_delay) of
|
||||
Seconds when is_integer(Seconds) ->
|
||||
Seconds*1000;
|
||||
_ ->
|
||||
?MAX_RETRY_DELAY
|
||||
end.
|
||||
|
||||
%% Terminate s2s_out connections that are in state wait_before_retry
|
||||
terminate_if_waiting_delay(From, To) ->
|
||||
FromTo = {From, To},
|
||||
Pids = ejabberd_s2s:get_connections_pids(FromTo),
|
||||
lists:foreach(
|
||||
fun(Pid) ->
|
||||
Pid ! terminate_if_waiting_before_retry
|
||||
end,
|
||||
Pids).
|
||||
|
||||
fsm_limit_opts() ->
|
||||
case ejabberd_config:get_local_option(max_fsm_queue) of
|
||||
N when is_integer(N) ->
|
||||
[{max_queue, N}];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_service.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : External component management
|
||||
%%% Purpose : External component management (XEP-0114)
|
||||
%%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -27,7 +27,9 @@
|
||||
-module(ejabberd_service).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
-define(GEN_FSM, p1_fsm).
|
||||
|
||||
-behaviour(?GEN_FSM).
|
||||
|
||||
%% External exports
|
||||
-export([start/2,
|
||||
@@ -45,7 +47,8 @@
|
||||
handle_sync_event/4,
|
||||
code_change/4,
|
||||
handle_info/3,
|
||||
terminate/3]).
|
||||
terminate/3,
|
||||
print_state/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
@@ -73,13 +76,18 @@
|
||||
-define(STREAM_TRAILER, "</stream:stream>").
|
||||
|
||||
-define(INVALID_HEADER_ERR,
|
||||
"<stream:stream>"
|
||||
"<stream:stream "
|
||||
"xmlns:stream='http://etherx.jabber.org/streams'>"
|
||||
"<stream:error>Invalid Stream Header</stream:error>"
|
||||
"</stream:stream>"
|
||||
).
|
||||
|
||||
-define(INVALID_HANDSHAKE_ERR,
|
||||
"<stream:error>Invalid Handshake</stream:error>"
|
||||
"<stream:error>"
|
||||
"<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>"
|
||||
"<text xmlns='urn:ietf:params:xml:ns:xmpp-streams' xml:lang='en'>"
|
||||
"Invalid Handshake</text>"
|
||||
"</stream:error>"
|
||||
"</stream:stream>"
|
||||
).
|
||||
|
||||
@@ -95,7 +103,8 @@ start(SockData, Opts) ->
|
||||
supervisor:start_child(ejabberd_service_sup, [SockData, Opts]).
|
||||
|
||||
start_link(SockData, Opts) ->
|
||||
gen_fsm:start_link(ejabberd_service, [SockData, Opts], ?FSMOPTS).
|
||||
?GEN_FSM:start_link(ejabberd_service, [SockData, Opts],
|
||||
fsm_limit_opts(Opts) ++ ?FSMOPTS).
|
||||
|
||||
socket_type() ->
|
||||
xml_stream.
|
||||
@@ -168,11 +177,15 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
%%----------------------------------------------------------------------
|
||||
|
||||
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
% TODO
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
"jabber:component:accept" ->
|
||||
%% Note: XEP-0114 requires to check that destination is a Jabber
|
||||
%% component served by this Jabber server.
|
||||
%% However several transports don't respect that,
|
||||
%% so ejabberd doesn't check 'to' attribute (EJAB-717)
|
||||
To = xml:get_attr_s("to", Attrs),
|
||||
Header = io_lib:format(?STREAM_HEADER,
|
||||
[StateData#state.streamid, ?MYNAME]),
|
||||
[StateData#state.streamid, xml:crypt(To)]),
|
||||
send_text(StateData, Header),
|
||||
{next_state, wait_for_handshake, StateData};
|
||||
_ ->
|
||||
@@ -334,12 +347,15 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
|
||||
Attrs2 = jlib:replace_from_to_attrs(jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To),
|
||||
Attrs),
|
||||
Text = xml:element_to_string({xmlelement, Name, Attrs2, Els}),
|
||||
Text = xml:element_to_binary({xmlelement, Name, Attrs2, Els}),
|
||||
send_text(StateData, Text);
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
ejabberd_router:route_error(To, From, Err, Packet)
|
||||
end,
|
||||
{next_state, StateName, StateData};
|
||||
handle_info(Info, StateName, StateData) ->
|
||||
?ERROR_MSG("Unexpected info: ~p", [Info]),
|
||||
{next_state, StateName, StateData}.
|
||||
|
||||
|
||||
@@ -362,6 +378,14 @@ terminate(Reason, StateName, StateData) ->
|
||||
(StateData#state.sockmod):close(StateData#state.socket),
|
||||
ok.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: print_state/1
|
||||
%% Purpose: Prepare the state to be printed on error log
|
||||
%% Returns: State to print
|
||||
%%----------------------------------------------------------------------
|
||||
print_state(State) ->
|
||||
State.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -370,7 +394,20 @@ send_text(StateData, Text) ->
|
||||
(StateData#state.sockmod):send(StateData#state.socket, Text).
|
||||
|
||||
send_element(StateData, El) ->
|
||||
send_text(StateData, xml:element_to_string(El)).
|
||||
send_text(StateData, xml:element_to_binary(El)).
|
||||
|
||||
new_id() ->
|
||||
randoms:get_string().
|
||||
|
||||
fsm_limit_opts(Opts) ->
|
||||
case lists:keysearch(max_fsm_queue, 1, Opts) of
|
||||
{value, {_, N}} when is_integer(N) ->
|
||||
[{max_queue, N}];
|
||||
_ ->
|
||||
case ejabberd_config:get_local_option(max_fsm_queue) of
|
||||
N when is_integer(N) ->
|
||||
[{max_queue, N}];
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 24 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -43,11 +43,16 @@
|
||||
dirty_get_sessions_list/0,
|
||||
dirty_get_my_sessions_list/0,
|
||||
get_vh_session_list/1,
|
||||
get_vh_session_number/1,
|
||||
register_iq_handler/4,
|
||||
register_iq_handler/5,
|
||||
unregister_iq_handler/2,
|
||||
ctl_process/2,
|
||||
force_update_presence/1,
|
||||
connected_users/0,
|
||||
connected_users_number/0,
|
||||
user_resources/2,
|
||||
get_session_pid/3,
|
||||
get_user_info/3,
|
||||
get_user_ip/3
|
||||
]).
|
||||
|
||||
@@ -57,9 +62,11 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd_ctl.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
-record(session, {sid, usr, us, priority, info}).
|
||||
-record(session_counter, {vhost, count}).
|
||||
-record(state, {}).
|
||||
|
||||
%% default value for the maximum number of user connections
|
||||
@@ -84,21 +91,29 @@ route(From, To, Packet) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
open_session(SID, User, Server, Resource, IP) ->
|
||||
set_session(SID, User, Server, Resource, undefined, IP),
|
||||
open_session(SID, User, Server, Resource, Info) ->
|
||||
set_session(SID, User, Server, Resource, undefined, Info),
|
||||
mnesia:dirty_update_counter(session_counter,
|
||||
jlib:nameprep(Server), 1),
|
||||
check_for_sessions_to_replace(User, Server, Resource),
|
||||
JID = jlib:make_jid(User, Server, Resource),
|
||||
ejabberd_hooks:run(sm_register_connection_hook, JID#jid.lserver,
|
||||
[SID, JID]).
|
||||
[SID, JID, Info]).
|
||||
|
||||
close_session(SID, User, Server, Resource) ->
|
||||
Info = case mnesia:dirty_read({session, SID}) of
|
||||
[] -> [];
|
||||
[#session{info=I}] -> I
|
||||
end,
|
||||
F = fun() ->
|
||||
mnesia:delete({session, SID})
|
||||
mnesia:delete({session, SID}),
|
||||
mnesia:dirty_update_counter(session_counter,
|
||||
jlib:nameprep(Server), -1)
|
||||
end,
|
||||
mnesia:sync_dirty(F),
|
||||
JID = jlib:make_jid(User, Server, Resource),
|
||||
ejabberd_hooks:run(sm_remove_connection_hook, JID#jid.lserver,
|
||||
[SID, JID]).
|
||||
[SID, JID, Info]).
|
||||
|
||||
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
|
||||
case ejabberd_auth:is_user_exists(User, Server) of
|
||||
@@ -143,14 +158,29 @@ get_user_ip(User, Server, Resource) ->
|
||||
proplists:get_value(ip, Session#session.info)
|
||||
end.
|
||||
|
||||
get_user_info(User, Server, Resource) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
LResource = jlib:resourceprep(Resource),
|
||||
USR = {LUser, LServer, LResource},
|
||||
case mnesia:dirty_index_read(session, USR, #session.usr) of
|
||||
[] ->
|
||||
offline;
|
||||
Ss ->
|
||||
Session = lists:max(Ss),
|
||||
Node = node(element(2, Session#session.sid)),
|
||||
Conn = proplists:get_value(conn, Session#session.info),
|
||||
IP = proplists:get_value(ip, Session#session.info),
|
||||
[{node, Node}, {conn, Conn}, {ip, IP}]
|
||||
end.
|
||||
|
||||
set_presence(SID, User, Server, Resource, Priority, Presence, IP) ->
|
||||
set_session(SID, User, Server, Resource, Priority, IP),
|
||||
set_presence(SID, User, Server, Resource, Priority, Presence, Info) ->
|
||||
set_session(SID, User, Server, Resource, Priority, Info),
|
||||
ejabberd_hooks:run(set_presence_hook, jlib:nameprep(Server),
|
||||
[User, Server, Resource, Presence]).
|
||||
|
||||
unset_presence(SID, User, Server, Resource, Status, IP) ->
|
||||
set_session(SID, User, Server, Resource, undefined, IP),
|
||||
unset_presence(SID, User, Server, Resource, Status, Info) ->
|
||||
set_session(SID, User, Server, Resource, undefined, Info),
|
||||
ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server),
|
||||
[User, Server, Resource, Status]).
|
||||
|
||||
@@ -191,6 +221,19 @@ get_vh_session_list(Server) ->
|
||||
[{'==', {element, 2, '$1'}, LServer}],
|
||||
['$1']}]).
|
||||
|
||||
get_vh_session_number(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
Query = mnesia:dirty_select(
|
||||
session_counter,
|
||||
[{#session_counter{vhost = LServer, count = '$1'},
|
||||
[],
|
||||
['$1']}]),
|
||||
case Query of
|
||||
[Count] ->
|
||||
Count;
|
||||
_ -> 0
|
||||
end.
|
||||
|
||||
register_iq_handler(Host, XMLNS, Module, Fun) ->
|
||||
ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun}.
|
||||
|
||||
@@ -217,9 +260,13 @@ init([]) ->
|
||||
mnesia:create_table(session,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, session)}]),
|
||||
mnesia:create_table(session_counter,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, session_counter)}]),
|
||||
mnesia:add_table_index(session, usr),
|
||||
mnesia:add_table_index(session, us),
|
||||
mnesia:add_table_copy(session, node(), ram_copies),
|
||||
mnesia:add_table_copy(session_counter, node(), ram_copies),
|
||||
mnesia:subscribe(system),
|
||||
ets:new(sm_iqtable, [named_table]),
|
||||
lists:foreach(
|
||||
@@ -231,11 +278,7 @@ init([]) ->
|
||||
ejabberd_hooks:add(remove_user, Host,
|
||||
ejabberd_sm, disconnect_removed_user, 100)
|
||||
end, ?MYHOSTS),
|
||||
ejabberd_ctl:register_commands(
|
||||
[{"connected-users", "list all established sessions"},
|
||||
{"connected-users-number", "print a number of established sessions"},
|
||||
{"user-resources user server", "print user's connected resources"}],
|
||||
?MODULE, ctl_process),
|
||||
ejabberd_commands:register_commands(commands()),
|
||||
|
||||
{ok, #state{}}.
|
||||
|
||||
@@ -277,7 +320,7 @@ handle_info({route, From, To, Packet}, State) ->
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
|
||||
clean_table_from_bad_node(Node),
|
||||
recount_session_table(Node),
|
||||
{noreply, State};
|
||||
handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
|
||||
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function}),
|
||||
@@ -305,6 +348,7 @@ handle_info(_Info, State) ->
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ejabberd_commands:unregister_commands(commands()),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -318,13 +362,12 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
set_session(SID, User, Server, Resource, Priority, IP) ->
|
||||
set_session(SID, User, Server, Resource, Priority, Info) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
LResource = jlib:resourceprep(Resource),
|
||||
US = {LUser, LServer},
|
||||
USR = {LUser, LServer, LResource},
|
||||
Info = [{ip, IP}],
|
||||
F = fun() ->
|
||||
mnesia:write(#session{sid = SID,
|
||||
usr = USR,
|
||||
@@ -334,7 +377,9 @@ set_session(SID, User, Server, Resource, Priority, IP) ->
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
clean_table_from_bad_node(Node) ->
|
||||
%% Recalculates alive sessions when Node goes down
|
||||
%% and updates session and session_counter tables
|
||||
recount_session_table(Node) ->
|
||||
F = fun() ->
|
||||
Es = mnesia:select(
|
||||
session,
|
||||
@@ -343,13 +388,24 @@ clean_table_from_bad_node(Node) ->
|
||||
['$_']}]),
|
||||
lists:foreach(fun(E) ->
|
||||
mnesia:delete({session, E#session.sid})
|
||||
end, Es)
|
||||
end, Es),
|
||||
%% reset session_counter table with active sessions
|
||||
mnesia:clear_table(session_counter),
|
||||
lists:foreach(fun(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
Hs = mnesia:select(session,
|
||||
[{#session{usr = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, LServer}],
|
||||
['$1']}]),
|
||||
mnesia:write(
|
||||
#session_counter{vhost = LServer,
|
||||
count = length(Hs)})
|
||||
end, ?MYHOSTS)
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
mnesia:async_dirty(F).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
|
||||
do_route(From, To, Packet) ->
|
||||
?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
|
||||
[From, To, Packet, 8]),
|
||||
@@ -366,28 +422,32 @@ do_route(From, To, Packet) ->
|
||||
Reason = xml:get_path_s(
|
||||
Packet,
|
||||
[{elem, "status"}, cdata]),
|
||||
{ejabberd_hooks:run_fold(
|
||||
{is_privacy_allow(From, To, Packet) andalso
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server, From, subscribe, Reason]),
|
||||
true};
|
||||
"subscribed" ->
|
||||
{ejabberd_hooks:run_fold(
|
||||
{is_privacy_allow(From, To, Packet) andalso
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server, From, subscribed, ""]),
|
||||
true};
|
||||
"unsubscribe" ->
|
||||
{ejabberd_hooks:run_fold(
|
||||
{is_privacy_allow(From, To, Packet) andalso
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server, From, unsubscribe, ""]),
|
||||
true};
|
||||
"unsubscribed" ->
|
||||
{ejabberd_hooks:run_fold(
|
||||
{is_privacy_allow(From, To, Packet) andalso
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
@@ -437,7 +497,7 @@ do_route(From, To, Packet) ->
|
||||
_ ->
|
||||
Err =
|
||||
jlib:make_error_reply(
|
||||
Packet, ?ERR_RECIPIENT_UNAVAILABLE),
|
||||
Packet, ?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
_ ->
|
||||
@@ -451,6 +511,31 @@ do_route(From, To, Packet) ->
|
||||
end
|
||||
end.
|
||||
|
||||
%% The default list applies to the user as a whole,
|
||||
%% and is processed if there is no active list set
|
||||
%% for the target session/resource to which a stanza is addressed,
|
||||
%% or if there are no current sessions for the user.
|
||||
is_privacy_allow(From, To, Packet) ->
|
||||
User = To#jid.user,
|
||||
Server = To#jid.server,
|
||||
PrivacyList = ejabberd_hooks:run_fold(privacy_get_user_list, Server,
|
||||
#userlist{}, [User, Server]),
|
||||
is_privacy_allow(From, To, Packet, PrivacyList).
|
||||
|
||||
%% Check if privacy rules allow this delivery
|
||||
%% Function copied from ejabberd_c2s.erl
|
||||
is_privacy_allow(From, To, Packet, PrivacyList) ->
|
||||
User = To#jid.user,
|
||||
Server = To#jid.server,
|
||||
allow == ejabberd_hooks:run_fold(
|
||||
privacy_check_packet, Server,
|
||||
allow,
|
||||
[User,
|
||||
Server,
|
||||
PrivacyList,
|
||||
{From, To, Packet},
|
||||
in]).
|
||||
|
||||
route_message(From, To, Packet) ->
|
||||
LUser = To#jid.luser,
|
||||
LServer = To#jid.lserver,
|
||||
@@ -488,9 +573,14 @@ route_message(From, To, Packet) ->
|
||||
_ ->
|
||||
case ejabberd_auth:is_user_exists(LUser, LServer) of
|
||||
true ->
|
||||
ejabberd_hooks:run(offline_message_hook,
|
||||
LServer,
|
||||
[From, To, Packet]);
|
||||
case is_privacy_allow(From, To, Packet) of
|
||||
true ->
|
||||
ejabberd_hooks:run(offline_message_hook,
|
||||
LServer,
|
||||
[From, To, Packet]);
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_SERVICE_UNAVAILABLE),
|
||||
@@ -594,7 +684,6 @@ get_max_user_sessions(LUser, Host) ->
|
||||
_ -> ?MAX_USER_SESSIONS
|
||||
end.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
process_iq(From, To, Packet) ->
|
||||
@@ -628,28 +717,57 @@ process_iq(From, To, Packet) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
force_update_presence({LUser, _LServer} = US) ->
|
||||
case catch mnesia:dirty_index_read(session, US, #session.us) of
|
||||
{'EXIT', _Reason} ->
|
||||
ok;
|
||||
Ss ->
|
||||
lists:foreach(fun(#session{sid = {_, Pid}}) ->
|
||||
Pid ! {force_update_presence, LUser}
|
||||
end, Ss)
|
||||
end.
|
||||
|
||||
ctl_process(_Val, ["connected-users"]) ->
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% ejabberd commands
|
||||
|
||||
commands() ->
|
||||
[
|
||||
#ejabberd_commands{name = connected_users,
|
||||
tags = [session],
|
||||
desc = "List all established sessions",
|
||||
module = ?MODULE, function = connected_users,
|
||||
args = [],
|
||||
result = {connected_users, {list, {sessions, string}}}},
|
||||
#ejabberd_commands{name = connected_users_number,
|
||||
tags = [session, stats],
|
||||
desc = "Get the number of established sessions",
|
||||
module = ?MODULE, function = connected_users_number,
|
||||
args = [],
|
||||
result = {num_sessions, integer}},
|
||||
#ejabberd_commands{name = user_resources,
|
||||
tags = [session],
|
||||
desc = "List user's connected resources",
|
||||
module = ?MODULE, function = user_resources,
|
||||
args = [{user, string}, {host, string}],
|
||||
result = {resources, {list, {resource, string}}}}
|
||||
].
|
||||
|
||||
connected_users() ->
|
||||
USRs = dirty_get_sessions_list(),
|
||||
NewLine = io_lib:format("~n", []),
|
||||
SUSRs = lists:sort(USRs),
|
||||
FUSRs = lists:map(fun({U, S, R}) -> [U, $@, S, $/, R, NewLine] end, SUSRs),
|
||||
io:format("~s", [FUSRs]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(_Val, ["connected-users-number"]) ->
|
||||
N = length(dirty_get_sessions_list()),
|
||||
io:format("~p~n", [N]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(_Val, ["user-resources", User, Server]) ->
|
||||
Resources = get_user_resources(User, Server),
|
||||
NewLine = io_lib:format("~n", []),
|
||||
SResources = lists:sort(Resources),
|
||||
FResources = lists:map(fun(R) -> [R, NewLine] end, SResources),
|
||||
io:format("~s", [FResources]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(Val, _Args) ->
|
||||
Val.
|
||||
lists:map(fun({U, S, R}) -> [U, $@, S, $/, R] end, SUSRs).
|
||||
|
||||
connected_users_number() ->
|
||||
length(dirty_get_sessions_list()).
|
||||
|
||||
user_resources(User, Server) ->
|
||||
Resources = get_user_resources(User, Server),
|
||||
lists:sort(Resources).
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% Update Mnesia tables
|
||||
|
||||
update_tables() ->
|
||||
case catch mnesia:table_info(session, attributes) of
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 23 Aug 2006 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2007 Process-one
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -16,7 +16,7 @@
|
||||
%%% 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
|
||||
@@ -30,12 +30,14 @@
|
||||
%% API
|
||||
-export([start/4,
|
||||
connect/3,
|
||||
connect/4,
|
||||
starttls/2,
|
||||
starttls/3,
|
||||
compress/1,
|
||||
compress/2,
|
||||
reset_stream/1,
|
||||
send/2,
|
||||
send_xml/2,
|
||||
change_shaper/2,
|
||||
monitor/1,
|
||||
get_sockmod/1,
|
||||
@@ -44,13 +46,15 @@
|
||||
close/1,
|
||||
sockname/1, peername/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(socket_state, {sockmod, socket, receiver}).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function:
|
||||
%% Function:
|
||||
%% Description:
|
||||
%%--------------------------------------------------------------------
|
||||
start(Module, SockMod, Socket, Opts) ->
|
||||
@@ -61,30 +65,57 @@ start(Module, SockMod, Socket, Opts) ->
|
||||
{value, {_, Size}} -> Size;
|
||||
_ -> infinity
|
||||
end,
|
||||
Receiver = ejabberd_receiver:start(Socket, SockMod, none, MaxStanzaSize),
|
||||
{ReceiverMod, Receiver, RecRef} =
|
||||
case catch SockMod:custom_receiver(Socket) of
|
||||
{receiver, RecMod, RecPid} ->
|
||||
{RecMod, RecPid, RecMod};
|
||||
_ ->
|
||||
RecPid = ejabberd_receiver:start(
|
||||
Socket, SockMod, none, MaxStanzaSize),
|
||||
{ejabberd_receiver, RecPid, RecPid}
|
||||
end,
|
||||
SocketData = #socket_state{sockmod = SockMod,
|
||||
socket = Socket,
|
||||
receiver = Receiver},
|
||||
{ok, Pid} = Module:start({?MODULE, SocketData}, Opts),
|
||||
case SockMod:controlling_process(Socket, Receiver) of
|
||||
ok ->
|
||||
ok;
|
||||
receiver = RecRef},
|
||||
case Module:start({?MODULE, SocketData}, Opts) of
|
||||
{ok, Pid} ->
|
||||
case SockMod:controlling_process(Socket, Receiver) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, _Reason} ->
|
||||
SockMod:close(Socket)
|
||||
end,
|
||||
ReceiverMod:become_controller(Receiver, Pid);
|
||||
{error, _Reason} ->
|
||||
SockMod:close(Socket)
|
||||
end,
|
||||
ejabberd_receiver:become_controller(Receiver, Pid);
|
||||
SockMod:close(Socket),
|
||||
case ReceiverMod of
|
||||
ejabberd_receiver ->
|
||||
ReceiverMod:close(Receiver);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end;
|
||||
independent ->
|
||||
ok;
|
||||
raw ->
|
||||
{ok, Pid} = Module:start({SockMod, Socket}, Opts),
|
||||
case SockMod:controlling_process(Socket, Pid) of
|
||||
ok ->
|
||||
ok;
|
||||
case Module:start({SockMod, Socket}, Opts) of
|
||||
{ok, Pid} ->
|
||||
case SockMod:controlling_process(Socket, Pid) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, _Reason} ->
|
||||
SockMod:close(Socket)
|
||||
end;
|
||||
{error, _Reason} ->
|
||||
SockMod:close(Socket)
|
||||
end
|
||||
end.
|
||||
|
||||
connect(Addr, Port, Opts) ->
|
||||
case gen_tcp:connect(Addr, Port, Opts) of
|
||||
connect(Addr, Port, Opts, infinity).
|
||||
|
||||
connect(Addr, Port, Opts, Timeout) ->
|
||||
case gen_tcp:connect(Addr, Port, Opts, Timeout) of
|
||||
{ok, Socket} ->
|
||||
Receiver = ejabberd_receiver:start(Socket, gen_tcp, none),
|
||||
SocketData = #socket_state{sockmod = gen_tcp,
|
||||
@@ -129,18 +160,45 @@ compress(SocketData, Data) ->
|
||||
send(SocketData, Data),
|
||||
SocketData#socket_state{socket = ZlibSocket, sockmod = ejabberd_zlib}.
|
||||
|
||||
reset_stream(SocketData) ->
|
||||
ejabberd_receiver:reset_stream(SocketData#socket_state.receiver).
|
||||
reset_stream(SocketData) when is_pid(SocketData#socket_state.receiver) ->
|
||||
ejabberd_receiver:reset_stream(SocketData#socket_state.receiver);
|
||||
reset_stream(SocketData) when is_atom(SocketData#socket_state.receiver) ->
|
||||
(SocketData#socket_state.receiver):reset_stream(
|
||||
SocketData#socket_state.socket).
|
||||
|
||||
%% sockmod=gen_tcp|tls|ejabberd_zlib
|
||||
send(SocketData, Data) ->
|
||||
catch (SocketData#socket_state.sockmod):send(
|
||||
case catch (SocketData#socket_state.sockmod):send(
|
||||
SocketData#socket_state.socket, Data) of
|
||||
ok -> ok;
|
||||
{error, timeout} ->
|
||||
?INFO_MSG("Timeout on ~p:send",[SocketData#socket_state.sockmod]),
|
||||
exit(normal);
|
||||
Error ->
|
||||
?DEBUG("Error in ~p:send: ~p",[SocketData#socket_state.sockmod, Error]),
|
||||
exit(normal)
|
||||
end.
|
||||
|
||||
%% Can only be called when in c2s StateData#state.xml_socket is true
|
||||
%% This function is used for HTTP bind
|
||||
%% sockmod=ejabberd_http_poll|ejabberd_http_bind or any custom module
|
||||
send_xml(SocketData, Data) ->
|
||||
catch (SocketData#socket_state.sockmod):send_xml(
|
||||
SocketData#socket_state.socket, Data).
|
||||
|
||||
change_shaper(SocketData, Shaper) ->
|
||||
ejabberd_receiver:change_shaper(SocketData#socket_state.receiver, Shaper).
|
||||
change_shaper(SocketData, Shaper)
|
||||
when is_pid(SocketData#socket_state.receiver) ->
|
||||
ejabberd_receiver:change_shaper(SocketData#socket_state.receiver, Shaper);
|
||||
change_shaper(SocketData, Shaper)
|
||||
when is_atom(SocketData#socket_state.receiver) ->
|
||||
(SocketData#socket_state.receiver):change_shaper(
|
||||
SocketData#socket_state.socket, Shaper).
|
||||
|
||||
monitor(SocketData) ->
|
||||
erlang:monitor(process, SocketData#socket_state.receiver).
|
||||
monitor(SocketData) when is_pid(SocketData#socket_state.receiver) ->
|
||||
erlang:monitor(process, SocketData#socket_state.receiver);
|
||||
monitor(SocketData) when is_atom(SocketData#socket_state.receiver) ->
|
||||
(SocketData#socket_state.receiver):monitor(
|
||||
SocketData#socket_state.socket).
|
||||
|
||||
get_sockmod(SocketData) ->
|
||||
SocketData#socket_state.sockmod.
|
||||
|
||||