Compare commits
827 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5feeacf875 | |||
| 90954cb248 | |||
| 8220db189f | |||
| 45db79b9a7 | |||
| 870edd13e5 | |||
| 6d811f5178 | |||
| 9ca775e199 | |||
| 0b96b745bf | |||
| e85f7566dd | |||
| bc8264b2ac | |||
| 371f1f200e | |||
| c058687bf9 | |||
| aa47033fa1 | |||
| 3f93931409 | |||
| 426f1107c5 | |||
| 2704378d43 | |||
| e500fd74ad | |||
| e7f85411fe | |||
| 15073aafa5 | |||
| 0ae400533c | |||
| d47dee8009 | |||
| d5506a3ff0 | |||
| d81eb95cab | |||
| 1f290c8a29 | |||
| be33ab890e | |||
| 2c26926689 | |||
| 9208f4dd50 | |||
| dfcdf1ca7a | |||
| edbb30babd | |||
| cdb2ebe4bd | |||
| ec51ba7bc6 | |||
| 625e801431 | |||
| 2dc2769c92 | |||
| 8a737f875f | |||
| 4147081f0b | |||
| a47fd96cc8 | |||
| 4d54bce621 | |||
| 5e7a1c8829 | |||
| bc65b5008e | |||
| a407382d9f | |||
| d2d51381ec | |||
| e06c1c49c1 | |||
| 0cc920579c | |||
| efc0b0ba5a | |||
| 4e6eff3f1a | |||
| 2bf8125abf | |||
| 75756f0fc9 | |||
| bc077266d9 | |||
| a99c1e7031 | |||
| bb026ef598 | |||
| e5d5ea49be | |||
| 6c94d040fa | |||
| e8921d79ba | |||
| 6719d9669b | |||
| 8b13226d00 | |||
| 437f68a9f3 | |||
| 453e249de3 | |||
| 8688030d7a | |||
| 6e2003c2e2 | |||
| 5ed8959eba | |||
| 7623ebd327 | |||
| 522eb0fe9d | |||
| 7dd171322d | |||
| 752652e56c | |||
| 2582a478ab | |||
| 52ff5de564 | |||
| ba793af8b7 | |||
| 3304ecd1f0 | |||
| 4e0394cab3 | |||
| eaa963a840 | |||
| 31c13cc1c8 | |||
| 310aa7a00d | |||
| fafd605d4d | |||
| 078f3583f0 | |||
| 0b337329ba | |||
| f5df63554f | |||
| 46c4f46451 | |||
| e034864c12 | |||
| 91f38cc849 | |||
| e616d2686f | |||
| 5a09d671ab | |||
| 58d49d2867 | |||
| 8bb58ba3c3 | |||
| 017ce4e092 | |||
| 49859683b0 | |||
| 169b83b36d | |||
| 86435ba9aa | |||
| e2d6bc95a4 | |||
| 06f570f4ff | |||
| 730dfe8fc7 | |||
| 5619d39bd3 | |||
| 959e0ff1c9 | |||
| 2c228e6414 | |||
| 82468bff00 | |||
| 5f82846732 | |||
| 09c75af15c | |||
| 91964b1aba | |||
| af2e9169e6 | |||
| 4637e2c66b | |||
| 1f1a98d7ae | |||
| cea23b055b | |||
| 41a0eae0bd | |||
| 5fab00011e | |||
| ca5f028016 | |||
| b43444f674 | |||
| 866085c918 | |||
| 585c5afd1a | |||
| 6ec5c7c4d8 | |||
| 6b3875814c | |||
| 3a51c801e1 | |||
| 983da9c887 | |||
| f7b6446c74 | |||
| deb87f2dfa | |||
| b1e2538488 | |||
| 7b9e59d15d | |||
| 794d0bbe69 | |||
| f7ffdfa15d | |||
| 4900ac31a7 | |||
| 112a18f966 | |||
| 7d623d5eb4 | |||
| 4819738e8f | |||
| 948318ca19 | |||
| 89aa7baa5b | |||
| 95928a7cd4 | |||
| 1ecc9ac48c | |||
| 288196c7e8 | |||
| 0283c6cdfe | |||
| d4e7b0cda0 | |||
| bcdae19141 | |||
| 6edbbded40 | |||
| 684cac274d | |||
| ce7b008b8f | |||
| a3c58f2896 | |||
| 3839f1055a | |||
| 94350ddb3a | |||
| 919cdc27db | |||
| 2029e39299 | |||
| 707bb0a321 | |||
| 298a4a3acf | |||
| 262157c88d | |||
| 37107af7b4 | |||
| 13a9ca65a4 | |||
| d3369c83e7 | |||
| 48308042b6 | |||
| d30ad8ba28 | |||
| cf973f27bb | |||
| 87df27109a | |||
| 89e4e822bf | |||
| b89d4c7476 | |||
| ee968b5573 | |||
| 1f97740cfa | |||
| 07cfc000ea | |||
| 944791e888 | |||
| 0656377165 | |||
| 1a419972d2 | |||
| 98b150e1a4 | |||
| 96800e9b75 | |||
| c1d479f863 | |||
| 4be7984a0e | |||
| 3b0b318730 | |||
| 39bc499b10 | |||
| de4d4a4bbb | |||
| 8631dd14e9 | |||
| e7d12f4b9e | |||
| f17a531e15 | |||
| ff57c8a58c | |||
| 499b884c67 | |||
| 0c261fb903 | |||
| 322263fd5c | |||
| 845f5cabec | |||
| f919349173 | |||
| a91ae03dc6 | |||
| faed7698c2 | |||
| eb8347f92b | |||
| c53ffe6dc5 | |||
| 7c8eab4f43 | |||
| b1d8168dd3 | |||
| c196c1f5e5 | |||
| 604563af76 | |||
| 6848d3affe | |||
| 6708914e99 | |||
| 5921f9c506 | |||
| 6193ed63b1 | |||
| 18dd0d08cb | |||
| 8dd6f128bf | |||
| 04267930de | |||
| d83716dece | |||
| c77358bdc1 | |||
| 44987479a5 | |||
| 30d89abf66 | |||
| f175be6b78 | |||
| 2a41ca0628 | |||
| c4f9a050c9 | |||
| 32ff6b56eb | |||
| 33c0ebb100 | |||
| cb9aecf35e | |||
| d5b4d67858 | |||
| 9f4ae07100 | |||
| 32fc36b17d | |||
| 33766a124d | |||
| ede8d57373 | |||
| 2494e39c9e | |||
| 07fdd089a0 | |||
| 228a587fc5 | |||
| e432ff58f9 | |||
| b8969f68e0 | |||
| dbb2d1c229 | |||
| 609b7ed5c5 | |||
| 3eca0259f7 | |||
| 20f3fc96c3 | |||
| 76a174a007 | |||
| 0fde148a9f | |||
| 6b0eb1f09d | |||
| f3c90ee266 | |||
| 2482c9c5ed | |||
| da555fb908 | |||
| 7ae7d6315f | |||
| eccb499346 | |||
| 0c1796ea40 | |||
| 826fab9999 | |||
| 3b95108a3a | |||
| ab2a298ebf | |||
| 8b5fa88b6f | |||
| de712e5ccb | |||
| ffdf5cb206 | |||
| f1a91108ef | |||
| ffe063a0e9 | |||
| d74f02e367 | |||
| 0da2107974 | |||
| 1994c8a174 | |||
| 2434be4803 | |||
| d6fcee4faa | |||
| 41d028d101 | |||
| 75fc431fb8 | |||
| 917d1be994 | |||
| 900b013658 | |||
| 84486dcb2c | |||
| 9a190f3bc4 | |||
| c76bbdf16f | |||
| 24c5063b99 | |||
| a0f8a2c3a4 | |||
| 3edea6c806 | |||
| abebabc487 | |||
| 1ee6eae684 | |||
| 24852b9be8 | |||
| e80b92b481 | |||
| 573a84b2ec | |||
| 75f8e174dc | |||
| e690afc33b | |||
| 493b664f19 | |||
| 83014fb77b | |||
| 1a4722e909 | |||
| 369fefd5a8 | |||
| cc0aa707c4 | |||
| 7e14b2d46a | |||
| 1e88c3f180 | |||
| 49ee637a5c | |||
| 71f085fec0 | |||
| caa84e2731 | |||
| 2787df035c | |||
| 32da749041 | |||
| 0cbd702419 | |||
| 15f664af26 | |||
| d673ff5fad | |||
| f112df2cfc | |||
| 35644cf73c | |||
| 6cc6c04c4a | |||
| b9e1bc34da | |||
| f4d0b201ad | |||
| aff99097e7 | |||
| 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 | |||
| f383511e0d | |||
| 2f65188edb |
+38
@@ -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
|
||||
@@ -9,10 +9,9 @@ To compile ejabberd you need:
|
||||
- GNU Make
|
||||
- GCC
|
||||
- Libexpat 1.95 or higher
|
||||
- Erlang/OTP R10B-9 or higher. The recommended version is R12B-5.
|
||||
Support for R13 is experimental.
|
||||
- OpenSSL 0.9.6 or higher, for STARTTLS, SASL and SSL
|
||||
encryption. Optional, highly recommended.
|
||||
- Erlang/OTP R10B-9 or higher. Recommended: R13B04 and R14B04.
|
||||
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.
|
||||
@@ -20,8 +19,8 @@ To compile ejabberd you need:
|
||||
- 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.1 or higher. Optional. For import/export XEP-0227 files.
|
||||
- ImageMagick's Convert program. Optional. For CAPTCHA challenges.
|
||||
- exmpp 0.9.6 or higher. Optional. For import/export XEP-0227 files.
|
||||
|
||||
|
||||
1. Compile and install on *nix systems
|
||||
|
||||
@@ -281,14 +281,14 @@ build_additional_translators(List) ->
|
||||
List).
|
||||
|
||||
print_translation(File, Line, Str, StrT) ->
|
||||
{ok, StrQ, _} = regexp:gsub(Str, "\"", "\\\""),
|
||||
{ok, StrTQ, _} = regexp:gsub(StrT, "\"", "\\\""),
|
||||
StrQ = ejabberd_regexp:greplace(Str, "\\\"", "\\\\\""),
|
||||
StrTQ = ejabberd_regexp:greplace(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, "\"", "\\\""),
|
||||
StrQ = ejabberd_regexp:greplace(Str, "\\\"", "\\\\\""),
|
||||
StrTQ = ejabberd_regexp:greplace(StrT, "\\\"", "\\\\\""),
|
||||
io:format("#: ~s:~p~n#~~ msgid \"~s\"~n#~~ msgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).
|
||||
|
||||
|
||||
@@ -91,13 +91,13 @@ extract_lang_all ()
|
||||
cd $MSGS_DIR
|
||||
for i in $( ls *.msg ) ; do
|
||||
MISSING=`cat $i.translate | grep "\", \"\"}." | wc -l`
|
||||
LANGUAGE=`grep "Language:" $i.translate | sed 's/% Language: //g'`
|
||||
LANGUAGE=`grep "X-Language:" $i.translate | sed 's/% Language: //g'`
|
||||
LASTAUTH=`grep "Author:" $i.translate | head -n 1 | sed 's/% Author: //g'`
|
||||
echo -e "$i\t$MISSING\t$LANGUAGE\t$LASTAUTH"
|
||||
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
|
||||
@@ -258,7 +258,7 @@ extract_lang_updateall ()
|
||||
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}'`
|
||||
LANGUAGE=`grep "X-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'`
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ release:
|
||||
@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}{"`sed '/vsn/!d;s/\(.*\)"\(.*\)"\(.*\)/\2/' ../src/ejabberd.app`"}" >> version.tex
|
||||
@echo -n "% Contributed modules (automatically generated)." > contributed_modules.tex
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@author Mickael Remond <mickael.remond@process-one.net>
|
||||
[http://www.process-one.net/]
|
||||
@copyright 2007 ProcessOne
|
||||
@copyright 2013 ProcessOne
|
||||
@version {@vsn}, {@date} {@time}
|
||||
@title ejabberd Development API Documentation
|
||||
|
||||
|
||||
+6
-3
@@ -2,7 +2,7 @@
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Ejabberd 2.1.0 Developers Guide
|
||||
<TITLE>Ejabberd 2.1.13 Developers Guide
|
||||
</TITLE>
|
||||
|
||||
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
|
||||
@@ -49,7 +49,7 @@ TD P{margin:0px;}
|
||||
<!--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.1.0 Developers Guide</H1><H3 CLASS="titlerest">Alexey Shchepin<BR>
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.13 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">
|
||||
@@ -170,7 +170,7 @@ 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 XMPP servers. First, it checks if an
|
||||
@@ -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
|
||||
|
||||
+4
-1
@@ -141,7 +141,7 @@ 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.
|
||||
|
||||
@@ -176,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
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Ejabberd 2.1.0 Feature Sheet
|
||||
<TITLE>Ejabberd 2.1.13 Feature Sheet
|
||||
</TITLE>
|
||||
|
||||
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
|
||||
@@ -50,7 +50,7 @@ SPAN{width:20%; float:right; text-align:left; margin-left:auto;}
|
||||
<!--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.1.0 Feature Sheet</H1><H3 CLASS="titlerest">Sander Devrieze<BR>
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.13 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">
|
||||
|
||||
+942
-393
File diff suppressed because it is too large
Load Diff
+934
-238
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@@ -135,6 +135,8 @@
|
||||
- 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.
|
||||
@@ -207,14 +209,24 @@
|
||||
|
||||
Upgrading From ejabberd 2.0.x:
|
||||
|
||||
- The database schemas didn't change since ejabberd 1.1.4.
|
||||
Anyway, it is recommended to backup the Mnesia spool directory and
|
||||
- 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.
|
||||
|
||||
- The plugin of mod_pubsub "default" is renamed to "flat". You need
|
||||
to edit the ejabberd configuration file and replace those names.
|
||||
- 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
|
||||
- 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.
|
||||
|
||||
@@ -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,39 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.10
|
||||
|
||||
ejabberd 2.1.10 includes a few bugfixes and improvements.
|
||||
|
||||
Read more details about the changes in:
|
||||
http://redir.process-one.net/ejabberd-2.1.10
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The major changes are:
|
||||
|
||||
* Erlang/OTP compatibility
|
||||
- Support Erlang/OTP R15B regexp and drivers (EJAB-1521)
|
||||
- Fix modules update in R14B04 and higher
|
||||
- Fix modules update of stripped beams (EJAB-1520)
|
||||
|
||||
* XMPP Core
|
||||
- Fix presence problem in C2S after first unavailable (EJAB-1466)
|
||||
- Fix bug on S2S shaper when TLS is used
|
||||
- Prevent overload of incoming S2S connections
|
||||
|
||||
* XEPs
|
||||
- BOSH: Get rid of useless mnesia transaction (EJAB-1502)
|
||||
- MUC: Don't reveal invitee resource when room informs invitor
|
||||
- Privacy: Activate "Blocked Contacts" to current c2s connection (EJAB-1519)
|
||||
- Privacy: Always allow packets from user's server and bare jid (EJAB-1441)
|
||||
- Pubsub: Add hooks for node creation/deletion (EJAB-1470)
|
||||
- Shared Rosters: support groupname@vhost in Displayed Groups (EJAB-506)
|
||||
- Vcard: Fix error when lowercasing some search results (EJAB-1490)
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.11
|
||||
|
||||
ejabberd 2.1.11 includes a few bugfixes and improvements.
|
||||
|
||||
Read more details about the changes in:
|
||||
http://redir.process-one.net/ejabberd-2.1.11
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The major changes are:
|
||||
|
||||
* HTTP service
|
||||
- Fix ejabberd_http:get_line
|
||||
- Don't use binary:match to extract lines from binaries
|
||||
- Parse and encode https header names like native http parser does
|
||||
- Parse correctly https request split into multiple packets
|
||||
- Properly handle HEAD request in mod_http_bind (EJAB-1538)
|
||||
- New option default_host for handling requests with ambiguous Host (EJAB-1261)
|
||||
|
||||
* ODBC
|
||||
- New ODBC support for mod_announce
|
||||
- New ODBC support for mod_blocking
|
||||
- New ODBC support for mod_irc
|
||||
- New ODBC support for mod_muc
|
||||
- New ODBC support for mod_shared_roster
|
||||
- New ODBC support for mod_vcard_xupdate
|
||||
- Add ODBC exporting function for privacy table
|
||||
- Work also with some unicode strings in PgSQL (EJAB-1490)
|
||||
- Replace a single quote with double quotes in an ODBC escape
|
||||
|
||||
* SSL
|
||||
- Make sure that res is initialized in all cases
|
||||
- Parse correctly https request split into multiple packets (EJAB-1537)
|
||||
- Added missed tls:recv_data/2
|
||||
- Don't ignore Length parameter in tls:recv
|
||||
- Avoid quadratic behavior in reading SSL data
|
||||
- Dix http_bind webserver TLS fail on Chrome (EJAB-1530)
|
||||
|
||||
* Miscelanea
|
||||
- Assume we have only one CPU when an auto-detection fails (EJAB-1516)
|
||||
- Auth: Relax digest-uri handling (EJAB-1529)
|
||||
- Caps: Cache caps timestamp before the IQ-request is done
|
||||
- IRC: Use of MUC password
|
||||
- Private: misc errors cases fixes
|
||||
- Pubsub: return user affiliation for a specified node (EJAB-1294)
|
||||
- Shared Roster: Foreign items were not pushed (EJAB-1509)
|
||||
- Shared Roster LDAP: user substitution in ldap_rfilter (EJAB-1555)
|
||||
- Windows: Fix makefile rules for building DLLs
|
||||
|
||||
|
||||
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.12
|
||||
|
||||
ejabberd 2.1.12 includes a many bugfixes and a few improvements.
|
||||
|
||||
Read more details about the changes in:
|
||||
http://redir.process-one.net/ejabberd-2.1.12
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The changes are:
|
||||
|
||||
* Core ejabberd
|
||||
- Make terms serialization faster
|
||||
- Reduce size of XML stream state
|
||||
|
||||
* Administration
|
||||
- Add SCRAM and remove MD5 support to ejabberd commands auth verification
|
||||
- Added command to list all the vhosts registered in an ejabberd node
|
||||
- Added export2odbc command, copied from mod_admin_extra.erl
|
||||
- Fix ejabberdctl number of arguments error report with R15
|
||||
- Check node name is available before starting ejabberd (EJAB-1572)
|
||||
- Fix ejabberd_xmlrpc commands authentication with SCRAM
|
||||
- Fix mod_offline:store_offline_msg argument (EJAB-1581)
|
||||
- Log IP address when auth attempt fails
|
||||
- Make sure update_info returns atoms only (EJAB-1595)
|
||||
- On shutdown, first stop listeners, then modules
|
||||
|
||||
* Encryption
|
||||
- Detect OpenSSL version at runtime, not at compile time
|
||||
- Fixed signedness issue in tls_drv GET_DESCRYPTED_INPUT (EJAB-1591)
|
||||
- Enable DHE key exchange in TLS driver
|
||||
- Enable ECDHE key exchange in TSL driver
|
||||
- Disable old and unsecure ciphers in TLS driver
|
||||
- Disable SSL 2.0 in TLS driver
|
||||
|
||||
* HTTP-Bind
|
||||
- Do not trigger item-not-found errors in mod_http_bind
|
||||
- Repeated http-bind request should abort only requests with same rid
|
||||
- Receiving missing request shouldn't close waiting out-ouf-order request
|
||||
|
||||
* XMPP
|
||||
- Allow multiple fqdn values in configuration (EJAB-1578)
|
||||
- Fix get_subscription_lists/4
|
||||
- Fix account registration
|
||||
- Send announce Message stanzas as Headline type instead of Normal
|
||||
|
||||
* Other
|
||||
- Guide: Fix file name of Name Service Switch
|
||||
- Guide: Document the db_type modules option (EJAB-1560)
|
||||
- LDAP: Fix broken JPEG photo (EJAB-1526)
|
||||
- LDAP: Fix compatibility with Erlang R16A (EJAB-1612)
|
||||
- MUC: Fix angle brackets handle in plaintext log (EJAB-1610)
|
||||
- MUC: Fix MUC start when Mnesia tables don't exist yet
|
||||
- MUC: New mod_muc_log option file_permissions (EJAB-1588)
|
||||
- ODBC: Merge SQL and Mnesia code into one module (EJAB-1560)
|
||||
- Translation: New Hebrew
|
||||
- Translation: Update Slovak
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.12
|
||||
|
||||
ejabberd 2.1.13 includes just a few bugfixes.
|
||||
|
||||
Read more details about the changes in:
|
||||
http://redir.process-one.net/ejabberd-2.1.13
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The changes are:
|
||||
|
||||
- Compilation: Detect correctly newer Darwin versions (EJAB-1594)
|
||||
- Guide: ejabberd_service expects a shaper_rule, not a shaper
|
||||
- MUC: Handle multiple < and > in mod_muc_log plaintext mode (EJAB-1640)
|
||||
- MUC: Handle ~ control sequence in text of mod_muc_log (EJAB-1639)
|
||||
- MUC: list_to_integer/2 only works in OTP R14 and newer
|
||||
- Pubsub: access_createnode acl also applies to auto created nodes
|
||||
- Web: Normalize HTTP path
|
||||
- WebAdmin: Fix bug when displaying offline messages in WebAdmin
|
||||
|
||||
|
||||
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/
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.9
|
||||
|
||||
ejabberd 2.1.9 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.9
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The changes are:
|
||||
|
||||
* Core ejabberd
|
||||
- Decrease CPU usage caused by tls:send with large data
|
||||
- Escape iolist correctly when NIFs are disabled (EJAB-1462)
|
||||
- Fix code to satisfy Dialyzer warnings
|
||||
- Fix compilation in Windows
|
||||
- Replace calls of OTP's Binary, since they would require R14
|
||||
|
||||
* LDAP
|
||||
- Document ldap_tls_cacertfile and ldap_tls_depth options (EJAB-1299)
|
||||
- Log an error when an LDAP filter is incorrect (EJAB-1395)
|
||||
- New options: ldap_tls_cacertfile and ldap_tls_depth (EJAB-1299)
|
||||
- New option: ldap_deref_aliases (EJAB-639)
|
||||
- Match ldap_uidattr_format case-insensitively (EJAB-1449)
|
||||
|
||||
* MUC
|
||||
- Support for multiple entry with same nick to MUC rooms (EJAB-305)
|
||||
- Support voice request and approvement
|
||||
- New room option: allow_private_messages_from_visitors
|
||||
- New room options: allow_voice_requests and voice_request_min_interval
|
||||
- Include status 110 in presence to new occupant (EJAB-740)
|
||||
- Fix mod_muc_log crash when first log entry is room destroy (EJAB-1499)
|
||||
- Many fixes and improvements in mod_muc
|
||||
|
||||
* Pubsub
|
||||
- Enable pubsub#deliver_notification checking (EJAB-1453)
|
||||
- Fix Denial of Service when user sends malformed publish stanza (EJAB-1498)
|
||||
|
||||
* ODBC
|
||||
- Fix ODBC account counting (EJAB-1491)
|
||||
- Optimized mod_roster_odbc:get_roster
|
||||
|
||||
* Miscellanea:
|
||||
- New SASL SCRAM-SHA-1 authentication mechanism (EJAB-1196)
|
||||
- New option: resource_conflict (EJAB-650)
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
% ejabberd version (automatically generated).
|
||||
\newcommand{\version}{2.1.0}
|
||||
\newcommand{\version}{2.1.13}
|
||||
|
||||
Regular → Executable
+16
@@ -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;
|
||||
|
||||
+37
-18
@@ -30,15 +30,17 @@ else
|
||||
INIT_USER=$(INSTALLUSER)
|
||||
endif
|
||||
|
||||
EFLAGS += @ERLANG_SSL39@ -pa .
|
||||
EFLAGS += @ERLANG_SSLVER@ -pa .
|
||||
ERLANG_CFLAGS += @ERLANG_SSLVER@
|
||||
|
||||
# make debug=true to compile Erlang module with debug informations.
|
||||
ifdef debug
|
||||
EFLAGS+=+debug_info
|
||||
EFLAGS+=+debug_info +export_all
|
||||
endif
|
||||
|
||||
ifdef ejabberd_debug
|
||||
EFLAGS+=-Dejabberd_debug
|
||||
DEBUGTOOLS = p1_prof.erl
|
||||
ifdef debugtools
|
||||
SOURCES+=$(DEBUGTOOLS)
|
||||
endif
|
||||
|
||||
ifeq (@hipe@, true)
|
||||
@@ -53,10 +55,20 @@ ifeq (@full_xml@, true)
|
||||
EFLAGS+=-DFULL_XML_SUPPORT
|
||||
endif
|
||||
|
||||
ifeq (@nif@, true)
|
||||
EFLAGS+=-DNIF
|
||||
ERLSHLIBS=xml.so
|
||||
endif
|
||||
|
||||
ifeq (@transient_supervisors@, false)
|
||||
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)
|
||||
@@ -66,10 +78,11 @@ prefix = @prefix@
|
||||
exec_prefix = @exec_prefix@
|
||||
|
||||
SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @mod_proxy65@ @eldap@ @pam@ @web@ stringprep stun @tls@ @odbc@ @ejabberd_zlib@
|
||||
ERLSHLIBS = expat_erl.so
|
||||
ERLSHLIBS += expat_erl.so
|
||||
ERLBEHAVS = cyrsasl.erl gen_mod.erl p1_fsm.erl
|
||||
SOURCES_ALL = $(wildcard *.erl)
|
||||
SOURCES = $(filter-out $(ERLBEHAVS),$(SOURCES_ALL))
|
||||
SOURCES_MISC = $(ERLBEHAVS) $(DEBUGTOOLS)
|
||||
SOURCES += $(filter-out $(SOURCES_MISC),$(SOURCES_ALL))
|
||||
ERLBEHAVBEAMS = $(ERLBEHAVS:.erl=.beam)
|
||||
BEAMS = $(SOURCES:.erl=.beam)
|
||||
|
||||
@@ -110,6 +123,9 @@ 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
|
||||
|
||||
@@ -192,6 +208,7 @@ install: all
|
||||
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)
|
||||
@@ -232,6 +249,11 @@ install: all
|
||||
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)
|
||||
@@ -239,9 +261,14 @@ install: all
|
||||
#
|
||||
# Documentation
|
||||
install -d $(DOCDIR)
|
||||
install ../doc/guide.html $(DOCDIR)
|
||||
install ../doc/*.png $(DOCDIR)
|
||||
install ../doc/*.txt $(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
|
||||
|
||||
@@ -265,6 +292,7 @@ uninstall-all: uninstall-binary
|
||||
rm -rf $(ETCDIR)
|
||||
rm -rf $(EJABBERDDIR)
|
||||
rm -rf $(SPOOLDIR)
|
||||
rm -rf $(CTLLOCKDIR)
|
||||
rm -rf $(LOGDIR)
|
||||
|
||||
clean: clean-recursive clean-local
|
||||
@@ -286,12 +314,3 @@ Makefile: Makefile.in
|
||||
|
||||
dialyzer: $(BEAMS)
|
||||
@dialyzer -c .
|
||||
|
||||
LASTSVNREVCHANGELOG = 2075
|
||||
changelog:
|
||||
svn up -r $(LASTSVNREVCHANGELOG) ../ChangeLog
|
||||
mv ../ChangeLog ../ChangeLog.old
|
||||
svn2cl -r BASE:$(LASTSVNREVCHANGELOG) -o ../ChangeLog --group-by-day \
|
||||
--separate-daylogs --break-before-msg --reparagraph ..
|
||||
cat ../ChangeLog.old >> ../ChangeLog
|
||||
rm ../ChangeLog.old
|
||||
|
||||
+2
-2
@@ -170,7 +170,7 @@ LD=link.exe
|
||||
LD_FLAGS=-release -nologo -incremental:no -dll "$(EI_DIR)\lib\ei_md.lib" "$(EI_DIR)\lib\erl_interface_md.lib" "$(EXPAT_LIB)" MSVCRT.LIB kernel32.lib advapi32.lib gdi32.lib user32.lib comctl32.lib comdlg32.lib shell32.lib
|
||||
|
||||
$(DLL) : $(OBJECT)
|
||||
$(LD) $(LD_FLAGS) -out:$(DLL) $(OBJECT)
|
||||
$(LD) $(LD_FLAGS) -out:$@ $<
|
||||
|
||||
$(OBJECT) : $(SOURCE)
|
||||
$(CC) $(CC_FLAGS) -c -Fo$(OBJECT) $(SOURCE)
|
||||
$(CC) $(CC_FLAGS) -c -Fo$@ $<
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
AC_DEFUN(AM_WITH_EXPAT,
|
||||
AC_DEFUN([AM_WITH_EXPAT],
|
||||
[ AC_ARG_WITH(expat,
|
||||
[AC_HELP_STRING([--with-expat=PREFIX], [prefix where EXPAT is installed])])
|
||||
|
||||
@@ -32,7 +32,7 @@ AC_DEFUN(AM_WITH_EXPAT,
|
||||
AC_SUBST(EXPAT_LIBS)
|
||||
])
|
||||
|
||||
AC_DEFUN(AM_WITH_ZLIB,
|
||||
AC_DEFUN([AM_WITH_ZLIB],
|
||||
[ AC_ARG_WITH(zlib,
|
||||
[AC_HELP_STRING([--with-zlib=PREFIX], [prefix where zlib is installed])])
|
||||
|
||||
@@ -68,7 +68,7 @@ if test x"$ejabberd_zlib" != x; then
|
||||
fi
|
||||
])
|
||||
|
||||
AC_DEFUN(AM_WITH_PAM,
|
||||
AC_DEFUN([AM_WITH_PAM],
|
||||
[ AC_ARG_WITH(pam,
|
||||
[AC_HELP_STRING([--with-pam=PREFIX], [prefix where PAM is installed])])
|
||||
if test x"$pam" != x; then
|
||||
@@ -103,7 +103,7 @@ if test x"$pam" != x; then
|
||||
fi
|
||||
])
|
||||
|
||||
AC_DEFUN(AM_WITH_ERLANG,
|
||||
AC_DEFUN([AM_WITH_ERLANG],
|
||||
[ AC_ARG_WITH(erlang,
|
||||
[AC_HELP_STRING([--with-erlang=PREFIX], [path to erlc and erl])])
|
||||
|
||||
@@ -121,7 +121,6 @@ AC_DEFUN(AM_WITH_ERLANG,
|
||||
-author('alexey@sevcom.net').
|
||||
|
||||
-export([[start/0]]).
|
||||
-include_lib("ssl/include/ssl_pkix.hrl").
|
||||
|
||||
start() ->
|
||||
EIDirS = code:lib_dir("erl_interface") ++ "\n",
|
||||
@@ -130,11 +129,13 @@ start() ->
|
||||
file:write_file("conftest.out", list_to_binary(EIDirS ++ EILibS ++ ssldef() ++ RootDirS)),
|
||||
halt().
|
||||
|
||||
-[ifdef]('id-pkix').
|
||||
ssldef() -> "-DSSL39\n".
|
||||
-else.
|
||||
ssldef() -> "\n".
|
||||
-endif.
|
||||
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() ->
|
||||
@@ -184,7 +185,7 @@ _EOF
|
||||
# Second line
|
||||
ERLANG_EI_LIB=`cat conftest.out | head -n 2 | tail -n 1`
|
||||
# Third line
|
||||
ERLANG_SSL39=`cat conftest.out | head -n 3 | tail -n 1`
|
||||
ERLANG_SSLVER=`cat conftest.out | head -n 3 | tail -n 1`
|
||||
# End line
|
||||
ERLANG_DIR=`cat conftest.out | tail -n 1`
|
||||
|
||||
@@ -193,12 +194,12 @@ _EOF
|
||||
|
||||
AC_SUBST(ERLANG_CFLAGS)
|
||||
AC_SUBST(ERLANG_LIBS)
|
||||
AC_SUBST(ERLANG_SSL39)
|
||||
AC_SUBST(ERLANG_SSLVER)
|
||||
AC_SUBST(ERLC)
|
||||
AC_SUBST(ERL)
|
||||
])
|
||||
|
||||
AC_DEFUN(AC_MOD_ENABLE,
|
||||
AC_DEFUN([AC_MOD_ENABLE],
|
||||
[
|
||||
$1=
|
||||
make_$1=
|
||||
@@ -308,7 +309,7 @@ size_t iconv();
|
||||
])
|
||||
|
||||
dnl <openssl>
|
||||
AC_DEFUN(AM_WITH_OPENSSL,
|
||||
AC_DEFUN([AM_WITH_OPENSSL],
|
||||
[ AC_ARG_WITH(openssl,
|
||||
[AC_HELP_STRING([--with-openssl=PREFIX], [prefix where OPENSSL is installed])])
|
||||
unset SSL_LIBS;
|
||||
+16
-8
@@ -5,7 +5,7 @@
|
||||
%%% Created : 18 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -181,9 +181,11 @@ match_acl(ACL, JID, Host) ->
|
||||
lists:member(Server, ?MYHOSTS)))
|
||||
andalso is_regexp_match(User, UR);
|
||||
{shared_group, G} ->
|
||||
mod_shared_roster:is_user_in_group({User, Server}, G, Host);
|
||||
Mod = loaded_shared_roster_module(Host),
|
||||
Mod:is_user_in_group({User, Server}, G, Host);
|
||||
{shared_group, G, H} ->
|
||||
mod_shared_roster:is_user_in_group({User, Server}, G, H);
|
||||
Mod = loaded_shared_roster_module(H),
|
||||
Mod:is_user_in_group({User, Server}, G, H);
|
||||
{user_regexp, UR, S} ->
|
||||
(S == Server) andalso
|
||||
is_regexp_match(User, UR);
|
||||
@@ -223,19 +225,25 @@ match_acl(ACL, JID, Host) ->
|
||||
end.
|
||||
|
||||
is_regexp_match(String, RegExp) ->
|
||||
case regexp:first_match(String, RegExp) of
|
||||
case ejabberd_regexp:run(String, RegExp) of
|
||||
nomatch ->
|
||||
false;
|
||||
{match, _, _} ->
|
||||
match ->
|
||||
true;
|
||||
{error, ErrDesc} ->
|
||||
?ERROR_MSG(
|
||||
"Wrong regexp ~p in ACL: ~p",
|
||||
[RegExp, lists:flatten(regexp:format_error(ErrDesc))]),
|
||||
[RegExp, ErrDesc]),
|
||||
false
|
||||
end.
|
||||
|
||||
is_glob_match(String, Glob) ->
|
||||
is_regexp_match(String, regexp:sh_to_awk(Glob)).
|
||||
|
||||
is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)).
|
||||
|
||||
loaded_shared_roster_module(Host) ->
|
||||
case gen_mod:is_loaded(Host, mod_shared_roster_ldap) of
|
||||
true ->
|
||||
mod_shared_roster_ldap;
|
||||
false ->
|
||||
mod_shared_roster
|
||||
end.
|
||||
|
||||
+7
-6
@@ -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-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -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
-1
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
|
||||
@@ -0,0 +1,609 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% 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-2013 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() ->
|
||||
case erlang:system_info(logical_processors) of
|
||||
unknown ->
|
||||
1;
|
||||
Num ->
|
||||
Num
|
||||
end.
|
||||
|
||||
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-2013 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
|
||||
%%====================================================================
|
||||
Vendored
+415
-313
File diff suppressed because it is too large
Load Diff
+16
-4
@@ -2,7 +2,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ(2.53)
|
||||
AC_INIT(ejabberd.erl, version, [ejabberd@process-one.net], [ejabberd])
|
||||
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
|
||||
@@ -81,7 +81,7 @@ AC_ARG_ENABLE(transient_supervisors,
|
||||
[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)
|
||||
|
||||
@@ -94,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
|
||||
@@ -139,14 +148,17 @@ if test "$ENABLEUSER" != ""; then
|
||||
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'"
|
||||
*darwin*)
|
||||
echo "Target OS is 'Darwin'"
|
||||
AC_LANG(Erlang)
|
||||
AC_RUN_IFELSE(
|
||||
[AC_LANG_PROGRAM([],[dnl
|
||||
|
||||
+5
-7
@@ -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-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -58,7 +58,7 @@ 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),
|
||||
@@ -67,8 +67,8 @@ start() ->
|
||||
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 ++
|
||||
@@ -87,5 +87,3 @@ start() ->
|
||||
ZlibDir ++
|
||||
ZlibLib)),
|
||||
halt().
|
||||
|
||||
|
||||
|
||||
+25
-12
@@ -5,7 +5,7 @@
|
||||
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -34,7 +34,9 @@
|
||||
server_start/3,
|
||||
server_step/2]).
|
||||
|
||||
-record(sasl_mechanism, {mechanism, module, require_plain_password}).
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(sasl_mechanism, {mechanism, module, password_type}).
|
||||
-record(sasl_state, {service, myname, realm,
|
||||
get_password, check_password, check_password_digest,
|
||||
mech_mod, mech_state}).
|
||||
@@ -52,14 +54,15 @@ start() ->
|
||||
{keypos, #sasl_mechanism.mechanism}]),
|
||||
cyrsasl_plain:start([]),
|
||||
cyrsasl_digest:start([]),
|
||||
cyrsasl_scram:start([]),
|
||||
cyrsasl_anonymous:start([]),
|
||||
ok.
|
||||
|
||||
register_mechanism(Mechanism, Module, RequirePlainPassword) ->
|
||||
register_mechanism(Mechanism, Module, PasswordType) ->
|
||||
ets:insert(sasl_mechanism,
|
||||
#sasl_mechanism{mechanism = Mechanism,
|
||||
module = Module,
|
||||
require_plain_password = RequirePlainPassword}).
|
||||
password_type = PasswordType}).
|
||||
|
||||
%%% TODO: use callbacks
|
||||
%%-include("ejabberd.hrl").
|
||||
@@ -97,17 +100,20 @@ check_credentials(_State, Props) ->
|
||||
end.
|
||||
|
||||
listmech(Host) ->
|
||||
RequirePlainPassword = ejabberd_auth:plain_password_required(Host),
|
||||
|
||||
Mechs = ets:select(sasl_mechanism,
|
||||
[{#sasl_mechanism{mechanism = '$1',
|
||||
require_plain_password = '$2',
|
||||
password_type = '$2',
|
||||
_ = '_'},
|
||||
if
|
||||
RequirePlainPassword ->
|
||||
[{'==', '$2', false}];
|
||||
true ->
|
||||
[]
|
||||
case catch ejabberd_auth:store_type(Host) of
|
||||
external ->
|
||||
[{'==', '$2', plain}];
|
||||
scram ->
|
||||
[{'/=', '$2', digest}];
|
||||
{'EXIT',{undef,[{Module,store_type,[]} | _]}} ->
|
||||
?WARNING_MSG("~p doesn't implement the function store_type/0", [Module]),
|
||||
[];
|
||||
_Else ->
|
||||
[]
|
||||
end,
|
||||
['$1']}]),
|
||||
filter_anonymous(Host, Mechs).
|
||||
@@ -152,6 +158,13 @@ server_step(State, ClientIn) ->
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
end;
|
||||
{ok, Props, ServerOut} ->
|
||||
case check_credentials(State, Props) of
|
||||
ok ->
|
||||
{ok, Props, ServerOut};
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
end;
|
||||
{continue, ServerOut, NewMechState} ->
|
||||
{continue, ServerOut,
|
||||
State#sasl_state{mech_state = NewMechState}};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
%%% Created : 23 Aug 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -34,7 +34,7 @@
|
||||
-record(state, {server}).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("ANONYMOUS", ?MODULE, false),
|
||||
cyrsasl:register_mechanism("ANONYMOUS", ?MODULE, plain),
|
||||
ok.
|
||||
|
||||
stop() ->
|
||||
|
||||
+42
-13
@@ -5,7 +5,7 @@
|
||||
%%% Created : 11 Mar 2003 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -37,10 +37,12 @@
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state, {step, nonce, username, authzid, get_password, check_password, auth_module,
|
||||
host}).
|
||||
host, hostfqdn}).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, true).
|
||||
Fqdn = get_local_fqdn(),
|
||||
?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~p", [Fqdn]),
|
||||
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, digest).
|
||||
|
||||
stop() ->
|
||||
ok.
|
||||
@@ -49,6 +51,7 @@ mech_new(Host, GetPassword, _CheckPassword, CheckPasswordDigest) ->
|
||||
{ok, #state{step = 1,
|
||||
nonce = randoms:get_string(),
|
||||
host = Host,
|
||||
hostfqdn = get_local_fqdn(),
|
||||
get_password = GetPassword,
|
||||
check_password = CheckPasswordDigest}}.
|
||||
|
||||
@@ -64,10 +67,11 @@ mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
|
||||
KeyVals ->
|
||||
DigestURI = xml:get_attr_s("digest-uri", KeyVals),
|
||||
UserName = xml:get_attr_s("username", KeyVals),
|
||||
case is_digesturi_valid(DigestURI, State#state.host) of
|
||||
case is_digesturi_valid(DigestURI, State#state.host, State#state.hostfqdn) of
|
||||
false ->
|
||||
?DEBUG("User login not authorized because digest-uri "
|
||||
"seems invalid: ~p", [DigestURI]),
|
||||
"seems invalid: ~p (checking for Host ~p, FQDN ~p)", [DigestURI,
|
||||
State#state.host, State#state.hostfqdn]),
|
||||
{error, "not-authorized", UserName};
|
||||
true ->
|
||||
AuthzId = xml:get_attr_s("authzid", KeyVals),
|
||||
@@ -154,21 +158,46 @@ parse4([], Key, Val, Ts) ->
|
||||
%% 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) ->
|
||||
%% then acceptable digest-uris would be:
|
||||
%% xmpp/server3.example.org/jabber.example.org, xmpp/server3.example.org and
|
||||
%% xmpp/jabber.example.org
|
||||
%% The last version is not actually allowed by the RFC, but implemented by popular clients
|
||||
is_digesturi_valid(DigestURICase, JabberDomain, JabberFQDN) ->
|
||||
DigestURI = stringprep:tolower(DigestURICase),
|
||||
case catch string:tokens(DigestURI, "/") of
|
||||
["xmpp", Host] when Host == JabberHost ->
|
||||
true;
|
||||
["xmpp", _Host, ServName] when ServName == JabberHost ->
|
||||
true;
|
||||
["xmpp", Host] ->
|
||||
IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
|
||||
(Host == JabberDomain) or IsHostFqdn;
|
||||
["xmpp", Host, ServName] ->
|
||||
IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
|
||||
(ServName == JabberDomain) and IsHostFqdn;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
is_host_fqdn(Host, [Letter | _Tail] = Fqdn) when not is_list(Letter) ->
|
||||
Host == Fqdn;
|
||||
is_host_fqdn(_Host, []) ->
|
||||
false;
|
||||
is_host_fqdn(Host, [Fqdn | _FqdnTail]) when Host == Fqdn ->
|
||||
true;
|
||||
is_host_fqdn(Host, [Fqdn | FqdnTail]) when Host /= Fqdn ->
|
||||
is_host_fqdn(Host, FqdnTail).
|
||||
|
||||
|
||||
get_local_fqdn() ->
|
||||
case (catch get_local_fqdn2()) of
|
||||
Str when is_list(Str) -> Str;
|
||||
_ -> "unknown-fqdn, please configure fqdn option in ejabberd.cfg!"
|
||||
end.
|
||||
get_local_fqdn2() ->
|
||||
case ejabberd_config:get_local_option(fqdn) of
|
||||
ConfiguredFqdn when is_list(ConfiguredFqdn) ->
|
||||
ConfiguredFqdn;
|
||||
_undefined ->
|
||||
{ok, Hostname} = inet:gethostname(),
|
||||
{ok, {hostent, Fqdn, _, _, _, _}} = inet:gethostbyname(Hostname),
|
||||
Fqdn
|
||||
end.
|
||||
|
||||
digit_to_xchar(D) when (D >= 0) and (D < 10) ->
|
||||
D + 48;
|
||||
|
||||
+29
-4
@@ -5,7 +5,7 @@
|
||||
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -34,7 +34,7 @@
|
||||
-record(state, {check_password}).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("PLAIN", ?MODULE, false),
|
||||
cyrsasl:register_mechanism("PLAIN", ?MODULE, plain),
|
||||
ok.
|
||||
|
||||
stop() ->
|
||||
@@ -44,7 +44,7 @@ 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, AuthModule} ->
|
||||
@@ -57,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, "", []).
|
||||
@@ -71,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,190 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl_scram.erl
|
||||
%%% Author : Stephen Röttger <stephen.roettger@googlemail.com>
|
||||
%%% Purpose : SASL SCRAM authentication
|
||||
%%% Created : 7 Aug 2011 by Stephen Röttger <stephen.roettger@googlemail.com>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 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_scram).
|
||||
-author('stephen.roettger@googlemail.com').
|
||||
|
||||
-export([start/1,
|
||||
stop/0,
|
||||
mech_new/4,
|
||||
mech_step/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state, {step, stored_key, server_key, username, get_password, check_password,
|
||||
auth_message, client_nonce, server_nonce}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
-define(NONCE_LENGTH, 16).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("SCRAM-SHA-1", ?MODULE, scram).
|
||||
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
mech_new(_Host, GetPassword, _CheckPassword, _CheckPasswordDigest) ->
|
||||
{ok, #state{step = 2, get_password = GetPassword}}.
|
||||
|
||||
mech_step(#state{step = 2} = State, ClientIn) ->
|
||||
case string:tokens(ClientIn, ",") of
|
||||
[CBind, UserNameAttribute, ClientNonceAttribute] when (CBind == "y") or (CBind == "n") ->
|
||||
case parse_attribute(UserNameAttribute) of
|
||||
{error, Reason} ->
|
||||
{error, Reason};
|
||||
{_, EscapedUserName} ->
|
||||
case unescape_username(EscapedUserName) of
|
||||
error ->
|
||||
{error, "protocol-error-bad-username"};
|
||||
UserName ->
|
||||
case parse_attribute(ClientNonceAttribute) of
|
||||
{$r, ClientNonce} ->
|
||||
case (State#state.get_password)(UserName) of
|
||||
{false, _} ->
|
||||
{error, "not-authorized", UserName};
|
||||
{Ret, _AuthModule} ->
|
||||
{StoredKey, ServerKey, Salt, IterationCount} = if
|
||||
is_tuple(Ret) ->
|
||||
Ret;
|
||||
true ->
|
||||
TempSalt = crypto:rand_bytes(?SALT_LENGTH),
|
||||
SaltedPassword = scram:salted_password(Ret, TempSalt, ?SCRAM_DEFAULT_ITERATION_COUNT),
|
||||
{scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
scram:server_key(SaltedPassword), TempSalt, ?SCRAM_DEFAULT_ITERATION_COUNT}
|
||||
end,
|
||||
ClientFirstMessageBare = string:substr(ClientIn, string:str(ClientIn, "n=")),
|
||||
ServerNonce = base64:encode_to_string(crypto:rand_bytes(?NONCE_LENGTH)),
|
||||
ServerFirstMessage = "r=" ++ ClientNonce ++ ServerNonce ++ "," ++
|
||||
"s=" ++ base64:encode_to_string(Salt) ++ "," ++
|
||||
"i=" ++ integer_to_list(IterationCount),
|
||||
{continue,
|
||||
ServerFirstMessage,
|
||||
State#state{step = 4, stored_key = StoredKey, server_key = ServerKey,
|
||||
auth_message = ClientFirstMessageBare ++ "," ++ ServerFirstMessage,
|
||||
client_nonce = ClientNonce, server_nonce = ServerNonce, username = UserName}}
|
||||
end;
|
||||
_Else ->
|
||||
{error, "not-supported"}
|
||||
end
|
||||
end
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
end;
|
||||
mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
case string:tokens(ClientIn, ",") of
|
||||
[GS2ChannelBindingAttribute, NonceAttribute, ClientProofAttribute] ->
|
||||
case parse_attribute(GS2ChannelBindingAttribute) of
|
||||
{$c, CVal} when (CVal == "biws") or (CVal == "eSws") ->
|
||||
%% biws is base64 for n,, => channelbinding not supported
|
||||
%% eSws is base64 for y,, => channelbinding supported by client only
|
||||
Nonce = State#state.client_nonce ++ State#state.server_nonce,
|
||||
case parse_attribute(NonceAttribute) of
|
||||
{$r, CompareNonce} when CompareNonce == Nonce ->
|
||||
case parse_attribute(ClientProofAttribute) of
|
||||
{$p, ClientProofB64} ->
|
||||
ClientProof = base64:decode(ClientProofB64),
|
||||
AuthMessage = State#state.auth_message ++ "," ++ string:substr(ClientIn, 1, string:str(ClientIn, ",p=")-1),
|
||||
ClientSignature = scram:client_signature(State#state.stored_key, AuthMessage),
|
||||
ClientKey = scram:client_key(ClientProof, ClientSignature),
|
||||
CompareStoredKey = scram:stored_key(ClientKey),
|
||||
if CompareStoredKey == State#state.stored_key ->
|
||||
ServerSignature = scram:server_signature(State#state.server_key, AuthMessage),
|
||||
{ok, [{username, State#state.username}], "v=" ++ base64:encode_to_string(ServerSignature)};
|
||||
true ->
|
||||
{error, "bad-auth"}
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
end;
|
||||
{$r, _} ->
|
||||
{error, "bad-nonce"};
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
end.
|
||||
|
||||
parse_attribute(Attribute) ->
|
||||
AttributeLen = string:len(Attribute),
|
||||
if
|
||||
AttributeLen >= 3 ->
|
||||
SecondChar = lists:nth(2, Attribute),
|
||||
case is_alpha(lists:nth(1, Attribute)) of
|
||||
true ->
|
||||
if
|
||||
SecondChar == $= ->
|
||||
String = string:substr(Attribute, 3),
|
||||
{lists:nth(1, Attribute), String};
|
||||
true ->
|
||||
{error, "bad-format second char not equal sign"}
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-format first char not a letter"}
|
||||
end;
|
||||
true ->
|
||||
{error, "bad-format attribute too short"}
|
||||
end.
|
||||
|
||||
unescape_username("") ->
|
||||
"";
|
||||
unescape_username(EscapedUsername) ->
|
||||
Pos = string:str(EscapedUsername, "="),
|
||||
if
|
||||
Pos == 0 ->
|
||||
EscapedUsername;
|
||||
true ->
|
||||
Start = string:substr(EscapedUsername, 1, Pos-1),
|
||||
End = string:substr(EscapedUsername, Pos),
|
||||
EndLen = string:len(End),
|
||||
if
|
||||
EndLen < 3 ->
|
||||
error;
|
||||
true ->
|
||||
case string:substr(End, 1, 3) of
|
||||
"=2C" ->
|
||||
Start ++ "," ++ unescape_username(string:substr(End, 4));
|
||||
"=3D" ->
|
||||
Start ++ "=" ++ unescape_username(string:substr(End, 4));
|
||||
_Else ->
|
||||
error
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
is_alpha(Char) when Char >= $a, Char =< $z ->
|
||||
true;
|
||||
is_alpha(Char) when Char >= $A, Char =< $Z ->
|
||||
true;
|
||||
is_alpha(_) ->
|
||||
false.
|
||||
|
||||
+3
-7
@@ -2,7 +2,7 @@
|
||||
|
||||
{application, ejabberd,
|
||||
[{description, "ejabberd"},
|
||||
{vsn, "2.1.0"},
|
||||
{vsn, "2.1.13"},
|
||||
{modules, [acl,
|
||||
adhoc,
|
||||
configure,
|
||||
@@ -10,6 +10,7 @@
|
||||
cyrsasl,
|
||||
cyrsasl_digest,
|
||||
cyrsasl_plain,
|
||||
cyrsasl_scram,
|
||||
ejabberd_admin,
|
||||
ejabberd_app,
|
||||
ejabberd_auth_anonymous,
|
||||
@@ -77,16 +78,12 @@
|
||||
mod_irc,
|
||||
mod_irc_connection,
|
||||
mod_last,
|
||||
mod_last_odbc,
|
||||
mod_muc,
|
||||
mod_muc_log,
|
||||
mod_muc_room,
|
||||
mod_offline,
|
||||
mod_offline_odbc,
|
||||
mod_privacy,
|
||||
mod_privacy_odbc,
|
||||
mod_private,
|
||||
mod_private_odbc,
|
||||
mod_proxy65,
|
||||
mod_proxy65_lib,
|
||||
mod_proxy65_service,
|
||||
@@ -95,14 +92,12 @@
|
||||
mod_pubsub,
|
||||
mod_register,
|
||||
mod_roster,
|
||||
mod_roster_odbc,
|
||||
mod_service_log,
|
||||
mod_shared_roster,
|
||||
mod_stats,
|
||||
mod_time,
|
||||
mod_vcard,
|
||||
mod_vcard_ldap,
|
||||
mod_vcard_odbc,
|
||||
mod_version,
|
||||
node_buddy,
|
||||
node_club,
|
||||
@@ -115,6 +110,7 @@
|
||||
nodetree_virtual,
|
||||
p1_fsm,
|
||||
p1_mnesia,
|
||||
p1_prof,
|
||||
randoms,
|
||||
sha,
|
||||
shaper,
|
||||
|
||||
+86
-37
@@ -5,7 +5,7 @@
|
||||
|
||||
%%% 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/
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
%%% [http_poll, web_admin, tls]
|
||||
%%%
|
||||
%%% - A keyword of ejabberd is a word in lowercase.
|
||||
%%% The strings are enclosed in "" and can have spaces, dots...
|
||||
%%% Strings are enclosed in "" and can contain spaces, dots, ...
|
||||
%%% {language, "en"}.
|
||||
%%% {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"]}.
|
||||
%%%
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
|
||||
%%
|
||||
%% watchdog_admins: Only useful for developers: if an ejabberd process
|
||||
%% consumes a lot of memory, send live notifications to these XMPP
|
||||
%% consumes a lot of memory, send live notifications to these XMPP
|
||||
%% accounts.
|
||||
%%
|
||||
%%{watchdog_admins, ["bob@example.com"]}.
|
||||
@@ -91,9 +91,9 @@
|
||||
{hosts, ["localhost"]}.
|
||||
|
||||
%%
|
||||
%% route_subdomains: Delegate subdomains to other XMPP 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 XMPP server called im.example.org.
|
||||
%% to allow communication with an XMPP server called im.example.org.
|
||||
%%
|
||||
%%{route_subdomains, s2s}.
|
||||
|
||||
@@ -102,8 +102,8 @@
|
||||
%%%' 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,
|
||||
[
|
||||
@@ -111,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,
|
||||
@@ -123,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},
|
||||
@@ -138,7 +138,7 @@
|
||||
]},
|
||||
|
||||
%%
|
||||
%% ejabberd_service: Interact with external components (transports...)
|
||||
%% ejabberd_service: Interact with external components (transports, ...)
|
||||
%%
|
||||
%%{8888, ejabberd_service, [
|
||||
%% {access, all},
|
||||
@@ -162,6 +162,7 @@
|
||||
captcha,
|
||||
http_bind,
|
||||
http_poll,
|
||||
%%register,
|
||||
web_admin
|
||||
]}
|
||||
|
||||
@@ -169,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.
|
||||
@@ -217,6 +218,13 @@
|
||||
%% comment this line and enable the correct ones.
|
||||
%%
|
||||
{auth_method, internal}.
|
||||
%%
|
||||
%% Store the plain passwords or hashed for SCRAM:
|
||||
%%{auth_password_format, plain}.
|
||||
%%{auth_password_format, scram}.
|
||||
%%
|
||||
%% Define the FQDN if ejabberd doesn't detect it:
|
||||
%%{fqdn, "server3.example.com"}.
|
||||
|
||||
%%
|
||||
%% Authentication using external script
|
||||
@@ -249,14 +257,14 @@
|
||||
%%{ldap_encrypt, none}.
|
||||
%%{ldap_encrypt, tls}.
|
||||
%%
|
||||
%% Port connect to LDAP servers:
|
||||
%% Port to connect to on LDAP servers:
|
||||
%%{ldap_port, 389}.
|
||||
%%{ldap_port, 636}.
|
||||
%%
|
||||
%% LDAP manager:
|
||||
%%{ldap_rootdn, "dc=example,dc=com"}.
|
||||
%%
|
||||
%% Password to LDAP manager:
|
||||
%% Password of LDAP manager:
|
||||
%%{ldap_password, "******"}.
|
||||
%%
|
||||
%% Search base of LDAP directory:
|
||||
@@ -286,11 +294,11 @@
|
||||
%%%. ==============
|
||||
%%%' 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:
|
||||
@@ -324,8 +332,8 @@
|
||||
%%{odbc_pool_size, 10}.
|
||||
|
||||
%%
|
||||
%% Interval to make a dummy SQL request to keep alive the connections
|
||||
%% to the database. Specify in seconds: for example 28800 means 8 hours
|
||||
%% 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}.
|
||||
|
||||
@@ -334,22 +342,28 @@
|
||||
%%%' TRAFFIC SHAPERS
|
||||
|
||||
%%
|
||||
%% The "normal" shaper limits traffic speed to 1.000 B/s
|
||||
%% 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
|
||||
|
||||
%%
|
||||
%% The 'admin' ACL grants administrative privileges to XMPP accounts.
|
||||
%% You can put as many accounts as you want.
|
||||
%% You can put here as many accounts as you want.
|
||||
%%
|
||||
%%{acl, admin, {user, "aleksey", "localhost"}}.
|
||||
%%{acl, admin, {user, "ermine", "example.org"}}.
|
||||
@@ -390,7 +404,7 @@
|
||||
{access, max_user_sessions, [{10, all}]}.
|
||||
|
||||
%% Maximum number of offline messages that users can have:
|
||||
{access, max_user_offline_messages, [{5000, admin}, {100, all}]}.
|
||||
{access, max_user_offline_messages, [{5000, admin}, {100, all}]}.
|
||||
|
||||
%% This rule allows access only for local users:
|
||||
{access, local, [{allow, local}]}.
|
||||
@@ -399,41 +413,41 @@
|
||||
{access, c2s, [{deny, blocked},
|
||||
{allow, all}]}.
|
||||
|
||||
%% For C2S connections, all users except admins use "normal" shaper
|
||||
%% For C2S connections, all users except admins use the "normal" shaper
|
||||
{access, c2s_shaper, [{none, admin},
|
||||
{normal, all}]}.
|
||||
|
||||
%% All S2S connections use "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}]}.
|
||||
|
||||
%% Only accounts of the local ejabberd server can create rooms:
|
||||
{access, muc_create, [{allow, local}]}.
|
||||
|
||||
%% All users are allowed to use MUC service:
|
||||
%% All users are allowed to use the MUC service:
|
||||
{access, muc, [{allow, all}]}.
|
||||
|
||||
%% Only accounts in the local ejabberd server can create Pubsub nodes:
|
||||
%% 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}]}.
|
||||
|
||||
%% By default frequency of account registrations from the same IP
|
||||
%% is limited to 1 account every 10 minutes. To disable put: infinity
|
||||
%% 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.
|
||||
%% Define specific Access Rules in a virtual host.
|
||||
%%
|
||||
%%{host_config, "localhost",
|
||||
%% [
|
||||
@@ -468,10 +482,14 @@
|
||||
%%{captcha_cmd, "/lib/ejabberd/priv/bin/captcha.sh"}.
|
||||
|
||||
%%
|
||||
%% Host part of the URL sent to the user.
|
||||
%% 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
|
||||
@@ -483,6 +501,7 @@
|
||||
[
|
||||
{mod_adhoc, []},
|
||||
{mod_announce, [{access, announce}]}, % recommends mod_adhoc
|
||||
{mod_blocking,[]}, % requires mod_privacy
|
||||
{mod_caps, []},
|
||||
{mod_configure,[]}, % requires mod_adhoc
|
||||
{mod_disco, []},
|
||||
@@ -490,7 +509,7 @@
|
||||
{mod_irc, []},
|
||||
{mod_http_bind, []},
|
||||
%%{mod_http_fileserver, [
|
||||
%% {docroot, "/var/www"},
|
||||
%% {docroot, "/var/www"},
|
||||
%% {accesslog, "/var/log/ejabberd/access.log"}
|
||||
%% ]},
|
||||
{mod_last, []},
|
||||
@@ -504,16 +523,28 @@
|
||||
%%{mod_muc_log,[]},
|
||||
{mod_offline, [{access_max_user_messages, max_user_offline_messages}]},
|
||||
{mod_ping, []},
|
||||
%%{mod_pres_counter,[{count, 5}, {interval, 60}]},
|
||||
{mod_privacy, []},
|
||||
{mod_private, []},
|
||||
%%{mod_proxy65,[]},
|
||||
{mod_pubsub, [
|
||||
{access_createnode, pubsub_createnode},
|
||||
{ignore_pep_from_offline, true},
|
||||
{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, [
|
||||
%%
|
||||
%% Protect In-Band account registrations with CAPTCHA.
|
||||
%%
|
||||
%%{captcha_protected, true},
|
||||
|
||||
%%
|
||||
%% Set the minimum informational entropy for passwords.
|
||||
%%
|
||||
%%{password_strength, 32},
|
||||
|
||||
%%
|
||||
%% After successful registration, the user receives
|
||||
%% a message with this subject and body.
|
||||
@@ -527,8 +558,26 @@
|
||||
%%
|
||||
%%{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,[]},
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
%%% Created : 16 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
|
||||
+4
-1
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -37,6 +37,9 @@
|
||||
|
||||
%%-define(DBGFSM, true).
|
||||
|
||||
-record(scram, {storedkey, serverkey, salt, iterationcount}).
|
||||
-define(SCRAM_DEFAULT_ITERATION_COUNT, 4096).
|
||||
|
||||
%% ---------------------------------
|
||||
%% Logging mechanism
|
||||
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
#! /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
|
||||
set -o nounset
|
||||
|
||||
DIR=@ctlscriptpath@
|
||||
CTL="$DIR"/ejabberdctl
|
||||
@@ -32,14 +46,17 @@ case "$1" in
|
||||
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}"
|
||||
echo "Usage: $0 {start|stop|restart|force-reload|status}"
|
||||
exit 1
|
||||
esac
|
||||
|
||||
|
||||
+157
-9
@@ -5,7 +5,7 @@
|
||||
%%% Created : 7 May 2006 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -30,6 +30,10 @@
|
||||
-export([start/0, stop/0,
|
||||
%% Server
|
||||
status/0, reopen_log/0,
|
||||
stop_kindly/2, send_service_message_all_mucs/2,
|
||||
registered_vhosts/0,
|
||||
%% Erlang
|
||||
update_list/0, update/1,
|
||||
%% Accounts
|
||||
register/3, unregister/2,
|
||||
registered_users/1,
|
||||
@@ -38,6 +42,8 @@
|
||||
%% Purge DB
|
||||
delete_expired_messages/0, delete_old_messages/1,
|
||||
%% Mnesia
|
||||
export2odbc/2,
|
||||
set_master/1,
|
||||
backup_mnesia/1, restore_mnesia/1,
|
||||
dump_mnesia/1, dump_table/2, load_mnesia/1,
|
||||
install_fallback_mnesia/1,
|
||||
@@ -79,6 +85,15 @@ commands() ->
|
||||
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",
|
||||
longdesc = "Provide the delay in seconds, and the "
|
||||
"announcement quoted, for example: \n"
|
||||
"ejabberdctl stop_kindly 60 "
|
||||
"\\\"The server will stop in one minute.\\\"",
|
||||
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,
|
||||
@@ -88,6 +103,17 @@ commands() ->
|
||||
{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,
|
||||
@@ -103,6 +129,11 @@ commands() ->
|
||||
module = ?MODULE, function = registered_users,
|
||||
args = [{host, string}],
|
||||
result = {users, {list, {username, string}}}},
|
||||
#ejabberd_commands{name = registered_vhosts, tags = [server],
|
||||
desc = "List all registered vhosts in SERVER",
|
||||
module = ?MODULE, function = registered_vhosts,
|
||||
args = [],
|
||||
result = {vhosts, {list, {vhost, string}}}},
|
||||
|
||||
#ejabberd_commands{name = import_file, tags = [mnesia],
|
||||
desc = "Import user data from jabberd14 spool file",
|
||||
@@ -137,10 +168,21 @@ commands() ->
|
||||
args = [{days, integer}], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = rename_default_nodeplugin, tags = [mnesia],
|
||||
desc = "Update PubSub table from ejabberd trunk SVN to 2.1.0",
|
||||
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 = export2odbc, tags = [mnesia],
|
||||
desc = "Export virtual host information from Mnesia tables to SQL files",
|
||||
module = ?MODULE, function = export2odbc,
|
||||
args = [{host, string}, {directory, string}],
|
||||
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,
|
||||
@@ -215,6 +257,76 @@ get_sasl_error_logger_type () ->
|
||||
_ -> 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 = (catch 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()],
|
||||
{ok, []};
|
||||
update(ModStr) ->
|
||||
update_module(ModStr).
|
||||
|
||||
update_module(ModuleNameString) ->
|
||||
ModuleName = list_to_atom(ModuleNameString),
|
||||
case ejabberd_update:update([ModuleName]) of
|
||||
{ok, _Res} -> {ok, []};
|
||||
{error, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% Account management
|
||||
%%%
|
||||
@@ -222,7 +334,7 @@ get_sasl_error_logger_type () ->
|
||||
register(User, Host, Password) ->
|
||||
case ejabberd_auth:try_register(User, Host, Password) of
|
||||
{atomic, ok} ->
|
||||
{ok, io_lib:format("User ~s@~s succesfully registered", [User, Host])};
|
||||
{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()]),
|
||||
@@ -242,6 +354,8 @@ registered_users(Host) ->
|
||||
SUsers = lists:sort(Users),
|
||||
lists:map(fun({U, _S}) -> U end, SUsers).
|
||||
|
||||
registered_vhosts() ->
|
||||
?MYHOSTS.
|
||||
|
||||
%%%
|
||||
%%% Migration management
|
||||
@@ -273,18 +387,52 @@ import_dir(Path) ->
|
||||
%%%
|
||||
|
||||
delete_expired_messages() ->
|
||||
{atomic, ok} = mod_offline:remove_expired_messages(),
|
||||
ok.
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
{atomic, ok} = mod_offline:remove_expired_messages(Host)
|
||||
end, ?MYHOSTS).
|
||||
|
||||
delete_old_messages(Days) ->
|
||||
{atomic, _} = mod_offline:remove_old_messages(Days),
|
||||
ok.
|
||||
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
{atomic, _} = mod_offline:remove_old_messages(Days, Host)
|
||||
end, ?MYHOSTS).
|
||||
|
||||
%%%
|
||||
%%% Mnesia management
|
||||
%%%
|
||||
|
||||
export2odbc(Host, Directory) ->
|
||||
Tables = [{export_last, last},
|
||||
{export_offline, offline},
|
||||
{export_private_storage, private_storage},
|
||||
{export_roster, roster},
|
||||
{export_vcard, vcard},
|
||||
{export_vcard_search, vcard_search},
|
||||
{export_passwd, passwd}],
|
||||
Export = fun({TableFun, Table}) ->
|
||||
Filename = filename:join([Directory, atom_to_list(Table)++".txt"]),
|
||||
io:format("Trying to export Mnesia table '~p' on Host '~s' to file '~s'~n", [Table, Host, Filename]),
|
||||
Res = (catch ejd2odbc:TableFun(Host, Filename)),
|
||||
io:format(" Result: ~p~n", [Res])
|
||||
end,
|
||||
lists:foreach(Export, Tables),
|
||||
ok.
|
||||
|
||||
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 ->
|
||||
@@ -325,7 +473,7 @@ restore(Path) ->
|
||||
%% Obsolete tables or tables created by module who are no longer used are not
|
||||
%% restored and are ignored.
|
||||
keep_tables() ->
|
||||
lists:flatten([acl, passwd, config, local_config, disco_publish,
|
||||
lists:flatten([acl, passwd, config, local_config,
|
||||
keep_modules_tables()]).
|
||||
|
||||
%% Returns the list of modules tables in use, according to the list of actually
|
||||
|
||||
+16
-3
@@ -5,7 +5,7 @@
|
||||
%%% Created : 31 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -46,6 +46,7 @@ start(normal, _Args) ->
|
||||
db_init(),
|
||||
sha:start(),
|
||||
stringprep_sup:start_link(),
|
||||
xml:start(),
|
||||
start(),
|
||||
translate:start(),
|
||||
acl:start(),
|
||||
@@ -56,6 +57,8 @@ start(normal, _Args) ->
|
||||
ejabberd_config: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(),
|
||||
@@ -75,15 +78,18 @@ start(_, _) ->
|
||||
%% This function is called when an application is about to be stopped,
|
||||
%% before shutting down the processes of the application.
|
||||
prep_stop(State) ->
|
||||
ejabberd_listener:stop_listeners(),
|
||||
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(),
|
||||
%%ejabberd_debug:stop(),
|
||||
ok.
|
||||
|
||||
|
||||
@@ -105,7 +111,7 @@ init() ->
|
||||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, expat_erl}, [binary]),
|
||||
Port = open_port({spawn, "expat_erl"}, [binary]),
|
||||
loop(Port).
|
||||
|
||||
|
||||
@@ -198,6 +204,13 @@ add_windows_nameservers() ->
|
||||
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
|
||||
%%%
|
||||
|
||||
+61
-12
@@ -5,7 +5,7 @@
|
||||
%%% Created : 23 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -49,7 +49,9 @@
|
||||
is_user_exists_in_other_modules/3,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
plain_password_required/1
|
||||
plain_password_required/1,
|
||||
store_type/1,
|
||||
entropy/1
|
||||
]).
|
||||
|
||||
-export([auth_modules/1]).
|
||||
@@ -68,12 +70,28 @@ start() ->
|
||||
end, auth_modules(Host))
|
||||
end, ?MYHOSTS).
|
||||
|
||||
%% This is only executed by ejabberd_c2s for non-SASL auth client
|
||||
plain_password_required(Server) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
M:plain_password_required()
|
||||
end, auth_modules(Server)).
|
||||
|
||||
store_type(Server) ->
|
||||
lists:foldl(
|
||||
fun(_, external) ->
|
||||
external;
|
||||
(M, scram) ->
|
||||
case M:store_type() of
|
||||
external ->
|
||||
external;
|
||||
_Else ->
|
||||
scram
|
||||
end;
|
||||
(M, plain) ->
|
||||
M:store_type()
|
||||
end, plain, auth_modules(Server)).
|
||||
|
||||
%% @doc Check if the user and password can login in server.
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% true | false
|
||||
@@ -231,8 +249,10 @@ get_password_s(User, Server) ->
|
||||
case get_password(User, Server) of
|
||||
false ->
|
||||
"";
|
||||
Password ->
|
||||
Password
|
||||
Password when is_list(Password) ->
|
||||
Password;
|
||||
_ ->
|
||||
""
|
||||
end.
|
||||
|
||||
%% @doc Get the password of the user and the auth module.
|
||||
@@ -251,7 +271,16 @@ get_password_with_authmodule(User, Server) ->
|
||||
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
|
||||
@@ -277,19 +306,16 @@ is_user_exists_in_other_modules_loop([AuthModule|AuthModules], User, Server) ->
|
||||
end.
|
||||
|
||||
|
||||
%% @spec (User, Server) -> ok | error | {error, not_allowed}
|
||||
%% @spec (User, Server) -> ok
|
||||
%% @doc Remove user.
|
||||
%% Note: it may return ok even if there was some problem removing the user.
|
||||
remove_user(User, Server) ->
|
||||
R = lists:foreach(
|
||||
lists:foreach(
|
||||
fun(M) ->
|
||||
M:remove_user(User, Server)
|
||||
end, auth_modules(Server)),
|
||||
case R of
|
||||
ok -> ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]);
|
||||
_ -> none
|
||||
end,
|
||||
R.
|
||||
ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]),
|
||||
ok.
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error
|
||||
%% @doc Try to remove user if the provided password is correct.
|
||||
@@ -309,6 +335,29 @@ remove_user(User, Server, Password) ->
|
||||
end,
|
||||
R.
|
||||
|
||||
%% @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
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 17 Feb 2006 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -51,6 +51,7 @@
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
plain_password_required/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -79,7 +80,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 +98,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 +116,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) ->
|
||||
@@ -145,6 +144,7 @@ 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})
|
||||
@@ -176,7 +176,7 @@ check_password(User, Server, Password) ->
|
||||
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;
|
||||
@@ -217,8 +217,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
|
||||
@@ -249,3 +249,5 @@ remove_user(_User, _Server, _Password) ->
|
||||
plain_password_required() ->
|
||||
false.
|
||||
|
||||
store_type() ->
|
||||
plain.
|
||||
|
||||
+233
-19
@@ -5,7 +5,7 @@
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -35,53 +35,102 @@
|
||||
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,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
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.
|
||||
|
||||
store_type() ->
|
||||
external.
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
extauth:check_password(User, Server, Password) andalso 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, _Digest, _DigestGen) ->
|
||||
check_password(User, Server, Password).
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
case extauth:set_password(User, Server, Password) of
|
||||
true -> ok;
|
||||
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.
|
||||
|
||||
%% TODO
|
||||
%% Return the list of all users handled by external
|
||||
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) ->
|
||||
@@ -91,9 +140,174 @@ is_user_exists(User, Server) ->
|
||||
_: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);
|
||||
no_mod_last -> mod_last_required
|
||||
end.
|
||||
|
||||
%% @spec (Server) -> mod_last | no_mod_last
|
||||
get_mod_last_enabled(Server) ->
|
||||
case gen_mod:is_loaded(Server, mod_last) of
|
||||
true -> mod_last;
|
||||
false -> no_mod_last
|
||||
end.
|
||||
|
||||
get_mod_last_configured(Server) ->
|
||||
case is_configured(Server, mod_last) of
|
||||
true -> mod_last;
|
||||
false -> no_mod_last
|
||||
end.
|
||||
|
||||
is_configured(Host, Module) ->
|
||||
lists:keymember(Module, 1, ejabberd_config:get_local_option({modules, Host})).
|
||||
|
||||
+138
-50
@@ -5,7 +5,7 @@
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -43,6 +43,7 @@
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
@@ -51,6 +52,8 @@
|
||||
-record(passwd, {us, password}).
|
||||
-record(reg_users_counter, {vhost, count}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -62,24 +65,40 @@ start(Host) ->
|
||||
{attributes, record_info(fields, reg_users_counter)}]),
|
||||
update_table(),
|
||||
update_reg_users_counter_table(Host),
|
||||
maybe_alert_password_scrammed_without_option(),
|
||||
ok.
|
||||
|
||||
update_reg_users_counter_table(Server) ->
|
||||
Set = get_vh_registered_users(Server),
|
||||
Size = length(Set),
|
||||
LServer = jlib:nameprep(Server),
|
||||
set_vh_registered_users_counter(LServer, Size).
|
||||
F = fun() ->
|
||||
mnesia:write(#reg_users_counter{vhost = LServer,
|
||||
count = Size})
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
plain_password_required() ->
|
||||
false.
|
||||
case is_scrammed() of
|
||||
false -> false;
|
||||
true -> true
|
||||
end.
|
||||
|
||||
store_type() ->
|
||||
case is_scrammed() of
|
||||
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
|
||||
true -> scram %% allows: PLAIN SCRAM
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Password}] ->
|
||||
[#passwd{password = Password}] when is_list(Password) ->
|
||||
Password /= "";
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
is_password_scram_valid(Password, Scram);
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
@@ -89,7 +108,20 @@ check_password(User, Server, Password, Digest, DigestGen) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Passwd}] ->
|
||||
[#passwd{password = Passwd}] when is_list(Passwd) ->
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true ->
|
||||
false
|
||||
end,
|
||||
if DigRes ->
|
||||
true;
|
||||
true ->
|
||||
(Passwd == Password) and (Password /= "")
|
||||
end;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
Passwd = base64:decode(Scram#scram.storedkey),
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
Digest == DigestGen(Passwd);
|
||||
@@ -116,8 +148,12 @@ set_password(User, Server, Password) ->
|
||||
{error, invalid_jid};
|
||||
true ->
|
||||
F = fun() ->
|
||||
Password2 = case is_scrammed() and is_list(Password) of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
mnesia:write(#passwd{us = US,
|
||||
password = Password})
|
||||
password = Password2})
|
||||
end,
|
||||
{atomic, ok} = mnesia:transaction(F),
|
||||
ok
|
||||
@@ -135,9 +171,15 @@ try_register(User, Server, Password) ->
|
||||
F = fun() ->
|
||||
case mnesia:read({passwd, US}) of
|
||||
[] ->
|
||||
Password2 = case is_scrammed() and is_list(Password) of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
mnesia:write(#passwd{us = US,
|
||||
password = Password}),
|
||||
inc_vh_registered_users_counter(LServer),
|
||||
password = Password2}),
|
||||
mnesia:dirty_update_counter(
|
||||
reg_users_counter,
|
||||
LServer, 1),
|
||||
ok;
|
||||
[_E] ->
|
||||
exists
|
||||
@@ -224,47 +266,18 @@ get_vh_registered_users_number(Server, [{prefix, Prefix}]) when is_list(Prefix)
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
|
||||
inc_vh_registered_users_counter(LServer) ->
|
||||
F = fun() ->
|
||||
case mnesia:wread({reg_users_counter, LServer}) of
|
||||
[C] ->
|
||||
Count = C#reg_users_counter.count + 1,
|
||||
C2 = C#reg_users_counter{count = Count},
|
||||
mnesia:write(C2);
|
||||
_ ->
|
||||
mnesia:write(#reg_users_counter{vhost = LServer,
|
||||
count = 1})
|
||||
end
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
dec_vh_registered_users_counter(LServer) ->
|
||||
F = fun() ->
|
||||
case mnesia:wread({reg_users_counter, LServer}) of
|
||||
[C] ->
|
||||
Count = C#reg_users_counter.count - 1,
|
||||
C2 = C#reg_users_counter{count = Count},
|
||||
mnesia:write(C2);
|
||||
_ ->
|
||||
error
|
||||
end
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
set_vh_registered_users_counter(LServer, Count) ->
|
||||
F = fun() ->
|
||||
mnesia:write(#reg_users_counter{vhost = LServer,
|
||||
count = Count})
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
get_password(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read(passwd, US) of
|
||||
[#passwd{password = Password}] ->
|
||||
[#passwd{password = Password}] when is_list(Password) ->
|
||||
Password;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
{base64:decode(Scram#scram.storedkey),
|
||||
base64:decode(Scram#scram.serverkey),
|
||||
base64:decode(Scram#scram.salt),
|
||||
Scram#scram.iterationcount};
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
@@ -274,8 +287,10 @@ get_password_s(User, Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read(passwd, US) of
|
||||
[#passwd{password = Password}] ->
|
||||
[#passwd{password = Password}] when is_list(Password) ->
|
||||
Password;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
[];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
@@ -303,7 +318,8 @@ remove_user(User, Server) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:delete({passwd, US}),
|
||||
dec_vh_registered_users_counter(LServer)
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, -1)
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
ok.
|
||||
@@ -316,12 +332,21 @@ remove_user(User, Server, Password) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
case mnesia:read({passwd, US}) of
|
||||
[#passwd{password = Password}] ->
|
||||
[#passwd{password = Password}] when is_list(Password) ->
|
||||
mnesia:delete({passwd, US}),
|
||||
dec_vh_registered_users_counter(LServer),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, -1),
|
||||
ok;
|
||||
[_] ->
|
||||
not_allowed;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
case is_password_scram_valid(Password, Scram) of
|
||||
true ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, -1),
|
||||
ok;
|
||||
false ->
|
||||
not_allowed
|
||||
end;
|
||||
_ ->
|
||||
not_exists
|
||||
end
|
||||
@@ -335,11 +360,11 @@ remove_user(User, Server, Password) ->
|
||||
bad_request
|
||||
end.
|
||||
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, passwd),
|
||||
case mnesia:table_info(passwd, attributes) of
|
||||
Fields ->
|
||||
maybe_scram_passwords(),
|
||||
ok;
|
||||
[user, password] ->
|
||||
?INFO_MSG("Converting passwd table from "
|
||||
@@ -378,5 +403,68 @@ update_table() ->
|
||||
mnesia:transform_table(passwd, ignore, Fields)
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% SCRAM
|
||||
%%%
|
||||
|
||||
%% The passwords are stored scrammed in the table either if the option says so,
|
||||
%% or if at least the first password is scrammed.
|
||||
is_scrammed() ->
|
||||
OptionScram = is_option_scram(),
|
||||
FirstElement = mnesia:dirty_read(passwd, mnesia:dirty_first(passwd)),
|
||||
case {OptionScram, FirstElement} of
|
||||
{true, _} ->
|
||||
true;
|
||||
{false, [#passwd{password = Scram}]} when is_record(Scram, scram) ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
is_option_scram() ->
|
||||
scram == ejabberd_config:get_local_option({auth_password_format, ?MYNAME}).
|
||||
|
||||
maybe_alert_password_scrammed_without_option() ->
|
||||
case is_scrammed() andalso not is_option_scram() of
|
||||
true ->
|
||||
?ERROR_MSG("Some passwords were stored in the database as SCRAM, "
|
||||
"but 'auth_password_format' is not configured 'scram'. "
|
||||
"The option will now be considered to be 'scram'.", []);
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
maybe_scram_passwords() ->
|
||||
case is_scrammed() of
|
||||
true -> scram_passwords();
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
scram_passwords() ->
|
||||
?INFO_MSG("Converting the stored passwords into SCRAM bits", []),
|
||||
Fun = fun(#passwd{password = Password} = P) ->
|
||||
Scram = password_to_scram(Password),
|
||||
P#passwd{password = Scram}
|
||||
end,
|
||||
Fields = record_info(fields, passwd),
|
||||
mnesia:transform_table(passwd, Fun, Fields).
|
||||
|
||||
password_to_scram(Password) ->
|
||||
password_to_scram(Password, ?SCRAM_DEFAULT_ITERATION_COUNT).
|
||||
|
||||
password_to_scram(Password, IterationCount) ->
|
||||
Salt = crypto:rand_bytes(?SALT_LENGTH),
|
||||
SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
|
||||
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
ServerKey = scram:server_key(SaltedPassword),
|
||||
#scram{storedkey = base64:encode(StoredKey),
|
||||
serverkey = base64:encode(ServerKey),
|
||||
salt = base64:encode(Salt),
|
||||
iterationcount = IterationCount}.
|
||||
|
||||
is_password_scram_valid(Password, Scram) ->
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
Salt = base64:decode(Scram#scram.salt),
|
||||
SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
|
||||
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
(base64:decode(Scram#scram.storedkey) == StoredKey).
|
||||
|
||||
+78
-35
@@ -5,7 +5,7 @@
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -54,6 +54,7 @@
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
@@ -66,7 +67,7 @@
|
||||
servers,
|
||||
backups,
|
||||
port,
|
||||
encrypt,
|
||||
tls_options,
|
||||
dn,
|
||||
password,
|
||||
base,
|
||||
@@ -74,6 +75,7 @@
|
||||
ufilter,
|
||||
sfilter,
|
||||
lfilter, %% Local filter (performed by ejabberd, not LDAP)
|
||||
deref_aliases,
|
||||
dn_filter,
|
||||
dn_filter_attrs
|
||||
}).
|
||||
@@ -119,24 +121,27 @@ terminate(_Reason, _State) ->
|
||||
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.encrypt),
|
||||
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,
|
||||
State#state.encrypt),
|
||||
State#state.servers,
|
||||
State#state.backups,
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password,
|
||||
State#state.tls_options),
|
||||
{ok, State}.
|
||||
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
store_type() ->
|
||||
external.
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
%% In LDAP spec: empty password means anonymous authentication.
|
||||
%% As ejabberd is providing other anonymous authentication mechanisms
|
||||
@@ -153,8 +158,14 @@ check_password(User, Server, Password) ->
|
||||
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) ->
|
||||
@@ -217,13 +228,15 @@ 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},
|
||||
{timeout, ?LDAP_SEARCH_TIMEOUT},
|
||||
{attributes, SortedDNAttrs}]) of
|
||||
case eldap_pool:search(Eldap_ID,
|
||||
[{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{timeout, ?LDAP_SEARCH_TIMEOUT},
|
||||
{deref_aliases, State#state.deref_aliases},
|
||||
{attributes, ResAttrs}]) of
|
||||
#eldap_search_result{entries = Entries} ->
|
||||
lists:flatmap(
|
||||
fun(#eldap_entry{attributes = Attrs,
|
||||
@@ -269,15 +282,17 @@ 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},
|
||||
{deref_aliases, State#state.deref_aliases},
|
||||
{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;
|
||||
@@ -311,10 +326,11 @@ is_valid_dn(DN, Attrs, State) ->
|
||||
end ++ [{"%d", State#state.host}, {"%D", DN}],
|
||||
case eldap_filter:parse(State#state.dn_filter, SubstValues) of
|
||||
{ok, EldapFilter} ->
|
||||
case eldap_pool:search(State#state.eldap_id, [
|
||||
{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{attributes, ["dn"]}]) of
|
||||
case eldap_pool:search(State#state.eldap_id,
|
||||
[{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{deref_aliases, State#state.deref_aliases},
|
||||
{attributes, ["dn"]}]) of
|
||||
#eldap_search_result{entries = [_|_]} ->
|
||||
DN;
|
||||
_ ->
|
||||
@@ -346,6 +362,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
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -358,6 +382,9 @@ parse_options(Host) ->
|
||||
Backups -> Backups
|
||||
end,
|
||||
LDAPEncrypt = ejabberd_config:get_local_option({ldap_encrypt, Host}),
|
||||
LDAPTLSVerify = ejabberd_config:get_local_option({ldap_tls_verify, Host}),
|
||||
LDAPTLSCAFile = ejabberd_config:get_local_option({ldap_tls_cacertfile, Host}),
|
||||
LDAPTLSDepth = ejabberd_config:get_local_option({ldap_tls_depth, Host}),
|
||||
LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of
|
||||
undefined -> case LDAPEncrypt of
|
||||
tls -> ?LDAPS_PORT;
|
||||
@@ -382,23 +409,38 @@ parse_options(Host) ->
|
||||
UserFilter = case ejabberd_config:get_local_option({ldap_filter, Host}) of
|
||||
undefined -> SubFilter;
|
||||
"" -> SubFilter;
|
||||
F -> "(&" ++ SubFilter ++ F ++ ")"
|
||||
F ->
|
||||
eldap_utils:check_filter(F),
|
||||
"(&" ++ SubFilter ++ F ++ ")"
|
||||
end,
|
||||
SearchFilter = eldap_filter:do_sub(UserFilter, [{"%u", "*"}]),
|
||||
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}),
|
||||
eldap_utils:check_filter(DNFilter),
|
||||
LocalFilter = ejabberd_config:get_local_option({ldap_local_filter, Host}),
|
||||
DerefAliases = case ejabberd_config:get_local_option(
|
||||
{ldap_deref_aliases, Host}) of
|
||||
undefined -> never;
|
||||
Val -> Val
|
||||
end,
|
||||
#state{host = Host,
|
||||
eldap_id = Eldap_ID,
|
||||
bind_eldap_id = Bind_Eldap_ID,
|
||||
servers = LDAPServers,
|
||||
backups = LDAPBackups,
|
||||
port = LDAPPort,
|
||||
encrypt = LDAPEncrypt,
|
||||
tls_options = [{encrypt, LDAPEncrypt},
|
||||
{tls_verify, LDAPTLSVerify},
|
||||
{tls_cacertfile, LDAPTLSCAFile},
|
||||
{tls_depth, LDAPTLSDepth}],
|
||||
dn = RootDN,
|
||||
password = Password,
|
||||
base = LDAPBase,
|
||||
@@ -406,6 +448,7 @@ parse_options(Host) ->
|
||||
ufilter = UserFilter,
|
||||
sfilter = SearchFilter,
|
||||
lfilter = LocalFilter,
|
||||
deref_aliases = DerefAliases,
|
||||
dn_filter = DNFilter,
|
||||
dn_filter_attrs = DNFilterAttrs
|
||||
}.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -43,6 +43,7 @@
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
@@ -57,6 +58,9 @@ start(_Host) ->
|
||||
plain_password_required() ->
|
||||
false.
|
||||
|
||||
store_type() ->
|
||||
plain.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false | {error, Error}
|
||||
check_password(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 5 Jul 2007 by Evgeniy Khramtsov <xram@jabber.ru>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -39,6 +39,7 @@
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
@@ -60,7 +61,11 @@ check_password(User, Server, Password, _Digest, _DigestGen) ->
|
||||
|
||||
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.
|
||||
@@ -84,7 +89,11 @@ get_password_s(_User, _Server) ->
|
||||
%% 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.
|
||||
@@ -98,6 +107,9 @@ remove_user(_User, _Server, _Password) ->
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
store_type() ->
|
||||
external.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
@@ -106,3 +118,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.
|
||||
|
||||
+444
-238
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
||||
%%% Created : 2 Nov 2007 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
|
||||
+224
-45
@@ -5,7 +5,7 @@
|
||||
%%% Created : 26 Apr 2008 by Evgeniy Khramtsov <xramtsov@gmail.com>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -36,7 +36,8 @@
|
||||
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]).
|
||||
process_reply/1, process/2, is_feature_available/0,
|
||||
create_captcha_x/5, create_captcha_x/6]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
@@ -48,8 +49,9 @@
|
||||
|
||||
-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, {}).
|
||||
-record(state, {limits = treap:empty()}).
|
||||
-record(captcha, {id, pid, key, tref, args}).
|
||||
|
||||
-define(T(S),
|
||||
@@ -71,11 +73,12 @@
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
create_captcha(Id, SID, From, To, Lang, Args)
|
||||
when is_list(Id), is_list(Lang), is_list(SID),
|
||||
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() of
|
||||
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",
|
||||
@@ -91,7 +94,8 @@ create_captcha(Id, SID, From, To, Lang, Args)
|
||||
?VFIELD("hidden", "challenge", {xmlcdata, Id}),
|
||||
?VFIELD("hidden", "sid", {xmlcdata, SID}),
|
||||
{xmlelement, "field", [{"var", "ocr"}, {"label", ?CAPTCHA_TEXT(Lang)}],
|
||||
[{xmlelement, "media", [{"xmlns", ?NS_MEDIA}],
|
||||
[{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"),
|
||||
@@ -104,12 +108,61 @@ create_captcha(Id, SID, From, To, Lang, Args)
|
||||
case ?T(mnesia:write(#captcha{id=Id, pid=self(), key=Key,
|
||||
tref=Tref, args=Args})) of
|
||||
ok ->
|
||||
{ok, [Body, OOB, Captcha, Data]};
|
||||
_Err ->
|
||||
error
|
||||
{ok, Id, [Body, OOB, Captcha, Data]};
|
||||
Err ->
|
||||
{error, Err}
|
||||
end;
|
||||
_Err ->
|
||||
error
|
||||
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
|
||||
@@ -155,10 +208,18 @@ check_captcha(Id, ProvidedKey) ->
|
||||
mnesia:delete({captcha, Id}),
|
||||
erlang:cancel_timer(Tref),
|
||||
if StoredKey == ProvidedKey ->
|
||||
Pid ! {captcha_succeed, Args},
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_succeed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
captcha_valid;
|
||||
true ->
|
||||
Pid ! {captcha_failed, Args},
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
captcha_non_valid
|
||||
end;
|
||||
_ ->
|
||||
@@ -166,24 +227,32 @@ check_captcha(Id, ProvidedKey) ->
|
||||
end).
|
||||
|
||||
|
||||
process_reply({xmlelement, "captcha", _, _} = El) ->
|
||||
process_reply({xmlelement, _, _, _} = El) ->
|
||||
case xml:get_subtag(El, "x") of
|
||||
false ->
|
||||
{error, malformed};
|
||||
Xdata ->
|
||||
Fields = jlib:parse_xdata_submit(Xdata),
|
||||
case {proplists:get_value("challenge", Fields),
|
||||
proplists:get_value("ocr", Fields)} of
|
||||
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 ->
|
||||
Pid ! {captcha_succeed, Args},
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_succeed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
ok;
|
||||
true ->
|
||||
Pid ! {captcha_failed, Args},
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{error, bad_match}
|
||||
end;
|
||||
_ ->
|
||||
@@ -208,16 +277,19 @@ process(_Handlers, #request{method='GET', lang=Lang, path=[_, Id]}) ->
|
||||
ejabberd_web:error(not_found)
|
||||
end;
|
||||
|
||||
process(_Handlers, #request{method='GET', path=[_, Id, "image"]}) ->
|
||||
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(Key) of
|
||||
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;
|
||||
@@ -232,7 +304,7 @@ process(_Handlers, #request{method='POST', q=Q, lang=Lang, path=[_, Id]}) ->
|
||||
Form =
|
||||
{xmlelement, "p", [],
|
||||
[{xmlcdata,
|
||||
translate:translate(Lang, "The captcha is valid.")
|
||||
translate:translate(Lang, "The CAPTCHA is valid.")
|
||||
}]},
|
||||
ejabberd_web:make_xhtml([Form]);
|
||||
captcha_non_valid ->
|
||||
@@ -256,6 +328,20 @@ init([]) ->
|
||||
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}.
|
||||
|
||||
@@ -266,7 +352,11 @@ 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}] ->
|
||||
Pid ! {captcha_failed, Args},
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
mnesia:delete({captcha, Id});
|
||||
_ ->
|
||||
ok
|
||||
@@ -293,11 +383,22 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%% 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(Key).
|
||||
create_image(Limiter, Key).
|
||||
|
||||
create_image(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
|
||||
@@ -327,18 +428,82 @@ 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) ->
|
||||
case ejabberd_config:get_local_option(captcha_host) of
|
||||
Host when is_list(Host) ->
|
||||
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()
|
||||
@@ -387,27 +552,41 @@ return(Port, TRef, Result) ->
|
||||
catch port_close(Port),
|
||||
Result.
|
||||
|
||||
is_feature_enabled() ->
|
||||
case get_prog_name() of
|
||||
"" -> false;
|
||||
Prog when is_list(Prog) -> true
|
||||
end.
|
||||
|
||||
is_feature_available() ->
|
||||
case is_feature_enabled() of
|
||||
false -> false;
|
||||
true ->
|
||||
case create_image() of
|
||||
{ok, _, _, _} -> true;
|
||||
_Error -> false
|
||||
end
|
||||
case get_prog_name() of
|
||||
Prog when is_list(Prog) -> true;
|
||||
false -> false
|
||||
end.
|
||||
|
||||
check_captcha_setup() ->
|
||||
case is_feature_enabled() andalso not is_feature_available() of
|
||||
case is_feature_available() of
|
||||
true ->
|
||||
?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, "
|
||||
"but it can't generate images.", []);
|
||||
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).
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 27 Feb 2008 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
|
||||
+10
-15
@@ -5,7 +5,7 @@
|
||||
%%% Created : 20 May 2008 by Badlop <badlop@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -356,15 +356,14 @@ get_tags_commands() ->
|
||||
%% @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 | no_auth_provided
|
||||
%% Error = account_unprivileged | invalid_account_data
|
||||
check_access_commands([], _Auth, _Method, _Command, _Arguments) ->
|
||||
ok;
|
||||
check_access_commands(AccessCommands, Auth, Method, Command, Arguments) ->
|
||||
{ok, User, Server} = check_auth(Auth),
|
||||
AccessCommandsAllowed =
|
||||
lists:filter(
|
||||
fun({Access, Commands, ArgumentRestrictions}) ->
|
||||
case check_access(Access, User, Server) of
|
||||
case check_access(Access, Auth) of
|
||||
true ->
|
||||
check_access_command(Commands, Command, ArgumentRestrictions,
|
||||
Method, Arguments);
|
||||
@@ -379,22 +378,18 @@ check_access_commands(AccessCommands, Auth, Method, Command, Arguments) ->
|
||||
end.
|
||||
|
||||
check_auth(noauth) ->
|
||||
throw({error, no_auth_provided});
|
||||
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};
|
||||
case ejabberd_auth:check_password(User, Server, Password) of
|
||||
true -> {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(Access, User, Server) ->
|
||||
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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
|
||||
+93
-9
@@ -5,7 +5,7 @@
|
||||
%%% Created : 14 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -101,15 +101,14 @@ get_plain_terms_file(File1) ->
|
||||
case file:consult(File) of
|
||||
{ok, Terms} ->
|
||||
include_config_files(Terms);
|
||||
{error, {_LineNumber, erl_parse, _ParseMessage} = Reason} ->
|
||||
ExitText = lists:flatten(File ++ " approximately in the line "
|
||||
++ file:format_error(Reason)),
|
||||
?ERROR_MSG("Problem loading ejabberd config file ~n~s", [ExitText]),
|
||||
exit(ExitText);
|
||||
{error, {LineNumber, erl_parse, _ParseMessage} = Reason} ->
|
||||
ExitText = describe_config_problem(File, Reason, LineNumber),
|
||||
?ERROR_MSG(ExitText, []),
|
||||
exit_or_halt(ExitText);
|
||||
{error, Reason} ->
|
||||
ExitText = lists:flatten(File ++ ": " ++ file:format_error(Reason)),
|
||||
?ERROR_MSG("Problem loading ejabberd config file ~n~s", [ExitText]),
|
||||
exit(ExitText)
|
||||
ExitText = describe_config_problem(File, Reason),
|
||||
?ERROR_MSG(ExitText, []),
|
||||
exit_or_halt(ExitText)
|
||||
end.
|
||||
|
||||
%% @doc Convert configuration filename to absolute path.
|
||||
@@ -170,6 +169,57 @@ normalize_hosts([Host|Hosts], 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'
|
||||
|
||||
@@ -324,6 +374,9 @@ process_term(Term, State) ->
|
||||
State;
|
||||
{hosts, _Hosts} ->
|
||||
State;
|
||||
{fqdn, HostFQDN} ->
|
||||
?DEBUG("FQDN set to: ~p", [HostFQDN]),
|
||||
add_option(fqdn, HostFQDN, State);
|
||||
{host_config, Host, Terms} ->
|
||||
lists:foldl(fun(T, S) -> process_host_term(T, Host, S) end,
|
||||
State, Terms);
|
||||
@@ -381,6 +434,8 @@ process_term(Term, State) ->
|
||||
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} ->
|
||||
@@ -412,6 +467,8 @@ process_host_term(Term, Host, State) ->
|
||||
State;
|
||||
{odbc_server, ODBC_server} ->
|
||||
add_option({odbc_server, Host}, ODBC_server, State);
|
||||
{modules, Modules} ->
|
||||
add_option({modules, Host}, replace_modules(Modules), State);
|
||||
{Opt, Val} ->
|
||||
add_option({Opt, Host}, Val, State)
|
||||
end.
|
||||
@@ -555,3 +612,30 @@ is_file_readable(Path) ->
|
||||
{error, _Reason} ->
|
||||
false
|
||||
end.
|
||||
|
||||
replace_module(mod_announce_odbc) -> {mod_announce, odbc};
|
||||
replace_module(mod_blocking_odbc) -> {mod_blocking, odbc};
|
||||
replace_module(mod_irc_odbc) -> {mod_irc, odbc};
|
||||
replace_module(mod_last_odbc) -> {mod_last, odbc};
|
||||
replace_module(mod_muc_odbc) -> {mod_muc, odbc};
|
||||
replace_module(mod_offline_odbc) -> {mod_offline, odbc};
|
||||
replace_module(mod_privacy_odbc) -> {mod_privacy, odbc};
|
||||
replace_module(mod_private_odbc) -> {mod_private, odbc};
|
||||
replace_module(mod_roster_odbc) -> {mod_roster, odbc};
|
||||
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, odbc};
|
||||
replace_module(mod_vcard_odbc) -> {mod_vcard, odbc};
|
||||
replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, odbc};
|
||||
replace_module(Module) -> Module.
|
||||
|
||||
replace_modules(Modules) ->
|
||||
lists:map(
|
||||
fun({Module, Opts}) ->
|
||||
case replace_module(Module) of
|
||||
{NewModule, DBType} ->
|
||||
NewOpts = [{db_type, DBType} |
|
||||
lists:keydelete(db_type, 1, Opts)],
|
||||
{NewModule, NewOpts};
|
||||
NewModule ->
|
||||
{NewModule, Opts}
|
||||
end
|
||||
end, Modules).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
|
||||
+42
-22
@@ -5,7 +5,7 @@
|
||||
%%% Created : 11 Jan 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -189,16 +189,20 @@ process(["help" | Mode]) ->
|
||||
["help"] ->
|
||||
print_usage_help(MaxC, ShCode),
|
||||
?STATUS_SUCCESS;
|
||||
[CommandString | _] ->
|
||||
print_usage_commands(CommandString, MaxC, ShCode),
|
||||
[CmdString | _] ->
|
||||
CmdStringU = ejabberd_regexp:greplace(CmdString, "-", "_"),
|
||||
print_usage_commands(CmdStringU, MaxC, ShCode),
|
||||
?STATUS_SUCCESS
|
||||
end;
|
||||
|
||||
process(Args) ->
|
||||
AccessCommands = get_accesscommands(),
|
||||
{String, Code} = process2(Args, AccessCommands),
|
||||
io:format(String),
|
||||
io:format("\n"),
|
||||
case String of
|
||||
[] -> ok;
|
||||
_ ->
|
||||
io:format("~s~n", [String])
|
||||
end,
|
||||
Code.
|
||||
|
||||
%% @spec (Args::[string()], AccessCommands) -> {String::string(), Code::integer()}
|
||||
@@ -265,8 +269,8 @@ try_call_command(Args, Auth, AccessCommands) ->
|
||||
try call_command(Args, Auth, AccessCommands) of
|
||||
{error, command_unknown} ->
|
||||
{io_lib:format("Error: command ~p not known.", [hd(Args)]), ?STATUS_ERROR};
|
||||
{error, wrong_number_parameters} ->
|
||||
{"Error: wrong number of parameters", ?STATUS_ERROR};
|
||||
{error, wrong_command_arguments} ->
|
||||
{"Error: wrong arguments", ?STATUS_ERROR};
|
||||
Res ->
|
||||
Res
|
||||
catch
|
||||
@@ -277,7 +281,7 @@ try_call_command(Args, Auth, AccessCommands) ->
|
||||
|
||||
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType}
|
||||
call_command([CmdString | Args], Auth, AccessCommands) ->
|
||||
{ok, CmdStringU, _} = regexp:gsub(CmdString, "-", "_"),
|
||||
CmdStringU = ejabberd_regexp:greplace(CmdString, "-", "_"),
|
||||
Command = list_to_atom(CmdStringU),
|
||||
case ejabberd_commands:get_command_format(Command) of
|
||||
{error, command_unknown} ->
|
||||
@@ -288,7 +292,18 @@ call_command([CmdString | Args], Auth, AccessCommands) ->
|
||||
Result = ejabberd_commands:execute_command(AccessCommands, Auth, Command,
|
||||
ArgsFormatted),
|
||||
format_result(Result, ResultFormat);
|
||||
%% This case clause is for Erlang R14 and older
|
||||
{'EXIT', {function_clause,[{lists,zip,[A1, A2]} | _]}} ->
|
||||
{NumCompa, TextCompa} =
|
||||
case {length(A1), length(A2)} of
|
||||
{L1, L2} when L1 < L2 -> {L2-L1, "less argument"};
|
||||
{L1, L2} when L1 > L2 -> {L1-L2, "more argument"}
|
||||
end,
|
||||
{io_lib:format("Error: the command ~p requires ~p ~s.",
|
||||
[CmdString, NumCompa, TextCompa]),
|
||||
wrong_command_arguments};
|
||||
%% This case clause is for Erlang R15 and newer
|
||||
{'EXIT', {function_clause,[{lists,zip,[A1, A2],_} | _]}} ->
|
||||
{NumCompa, TextCompa} =
|
||||
case {length(A1), length(A2)} of
|
||||
{L1, L2} when L1 < L2 -> {L2-L1, "less argument"};
|
||||
@@ -314,18 +329,19 @@ format_args(Args, ArgsFormat) ->
|
||||
[],
|
||||
lists:zip(ArgsFormat, Args)).
|
||||
|
||||
format_arg(Arg, Format) ->
|
||||
Parse = case Format of
|
||||
integer ->
|
||||
"~d";
|
||||
string ->
|
||||
NumChars = integer_to_list(string:len(Arg)),
|
||||
"~" ++ NumChars ++ "c"
|
||||
end,
|
||||
format_arg(Arg, integer) ->
|
||||
format_arg2(Arg, "~d");
|
||||
format_arg("", string) ->
|
||||
"";
|
||||
format_arg(Arg, string) ->
|
||||
NumChars = integer_to_list(string:len(Arg)),
|
||||
Parse = "~" ++ NumChars ++ "c",
|
||||
format_arg2(Arg, Parse).
|
||||
|
||||
format_arg2(Arg, Parse)->
|
||||
{ok, [Arg2], _RemainingArguments} = io_lib:fread(Parse, Arg),
|
||||
Arg2.
|
||||
|
||||
|
||||
%%-----------------------------
|
||||
%% Format result
|
||||
%%-----------------------------
|
||||
@@ -632,7 +648,7 @@ print_usage_help(MaxC, ShCode) ->
|
||||
ArgsDef = [],
|
||||
C = #ejabberd_commands{
|
||||
desc = "Show help of ejabberd commands",
|
||||
longdesc = LongDesc,
|
||||
longdesc = lists:flatten(LongDesc),
|
||||
args = ArgsDef,
|
||||
result = {help, string}},
|
||||
print_usage_command("help", C, MaxC, ShCode).
|
||||
@@ -673,13 +689,13 @@ filter_commands(All, SubString) ->
|
||||
end.
|
||||
|
||||
filter_commands_regexp(All, Glob) ->
|
||||
RegExp = regexp:sh_to_awk(Glob),
|
||||
RegExp = ejabberd_regexp:sh_to_awk(Glob),
|
||||
lists:filter(
|
||||
fun(Command) ->
|
||||
case regexp:first_match(Command, RegExp) of
|
||||
{match, _, _} ->
|
||||
case ejabberd_regexp:run(Command, RegExp) of
|
||||
match ->
|
||||
true;
|
||||
_ ->
|
||||
nomatch ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
@@ -736,6 +752,10 @@ print_usage_command(Cmd, C, MaxC, ShCode) ->
|
||||
|
||||
?PRINT(["\n", NameFmt, "\n", ArgsFmt, "\n", ReturnsFmt, "\n\n", XmlrpcFmt, TagsFmt, "\n\n", DescFmt, "\n\n", LongDescFmt, NoteEjabberdctl], []).
|
||||
|
||||
format_usage_ctype(Type, _Indentation)
|
||||
when (Type==atom) or (Type==integer) or (Type==string) or (Type==rescode) or (Type==restuple)->
|
||||
io_lib:format("~p", [Type]);
|
||||
|
||||
format_usage_ctype({Name, Type}, _Indentation)
|
||||
when (Type==atom) or (Type==integer) or (Type==string) or (Type==rescode) or (Type==restuple)->
|
||||
io_lib:format("~p::~p", [Name, Type]);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_debug.erl
|
||||
%%% Author : Mickael Remond
|
||||
%%% Purpose : ejabberd's application callback module
|
||||
%%% Created : 6 may 2009 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 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_debug).
|
||||
|
||||
-export([eprof_start/0, fprof_start/0, stop/0]).
|
||||
-export([pids/0]).
|
||||
|
||||
eprof_start() ->
|
||||
eprof:start(),
|
||||
eprof:profile(pids()).
|
||||
|
||||
fprof_start() ->
|
||||
fprof:trace([start, {file, "/tmp/fprof"}, {procs, pids()}]).
|
||||
|
||||
%% Stop all profilers
|
||||
stop() ->
|
||||
catch eprof:stop(),
|
||||
catch fprof:stop(),
|
||||
ok.
|
||||
|
||||
pids() ->
|
||||
lists:zf(
|
||||
fun(Pid) ->
|
||||
case process_info(Pid) of
|
||||
ProcessInfo when is_list(ProcessInfo) ->
|
||||
CurrentFunction = current_function(ProcessInfo),
|
||||
InitialCall = initial_call(ProcessInfo),
|
||||
RegisteredName = registered_name(ProcessInfo),
|
||||
Ancestor = ancestor(ProcessInfo),
|
||||
filter_pid(Pid, CurrentFunction, InitialCall, RegisteredName, Ancestor);
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
processes()).
|
||||
|
||||
current_function(ProcessInfo) ->
|
||||
{value, {_, {CurrentFunction, _,_}}} =
|
||||
lists:keysearch(current_function, 1, ProcessInfo),
|
||||
atom_to_list(CurrentFunction).
|
||||
|
||||
initial_call(ProcessInfo) ->
|
||||
{value, {_, {InitialCall, _,_}}} =
|
||||
lists:keysearch(initial_call, 1, ProcessInfo),
|
||||
atom_to_list(InitialCall).
|
||||
|
||||
registered_name(ProcessInfo) ->
|
||||
case lists:keysearch(registered_name, 1, ProcessInfo) of
|
||||
{value, {_, Name}} when is_atom(Name) -> atom_to_list(Name);
|
||||
_ -> ""
|
||||
end.
|
||||
|
||||
ancestor(ProcessInfo) ->
|
||||
{value, {_, Dictionary}} = lists:keysearch(dictionary, 1, ProcessInfo),
|
||||
case lists:keysearch('$ancestors', 1, Dictionary) of
|
||||
{value, {_, [Ancestor|_T]}} when is_atom(Ancestor) ->
|
||||
atom_to_list(Ancestor);
|
||||
_ ->
|
||||
""
|
||||
end.
|
||||
|
||||
filter_pid(Pid, "ejabberd" ++ _, _InitialCall, _RegisteredName, _Ancestor) ->
|
||||
{true, Pid};
|
||||
filter_pid(Pid, _CurrentFunction, "ejabberd" ++ _, _RegisteredName, _Ancestor) ->
|
||||
{true, Pid};
|
||||
filter_pid(Pid, _CurrentFunction, _InitialCall, "ejabberd"++_, _Ancestor) ->
|
||||
{true, Pid};
|
||||
filter_pid(Pid, _CurrentFunction, _InitialCall, "stringprep"++_, _Ancestor) ->
|
||||
{true, Pid};
|
||||
filter_pid(Pid, _CurrentFunction, _InitialCall, _RegisteredName, "ejabberd"++_) ->
|
||||
{true, Pid};
|
||||
filter_pid(_Pid, _CurrentFunction, _InitialCall, _RegisteredName, _Ancestor) ->
|
||||
false.
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 23 Aug 2006 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 8 Aug 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
|
||||
+92
-32
@@ -5,7 +5,7 @@
|
||||
%%% Created : 16 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -31,6 +31,7 @@
|
||||
init/3,
|
||||
start_listeners/0,
|
||||
start_listener/3,
|
||||
stop_listeners/0,
|
||||
stop_listener/2,
|
||||
parse_listener_portip/2,
|
||||
add_listener/3,
|
||||
@@ -47,8 +48,42 @@ start_link() ->
|
||||
|
||||
|
||||
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 ->
|
||||
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 ->
|
||||
@@ -98,15 +133,7 @@ start_dependent(Port, Module, Opts) ->
|
||||
|
||||
init(PortIP, Module, RawOpts) ->
|
||||
{Port, IPT, IPS, IPV, Proto, OptsClean} = parse_listener_portip(PortIP, RawOpts),
|
||||
%% 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} = prepare_opts(IPT, IPV, OptsClean),
|
||||
if Proto == udp ->
|
||||
init_udp(PortIP, Module, Opts, SockOpts, Port, IPS);
|
||||
true ->
|
||||
@@ -127,26 +154,39 @@ init_udp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
||||
end.
|
||||
|
||||
init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
||||
SockOpts2 = case erlang:system_info(otp_release) >= "R13B" of
|
||||
true -> [{send_timeout_close, true} | SockOpts];
|
||||
false -> []
|
||||
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} ->
|
||||
%% 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);
|
||||
{error, Reason} ->
|
||||
socket_error(Reason, PortIP, Module, 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;
|
||||
_ ->
|
||||
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.
|
||||
|
||||
%% @spec (PortIP, Opts) -> {Port, IPT, IPS, IPV, OptsClean}
|
||||
@@ -191,6 +231,18 @@ parse_listener_portip(PortIP, Opts) ->
|
||||
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}.
|
||||
|
||||
add_proto(Port, Opts) when is_integer(Port) ->
|
||||
{Port, get_proto(Opts)};
|
||||
add_proto({Port, Proto}, _Opts) when is_atom(Proto) ->
|
||||
@@ -238,8 +290,8 @@ accept(ListenSocket, Module, Opts) ->
|
||||
CallMod:start(strip_frontend(Module), gen_tcp, Socket, Opts),
|
||||
accept(ListenSocket, Module, Opts);
|
||||
{error, Reason} ->
|
||||
?INFO_MSG("(~w) Failed TCP accept: ~w",
|
||||
[ListenSocket, Reason]),
|
||||
?ERROR_MSG("(~w) Failed TCP accept: ~w",
|
||||
[ListenSocket, Reason]),
|
||||
accept(ListenSocket, Module, Opts)
|
||||
end.
|
||||
|
||||
@@ -304,6 +356,14 @@ start_listener_sup(Port, Module, Opts) ->
|
||||
[?MODULE]},
|
||||
supervisor:start_child(ejabberd_listeners, ChildSpec).
|
||||
|
||||
stop_listeners() ->
|
||||
Ports = ejabberd_config:get_local_option(listen),
|
||||
lists:foreach(
|
||||
fun({PortIpNetp, Module, _Opts}) ->
|
||||
delete_listener(PortIpNetp, Module)
|
||||
end,
|
||||
Ports).
|
||||
|
||||
%% @spec (PortIP, Module) -> ok
|
||||
%% where
|
||||
%% PortIP = {Port, IPT | IPS}
|
||||
|
||||
+19
-5
@@ -5,7 +5,7 @@
|
||||
%%% Created : 30 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -34,10 +34,12 @@
|
||||
|
||||
-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,
|
||||
@@ -123,19 +125,31 @@ route(From, To, Packet) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
route_iq(From, To, #iq{type = Type} = IQ, F) when is_function(F) ->
|
||||
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),
|
||||
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) ->
|
||||
TRef = erlang:start_timer(?IQ_TIMEOUT, ejabberd_local, ID),
|
||||
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,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 23 Oct 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -151,6 +151,19 @@ write_event(Fd, {Time, {info_msg, _GL, {Pid, Format, Args}}}) ->
|
||||
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),
|
||||
file:write(Fd, io_lib:format(T ++ F, [Format,Args]))
|
||||
end;
|
||||
write_event(_, _) ->
|
||||
ok.
|
||||
|
||||
|
||||
+118
-52
@@ -9,7 +9,7 @@
|
||||
%%% Created : 29 Nov 2006 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -31,32 +31,59 @@
|
||||
-module(ejabberd_loglevel).
|
||||
-author('mickael.remond@process-one.net').
|
||||
|
||||
-export([set/1, get/0]).
|
||||
-export([set/1,
|
||||
get/0,
|
||||
set_custom/2,
|
||||
clear_custom/0,
|
||||
clear_custom/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-define(LOGMODULE, "error_logger").
|
||||
|
||||
%% Error levels:
|
||||
-define(LOG_LEVELS,[ {0, no_log, "No log"}
|
||||
,{1, critical, "Critical"}
|
||||
,{2, error, "Error"}
|
||||
,{3, warning, "Warning"}
|
||||
,{4, info, "Info"}
|
||||
,{5, debug, "Debug"}
|
||||
]).
|
||||
-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() ->
|
||||
Level = ejabberd_logger:get(),
|
||||
case lists:keysearch(Level, 1, ?LOG_LEVELS) of
|
||||
{value, Result} -> Result;
|
||||
_ -> erlang:error({no_such_loglevel, Level})
|
||||
{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.
|
||||
|
||||
|
||||
set(LogLevel) when is_atom(LogLevel) ->
|
||||
set(level_to_integer(LogLevel));
|
||||
set(Loglevel) when is_integer(Loglevel) ->
|
||||
%% @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)
|
||||
@@ -64,11 +91,45 @@ set(Loglevel) when is_integer(Loglevel) ->
|
||||
Type:Error -> ?CRITICAL_MSG("Error compiling logger (~p): ~p~n", [Type, Error])
|
||||
end;
|
||||
set(_) ->
|
||||
exit("Loglevel must be an integer").
|
||||
exit("Invalid loglevel format").
|
||||
|
||||
%% @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.
|
||||
|
||||
%% @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, 2, ?LOG_LEVELS) of
|
||||
{value, {Int, Level, _Desc}} -> Int;
|
||||
case lists:keysearch(Level, #loglevel.name, ?LOG_LEVELS) of
|
||||
{value, #loglevel{ordinal = Int}} -> Int;
|
||||
_ -> erlang:error({no_such_loglevel, Level})
|
||||
end.
|
||||
|
||||
@@ -77,7 +138,12 @@ level_to_integer(Level) ->
|
||||
%% This allows to dynamically change log level while keeping a
|
||||
%% 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').
|
||||
|
||||
@@ -87,42 +153,42 @@ ejabberd_logger_src(Loglevel) ->
|
||||
error_msg/4,
|
||||
critical_msg/4,
|
||||
get/0]).
|
||||
".
|
||||
|
||||
get() -> "++ L ++".
|
||||
get_src(Loglevel) ->
|
||||
io_lib:format("get() -> ~w.
|
||||
", [Loglevel]).
|
||||
|
||||
%% 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.
|
||||
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), ".
|
||||
"].
|
||||
|
||||
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.
|
||||
|
||||
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-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -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
|
||||
|
||||
+123
-72
@@ -5,7 +5,7 @@
|
||||
%%% Created : 17 Jul 2008 by Pablo Polvorin <pablo.polvorin@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -153,19 +153,19 @@ process_elements(Elements,State) ->
|
||||
process_element(El=#xmlel{name=user, ns=_XMLNS},
|
||||
State=#parsing_state{host=Host}) ->
|
||||
case add_user(El,Host) of
|
||||
{error, _Other} -> error;
|
||||
_ -> ok
|
||||
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))};
|
||||
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
|
||||
case exmpp_xml:get_attribute(El, <<"href">>, none) of
|
||||
none ->
|
||||
ok;
|
||||
HrefB ->
|
||||
@@ -194,36 +194,62 @@ process_element(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, ?BTL(User), ?BTL(Password)).
|
||||
User = exmpp_xml:get_attribute(El, <<"name">>, none),
|
||||
PasswordFormat = exmpp_xml:get_attribute(El, <<"password-format">>, <<"plaintext">>),
|
||||
Password = exmpp_xml:get_attribute(El, <<"password">>, none),
|
||||
add_user(El, Domain, User, PasswordFormat, Password).
|
||||
|
||||
%% @spec (El::xmlel(), Domain::string(), User::string(), Password::string())
|
||||
%% -> ok | {atomic, exists} | {error, not_allowed}
|
||||
%% @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, User, Password) ->
|
||||
case create_user(User,Password,Domain) of
|
||||
add_user(El, Domain, UserBinary, <<"plaintext">>, 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, PasswordFormat, PasswordBinary) ->
|
||||
User = ?BTL(UserBinary),
|
||||
Password2 = prepare_password(PasswordFormat, PasswordBinary, El),
|
||||
case create_user(User,Password2,Domain) of
|
||||
ok ->
|
||||
ok = exmpp_xml:foreach(
|
||||
fun(_,Child) ->
|
||||
populate_user(User,Domain,Child)
|
||||
end,
|
||||
El),
|
||||
populate_user_with_elements(El, Domain, User),
|
||||
ok;
|
||||
{atomic, exists} ->
|
||||
?INFO_MSG("User ~p@~p already exists, using stored profile...~n",
|
||||
io:format("Account ~s@~s already exists, updating it...~n",
|
||||
[User, Domain]),
|
||||
io:format(""),
|
||||
ok = exmpp_xml:foreach(
|
||||
fun(_,Child) ->
|
||||
populate_user(User,Domain,Child)
|
||||
end,
|
||||
El);
|
||||
populate_user_with_elements(El, Domain, User),
|
||||
ok;
|
||||
{error, Other} ->
|
||||
?ERROR_MSG("Error adding user ~s@~s: ~p~n", [User, Domain, Other])
|
||||
?ERROR_MSG("Error adding user ~s@~s: ~p~n", [User, Domain, Other]),
|
||||
{error, Other}
|
||||
end.
|
||||
|
||||
prepare_password(<<"plaintext">>, PasswordBinary, _El) ->
|
||||
?BTL(PasswordBinary);
|
||||
prepare_password(<<"scram">>, none, El) ->
|
||||
ScramEl = exmpp_xml:get_element(El, 'scram-hash'),
|
||||
#scram{storedkey = base64:decode(exmpp_xml:get_attribute(
|
||||
ScramEl, <<"stored-key">>, none)),
|
||||
serverkey = base64:decode(exmpp_xml:get_attribute(
|
||||
ScramEl, <<"server-key">>, none)),
|
||||
salt = base64:decode(exmpp_xml:get_attribute(
|
||||
ScramEl, <<"salt">>, none)),
|
||||
iterationcount = list_to_integer(exmpp_xml:get_attribute_as_list(
|
||||
ScramEl, <<"iteration-count">>,
|
||||
?SCRAM_DEFAULT_ITERATION_COUNT))
|
||||
}.
|
||||
|
||||
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
|
||||
@@ -263,9 +289,10 @@ create_user(User,Password,Domain) ->
|
||||
|
||||
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
|
||||
case loaded_module(Domain, mod_roster) of
|
||||
{ok, _DBType} ->
|
||||
case mod_roster:set_items(User, Domain,
|
||||
exmpp_xml:xmlel_to_xmlelement(El)) of
|
||||
{atomic, ok} ->
|
||||
io:format(" DONE.~n",[]),
|
||||
ok;
|
||||
@@ -276,7 +303,7 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) ->
|
||||
{error, not_found}
|
||||
end;
|
||||
E -> io:format(" ERROR: ~p~n",[E]),
|
||||
?ERROR_MSG("No modules loaded [mod_roster, mod_roster_odbc] ~s ~n",
|
||||
?ERROR_MSG("No modules loaded [mod_roster] ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
@@ -304,10 +331,10 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) ->
|
||||
|
||||
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)),
|
||||
case loaded_module(Domain, mod_vcard) of
|
||||
{ok, _} -> 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
|
||||
case mod_vcard:process_sm_iq(FullUser, FullUser , IQ) of
|
||||
{error,_Err} ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("Error processing vcard ~s : ~p ~n",
|
||||
@@ -317,7 +344,7 @@ populate_user(User,Domain,El=#xmlel{name='vCard', ns='vcard-temp'}) ->
|
||||
end;
|
||||
_ ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("No modules loaded [mod_vcard, mod_vcard_odbc] ~s ~n",
|
||||
?ERROR_MSG("No modules loaded [mod_vcard] ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
@@ -330,22 +357,22 @@ populate_user(User,Domain,El=#xmlel{name='vCard', ns='vcard-temp'}) ->
|
||||
|
||||
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} ->
|
||||
case loaded_module(Domain, mod_offline) of
|
||||
{ok, _DBType} ->
|
||||
ok = exmpp_xml:foreach(
|
||||
fun (_Element, {xmlcdata, _}) ->
|
||||
ok;
|
||||
(_Element, Child) ->
|
||||
From = exmpp_xml:get_attribute(Child,from,none),
|
||||
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)
|
||||
_R = mod_offline: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",
|
||||
?ERROR_MSG("No modules loaded [mod_offline] ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
@@ -358,15 +385,15 @@ populate_user(User,Domain,El=#xmlel{name='offline-messages'}) ->
|
||||
|
||||
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} ->
|
||||
case loaded_module(Domain, mod_private) of
|
||||
{ok, _DBType} ->
|
||||
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
|
||||
case mod_private:process_sm_iq(FullUser, FullUser, IQ ) of
|
||||
{error, _Err} ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("Error processing private storage ~s : ~p ~n",
|
||||
@@ -375,7 +402,7 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:private'}) ->
|
||||
end;
|
||||
_ ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("No modules loaded [mod_private, mod_private_odbc] ~s ~n",
|
||||
?ERROR_MSG("No modules loaded [mod_private] ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
@@ -389,13 +416,12 @@ populate_user(_User, _Domain, _El) ->
|
||||
%%%==================================
|
||||
%%%% 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}
|
||||
loaded_module(Domain, Module) ->
|
||||
case gen_mod:is_loaded(Domain, Module) of
|
||||
true ->
|
||||
{ok, gen_mod:db_type(Domain, Module)};
|
||||
false ->
|
||||
{error, not_found}
|
||||
end.
|
||||
|
||||
jid_to_old_jid(Jid) ->
|
||||
@@ -417,10 +443,10 @@ to_list(B) when is_atom(B) -> atom_to_list(B).
|
||||
|
||||
%%%==================================
|
||||
|
||||
%%%% Export server
|
||||
%%%% Export hosts
|
||||
|
||||
%% @spec (Dir::string()) -> ok
|
||||
export_server(Dir) ->
|
||||
%% @spec (Dir::string(), Hosts::[string()]) -> ok
|
||||
export_hosts(Dir, Hosts) ->
|
||||
try_start_exmpp(),
|
||||
|
||||
FnT = make_filename_template(),
|
||||
@@ -430,7 +456,6 @@ export_server(Dir) ->
|
||||
print(Fd, make_piefxis_xml_head()),
|
||||
print(Fd, make_piefxis_server_head()),
|
||||
|
||||
Hosts = ?MYHOSTS,
|
||||
FilesAndHosts = [{make_host_filename(FnT, Host), Host} || Host <- Hosts],
|
||||
[print(Fd, make_xinclude(FnH)) || {FnH, _Host} <- FilesAndHosts],
|
||||
|
||||
@@ -442,15 +467,21 @@ export_server(Dir) ->
|
||||
|
||||
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) ->
|
||||
try_start_exmpp(),
|
||||
FnT = make_filename_template(),
|
||||
FnH = make_host_filename(FnT, Host),
|
||||
export_host(Dir, FnH, Host).
|
||||
Hosts = [Host],
|
||||
export_hosts(Dir, Hosts).
|
||||
|
||||
%% @spec (Dir::string(), Fn::string(), Host::string()) -> ok
|
||||
export_host(Dir, FnH, Host) ->
|
||||
@@ -463,6 +494,7 @@ export_host(Dir, FnH, 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()),
|
||||
@@ -511,25 +543,44 @@ make_xinclude(Fn) ->
|
||||
%% @spec (Fd, Username::string(), Host::string()) -> ok
|
||||
%% @doc Extract user information and print it.
|
||||
export_user(Fd, Username, Host) ->
|
||||
UserString = extract_user(Username, Host),
|
||||
print(Fd, UserString).
|
||||
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),
|
||||
Password = ejabberd_auth:get_password(Username, Host),
|
||||
PasswordStr = build_password_string(Password),
|
||||
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]).
|
||||
io_lib:format("<user name='~s' ~s ~s</user>",
|
||||
[Username, PasswordStr, UserInfoString]).
|
||||
|
||||
build_password_string({StoredKey, ServerKey, Salt, IterationCount}) ->
|
||||
io_lib:format("password-format='scram'>"
|
||||
"<scram-hash stored-key='~s' server-key='~s' "
|
||||
"salt='~s' iteration-count='~w'/> ",
|
||||
[base64:encode_to_string(StoredKey),
|
||||
base64:encode_to_string(ServerKey),
|
||||
base64:encode_to_string(Salt),
|
||||
IterationCount]);
|
||||
build_password_string(Password) when is_list(Password) ->
|
||||
io_lib:format("password-format='plaintext' password='~s'>", [Password]).
|
||||
|
||||
%% @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} ->
|
||||
case loaded_module(Host, mod_roster) of
|
||||
{ok, _DBType} ->
|
||||
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),
|
||||
Res = mod_roster:process_local_iq(From, To, IQGet),
|
||||
%%[El] = Res#iq.payload, % this is for 3.0.0 version
|
||||
{iq, _, result, _, _, Els} = Res,
|
||||
case Els of
|
||||
@@ -541,8 +592,8 @@ extract_user_info(roster, Username, Host) ->
|
||||
end;
|
||||
|
||||
extract_user_info(offline, Username, Host) ->
|
||||
case loaded_module(Host,[mod_offline,mod_offline_odbc]) of
|
||||
{ok, mod_offline} ->
|
||||
case loaded_module(Host, mod_offline) of
|
||||
{ok, mnesia} ->
|
||||
Els = mnesia_pop_offline_messages([], Username, Host),
|
||||
case Els of
|
||||
[] -> "";
|
||||
@@ -550,30 +601,30 @@ extract_user_info(offline, Username, Host) ->
|
||||
OfEl = {xmlelement, "offline-messages", [], Els},
|
||||
exmpp_xml:document_to_list(OfEl)
|
||||
end;
|
||||
{ok, mod_offline_odbc} ->
|
||||
{ok, odbc} ->
|
||||
"";
|
||||
_E ->
|
||||
""
|
||||
end;
|
||||
|
||||
extract_user_info(private, Username, Host) ->
|
||||
case loaded_module(Host,[mod_private,mod_private_odbc]) of
|
||||
{ok, mod_private} ->
|
||||
case loaded_module(Host, mod_private) of
|
||||
{ok, mnesia} ->
|
||||
get_user_private_mnesia(Username, Host);
|
||||
{ok, mod_private_odbc} ->
|
||||
{ok, odbc} ->
|
||||
"";
|
||||
_E ->
|
||||
""
|
||||
end;
|
||||
|
||||
extract_user_info(vcard, Username, Host) ->
|
||||
case loaded_module(Host,[mod_vcard, mod_vcard_odbc, mod_vcard_odbc]) of
|
||||
{ok, M} ->
|
||||
case loaded_module(Host, mod_vcard) of
|
||||
{ok, _DBType} ->
|
||||
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),
|
||||
Res = mod_vcard:process_sm_iq(From, To, IQGet),
|
||||
%%[El] = Res#iq.payload, % this is for 3.0.0 version
|
||||
{iq, _, result, _, _, Els} = Res,
|
||||
case Els of
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 31 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 10 Nov 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -84,16 +84,16 @@ change_shaper(Pid, Shaper) ->
|
||||
gen_server:cast(Pid, {change_shaper, Shaper}).
|
||||
|
||||
reset_stream(Pid) ->
|
||||
gen_server:call(Pid, reset_stream).
|
||||
do_call(Pid, reset_stream).
|
||||
|
||||
starttls(Pid, TLSSocket) ->
|
||||
gen_server:call(Pid, {starttls, TLSSocket}).
|
||||
do_call(Pid, {starttls, TLSSocket}).
|
||||
|
||||
compress(Pid, ZlibSocket) ->
|
||||
gen_server:call(Pid, {compress, ZlibSocket}).
|
||||
do_call(Pid, {compress, ZlibSocket}).
|
||||
|
||||
become_controller(Pid, C2SPid) ->
|
||||
gen_server:call(Pid, {become_controller, C2SPid}).
|
||||
do_call(Pid, {become_controller, C2SPid}).
|
||||
|
||||
close(Pid) ->
|
||||
gen_server:cast(Pid, close).
|
||||
@@ -345,3 +345,11 @@ close_stream(undefined) ->
|
||||
ok;
|
||||
close_stream(XMLStreamState) ->
|
||||
xml_stream:close(XMLStreamState).
|
||||
|
||||
do_call(Pid, Msg) ->
|
||||
case catch gen_server:call(Pid, Msg) of
|
||||
{'EXIT', Why} ->
|
||||
{error, Why};
|
||||
Res ->
|
||||
Res
|
||||
end.
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_regexp.erl
|
||||
%%% Author : Badlop
|
||||
%%% Purpose : Frontend to Re and Regexp OTP modules
|
||||
%%% Created : 8 Dec 2011 by Badlop
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 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_regexp).
|
||||
-compile([export_all]).
|
||||
|
||||
exec(ReM, ReF, ReA, RgM, RgF, RgA) ->
|
||||
try apply(ReM, ReF, ReA)
|
||||
catch
|
||||
error:undef ->
|
||||
apply(RgM, RgF, RgA);
|
||||
A:B ->
|
||||
{error, {A, B}}
|
||||
end.
|
||||
|
||||
run(String, Regexp) ->
|
||||
case exec(re, run, [String, Regexp, [{capture, none}]], regexp, first_match, [String, Regexp]) of
|
||||
{match, _, _} -> match;
|
||||
{match, _} -> match;
|
||||
match -> match;
|
||||
nomatch -> nomatch;
|
||||
{error, Error} -> {error, Error}
|
||||
end.
|
||||
|
||||
split(String, Regexp) ->
|
||||
case exec(re, split, [String, Regexp, [{return, list}]], regexp, split, [String, Regexp]) of
|
||||
{ok, FieldList} -> FieldList;
|
||||
{error, Error} -> throw(Error);
|
||||
A -> A
|
||||
end.
|
||||
|
||||
replace(String, Regexp, New) ->
|
||||
case exec(re, replace, [String, Regexp, New, [{return, list}]], regexp, sub, [String, Regexp, New]) of
|
||||
{ok, NewString, _RepCount} -> NewString;
|
||||
{error, Error} -> throw(Error);
|
||||
A -> A
|
||||
end.
|
||||
|
||||
greplace(String, Regexp, New) ->
|
||||
case exec(re, replace, [String, Regexp, New, [global, {return, list}]], regexp, sub, [String, Regexp, New]) of
|
||||
{ok, NewString, _RepCount} -> NewString;
|
||||
{error, Error} -> throw(Error);
|
||||
A -> A
|
||||
end.
|
||||
|
||||
sh_to_awk(ShRegExp) ->
|
||||
case exec(xmerl_regexp, sh_to_awk, [ShRegExp], regexp, sh_to_awk, [ShRegExp]) of
|
||||
A -> A
|
||||
end.
|
||||
+14
-2
@@ -5,7 +5,7 @@
|
||||
%%% Created : 27 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -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,
|
||||
|
||||
+81
-26
@@ -5,7 +5,7 @@
|
||||
%%% Created : 7 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -37,10 +37,15 @@
|
||||
get_connections_pids/1,
|
||||
try_register/1,
|
||||
remove_connection/3,
|
||||
find_connection/2,
|
||||
dirty_get_connections/0,
|
||||
allow_host/2,
|
||||
incoming_s2s_number/0,
|
||||
outgoing_s2s_number/0
|
||||
outgoing_s2s_number/0,
|
||||
clean_temporarily_blocked_table/0,
|
||||
list_temporarily_blocked_hosts/0,
|
||||
external_host_overloaded/1,
|
||||
is_temporarly_blocked/1
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
@@ -56,9 +61,14 @@
|
||||
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1).
|
||||
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1).
|
||||
|
||||
-define(S2S_OVERLOAD_BLOCK_PERIOD, 60).
|
||||
%% once a server is temporarly blocked, it stay blocked for 60 seconds
|
||||
|
||||
-record(s2s, {fromto, pid, key}).
|
||||
-record(state, {}).
|
||||
|
||||
-record(temporarily_blocked, {host, timestamp}).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@@ -78,6 +88,31 @@ route(From, To, Packet) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
clean_temporarily_blocked_table() ->
|
||||
mnesia:clear_table(temporarily_blocked).
|
||||
list_temporarily_blocked_hosts() ->
|
||||
ets:tab2list(temporarily_blocked).
|
||||
|
||||
external_host_overloaded(Host) ->
|
||||
?INFO_MSG("Disabling connections from ~s for ~p seconds", [Host, ?S2S_OVERLOAD_BLOCK_PERIOD]),
|
||||
mnesia:transaction( fun() ->
|
||||
mnesia:write(#temporarily_blocked{host = Host, timestamp = now()})
|
||||
end).
|
||||
|
||||
is_temporarly_blocked(Host) ->
|
||||
case mnesia:dirty_read(temporarily_blocked, Host) of
|
||||
[] -> false;
|
||||
[#temporarily_blocked{timestamp = T}=Entry] ->
|
||||
case timer:now_diff(now(), T) of
|
||||
N when N > ?S2S_OVERLOAD_BLOCK_PERIOD * 1000 * 1000 ->
|
||||
mnesia:dirty_delete_object(Entry),
|
||||
false;
|
||||
_ ->
|
||||
true
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
remove_connection(FromTo, Pid, Key) ->
|
||||
case catch mnesia:dirty_match_object(s2s, #s2s{fromto = FromTo,
|
||||
pid = Pid,
|
||||
@@ -168,6 +203,7 @@ init([]) ->
|
||||
mnesia:add_table_copy(s2s, node(), ram_copies),
|
||||
mnesia:subscribe(system),
|
||||
ejabberd_commands:register_commands(commands()),
|
||||
mnesia:create_table(temporarily_blocked, [{ram_copies, [node()]}, {attributes, record_info(fields, temporarily_blocked)}]),
|
||||
{ok, #state{}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -245,7 +281,7 @@ 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",
|
||||
@@ -408,22 +444,21 @@ 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}.
|
||||
@@ -486,15 +521,35 @@ 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
|
||||
allow_host2(MyServer, S2SHost) andalso (not is_temporarly_blocked(S2SHost)).
|
||||
|
||||
allow_host2(MyServer, S2SHost) ->
|
||||
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.
|
||||
|
||||
+125
-47
@@ -5,7 +5,7 @@
|
||||
%%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -27,7 +27,7 @@
|
||||
-module(ejabberd_s2s_in).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% External exports
|
||||
-export([start/2,
|
||||
@@ -44,10 +44,16 @@
|
||||
handle_sync_event/4,
|
||||
code_change/4,
|
||||
handle_info/3,
|
||||
print_state/1,
|
||||
terminate/3]).
|
||||
|
||||
-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').
|
||||
@@ -58,6 +64,7 @@
|
||||
-define(PKIXEXPLICIT, 'PKIX1Explicit88').
|
||||
-define(PKIXIMPLICIT, 'PKIX1Implicit88').
|
||||
-endif.
|
||||
-endif.
|
||||
-include("XmppAddr.hrl").
|
||||
|
||||
-define(DICT, dict).
|
||||
@@ -68,7 +75,10 @@
|
||||
shaper,
|
||||
tls = false,
|
||||
tls_enabled = false,
|
||||
tls_required = false,
|
||||
tls_certverify = false,
|
||||
tls_options = [],
|
||||
server,
|
||||
authenticated = false,
|
||||
auth_domain,
|
||||
connections = ?DICT:new(),
|
||||
@@ -83,10 +93,12 @@
|
||||
-define(FSMOPTS, []).
|
||||
-endif.
|
||||
|
||||
-define(FSMLIMITS, [{max_queue, 2000}]). %% if queue grows more than this, we shutdown this connection.
|
||||
|
||||
%% Module start with or without supervisor:
|
||||
-ifdef(NO_TRANSIENT_SUPERVISORS).
|
||||
-define(SUPERVISOR_START, gen_fsm:start(ejabberd_s2s_in, [SockData, Opts],
|
||||
?FSMOPTS)).
|
||||
-define(SUPERVISOR_START, p1_fsm:start(ejabberd_s2s_in, [SockData, Opts],
|
||||
?FSMOPTS ++ ?FSMLIMITS)).
|
||||
-else.
|
||||
-define(SUPERVISOR_START, supervisor:start_child(ejabberd_s2s_in_sup,
|
||||
[SockData, Opts])).
|
||||
@@ -122,7 +134,7 @@ start(SockData, Opts) ->
|
||||
?SUPERVISOR_START.
|
||||
|
||||
start_link(SockData, Opts) ->
|
||||
gen_fsm:start_link(ejabberd_s2s_in, [SockData, Opts], ?FSMOPTS).
|
||||
p1_fsm:start_link(ejabberd_s2s_in, [SockData, Opts], ?FSMOPTS ++ ?FSMLIMITS).
|
||||
|
||||
socket_type() ->
|
||||
xml_stream.
|
||||
@@ -144,12 +156,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 ->
|
||||
[];
|
||||
@@ -164,6 +180,8 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
shaper = Shaper,
|
||||
tls = StartTLS,
|
||||
tls_enabled = false,
|
||||
tls_required = TLSRequired,
|
||||
tls_certverify = TLSCertverify,
|
||||
tls_options = TLSOpts,
|
||||
timer = Timer}}.
|
||||
|
||||
@@ -177,8 +195,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 =
|
||||
@@ -186,16 +205,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 ->
|
||||
[]
|
||||
@@ -206,21 +227,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};
|
||||
_ ->
|
||||
@@ -251,15 +295,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),
|
||||
@@ -295,8 +350,9 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
|
||||
error ->
|
||||
false
|
||||
end,
|
||||
AllowRemoteHost = ejabberd_s2s:allow_host("", AuthDomain),
|
||||
if
|
||||
AuthRes ->
|
||||
AuthRes andalso AllowRemoteHost ->
|
||||
(StateData#state.sockmod):reset_stream(
|
||||
StateData#state.socket),
|
||||
send_element(StateData,
|
||||
@@ -304,6 +360,12 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
|
||||
[{"xmlns", ?NS_SASL}], []}),
|
||||
?DEBUG("(~w) Accepted s2s authentication for ~s",
|
||||
[StateData#state.socket, AuthDomain]),
|
||||
|
||||
%% acess rules are first checked against the globally defined ones, that have precedence over
|
||||
%% domain-specific ones.. http://www.process-one.net/docs/ejabberd/guide_en.html#AccessRights
|
||||
%% since there is allways a shaper defined globally for s2s, it doesn't matter the actual
|
||||
%% local host, since the globall one will be used, even if this domain has a special rule
|
||||
change_shaper(StateData, "", jlib:make_jid("", AuthDomain, "")),
|
||||
{next_state, wait_for_stream,
|
||||
StateData#state{streamid = new_id(),
|
||||
authenticated = true,
|
||||
@@ -349,11 +411,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:terminate_if_waiting_delay(To, From),
|
||||
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,
|
||||
@@ -532,14 +594,7 @@ handle_sync_event(get_state_infos, _From, StateName, StateData) ->
|
||||
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,
|
||||
Domains = get_external_hosts(StateData),
|
||||
Infos = [
|
||||
{direction, in},
|
||||
{statename, StateName},
|
||||
@@ -598,9 +653,32 @@ handle_info(_, StateName, StateData) ->
|
||||
%%----------------------------------------------------------------------
|
||||
terminate(Reason, _StateName, StateData) ->
|
||||
?DEBUG("terminated: ~p", [Reason]),
|
||||
case Reason of
|
||||
{process_limit, _} ->
|
||||
[ejabberd_s2s:external_host_overloaded(Host) || Host <- get_external_hosts(StateData)];
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
(StateData#state.sockmod):close(StateData#state.socket),
|
||||
ok.
|
||||
|
||||
get_external_hosts(StateData) ->
|
||||
case StateData#state.authenticated of
|
||||
true ->
|
||||
[StateData#state.auth_domain];
|
||||
false ->
|
||||
Connections = StateData#state.connections,
|
||||
[D || {{D, _}, established} <- dict:to_list(Connections)]
|
||||
end.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: print_state/1
|
||||
%% Purpose: Prepare the state to be printed on error log
|
||||
%% Returns: State to print
|
||||
%%----------------------------------------------------------------------
|
||||
print_state(State) ->
|
||||
State.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -609,7 +687,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) ->
|
||||
@@ -753,11 +831,11 @@ match_labels([DL | DLabels], [PL | PLabels]) ->
|
||||
orelse (C == $-) orelse (C == $*)
|
||||
end, PL) of
|
||||
true ->
|
||||
Regexp = regexp:sh_to_awk(PL),
|
||||
case regexp:match(DL, Regexp) of
|
||||
{match, _, _} ->
|
||||
Regexp = ejabberd_regexp:sh_to_awk(PL),
|
||||
case ejabberd_regexp:run(DL, Regexp) of
|
||||
match ->
|
||||
match_labels(DLabels, PLabels);
|
||||
_ ->
|
||||
nomatch ->
|
||||
false
|
||||
end;
|
||||
false ->
|
||||
|
||||
+148
-85
@@ -5,7 +5,7 @@
|
||||
%%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -44,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,
|
||||
@@ -51,6 +52,7 @@
|
||||
handle_sync_event/4,
|
||||
handle_info/3,
|
||||
terminate/3,
|
||||
print_state/1,
|
||||
code_change/4,
|
||||
test_get_addr_port/1,
|
||||
get_addr_port/1]).
|
||||
@@ -64,13 +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).
|
||||
@@ -105,6 +108,7 @@
|
||||
"xmlns:stream='http://etherx.jabber.org/streams' "
|
||||
"xmlns='jabber:server' "
|
||||
"xmlns:db='jabber:server:dialback' "
|
||||
"from='~s' "
|
||||
"to='~s'~s>"
|
||||
).
|
||||
|
||||
@@ -135,7 +139,7 @@ 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
|
||||
@@ -151,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,
|
||||
@@ -174,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,
|
||||
@@ -191,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,
|
||||
@@ -220,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)
|
||||
%%{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};
|
||||
@@ -265,14 +284,15 @@ open_socket1(Host, Port) ->
|
||||
open_socket2(Type, Addr, Port) ->
|
||||
?DEBUG("s2s_out: connecting to ~p:~p~n", [Addr, Port]),
|
||||
Timeout = outgoing_s2s_timeout(),
|
||||
SockOpts = case erlang:system_info(otp_release) >= "R13B" of
|
||||
true -> [{send_timeout_close, true}];
|
||||
false -> []
|
||||
end,
|
||||
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},
|
||||
{send_timeout_close, true},
|
||||
{active, false}, Type | SockOpts],
|
||||
Timeout)) of
|
||||
{ok, _Socket} = R -> R;
|
||||
@@ -296,13 +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, _, _} ->
|
||||
{NSProvided, DB, _} ->
|
||||
send_text(StateData, ?INVALID_NAMESPACE_ERR),
|
||||
?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid namespace).~n"
|
||||
"Namespace provided: ~p~nNamespace expected: \"jabber:server\"",
|
||||
[StateData#state.myname, StateData#state.server, NSProvided]),
|
||||
"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;
|
||||
|
||||
@@ -334,16 +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)",
|
||||
@@ -536,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(),
|
||||
@@ -604,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 ->
|
||||
@@ -616,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};
|
||||
_ ->
|
||||
@@ -673,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
|
||||
@@ -823,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},
|
||||
@@ -873,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
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -881,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
|
||||
@@ -950,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" ->
|
||||
@@ -1006,33 +1073,29 @@ get_addr_port(Server) ->
|
||||
{ok, HEnt} ->
|
||||
?DEBUG("srv lookup of '~s': ~p~n",
|
||||
[Server, HEnt#hostent.h_addr_list]),
|
||||
case HEnt#hostent.h_addr_list of
|
||||
[] ->
|
||||
[{Server, outgoing_s2s_port()}];
|
||||
AddrList ->
|
||||
%% Probabilities are not exactly proportional to weights
|
||||
%% for simplicity (higher weigths are overvalued)
|
||||
{A1, A2, A3} = now(),
|
||||
random:seed(A1, A2, A3),
|
||||
case (catch lists:map(
|
||||
fun({Priority, Weight, Port, Host}) ->
|
||||
N = case Weight of
|
||||
0 -> 0;
|
||||
_ -> (Weight + 1) * random:uniform()
|
||||
end,
|
||||
{Priority * 65536 - N, Host, Port}
|
||||
end, AddrList)) of
|
||||
{'EXIT', _Reason} ->
|
||||
[{Server, outgoing_s2s_port()}];
|
||||
SortedList ->
|
||||
List = lists:map(
|
||||
fun({_, Host, Port}) ->
|
||||
{Host, Port}
|
||||
end, lists:keysort(1, SortedList)),
|
||||
?DEBUG("srv lookup of '~s': ~p~n", [Server, List]),
|
||||
List
|
||||
end
|
||||
end
|
||||
AddrList = HEnt#hostent.h_addr_list,
|
||||
%% Probabilities are not exactly proportional to weights
|
||||
%% for simplicity (higher weigths are overvalued)
|
||||
{A1, A2, A3} = now(),
|
||||
random:seed(A1, A2, A3),
|
||||
case (catch lists:map(
|
||||
fun({Priority, Weight, Port, Host}) ->
|
||||
N = case Weight of
|
||||
0 -> 0;
|
||||
_ -> (Weight + 1) * random:uniform()
|
||||
end,
|
||||
{Priority * 65536 - N, Host, Port}
|
||||
end, AddrList)) of
|
||||
SortedList = [_|_] ->
|
||||
List = lists:map(
|
||||
fun({_, Host, Port}) ->
|
||||
{Host, Port}
|
||||
end, lists:keysort(1, SortedList)),
|
||||
?DEBUG("srv lookup of '~s': ~p~n", [Server, List]),
|
||||
List;
|
||||
_ ->
|
||||
[{Server, outgoing_s2s_port()}]
|
||||
end
|
||||
end.
|
||||
|
||||
srv_lookup(Server) ->
|
||||
@@ -1134,10 +1197,10 @@ outgoing_s2s_timeout() ->
|
||||
|
||||
%% 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]).
|
||||
|
||||
%% Calculate timeout depending on which state we are in:
|
||||
%% Can return integer > 0 | infinity
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -47,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").
|
||||
@@ -346,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}.
|
||||
|
||||
|
||||
@@ -374,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
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -382,7 +394,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)).
|
||||
|
||||
new_id() ->
|
||||
randoms:get_string().
|
||||
|
||||
+57
-47
@@ -5,7 +5,7 @@
|
||||
%%% Created : 24 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -47,12 +47,14 @@
|
||||
register_iq_handler/4,
|
||||
register_iq_handler/5,
|
||||
unregister_iq_handler/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
|
||||
get_user_ip/3,
|
||||
is_existing_resource/3
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
@@ -92,7 +94,8 @@ route(From, To, Packet) ->
|
||||
|
||||
open_session(SID, User, Server, Resource, Info) ->
|
||||
set_session(SID, User, Server, Resource, undefined, Info),
|
||||
inc_session_counter(jlib:nameprep(Server)),
|
||||
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,
|
||||
@@ -105,7 +108,8 @@ close_session(SID, User, Server, Resource) ->
|
||||
end,
|
||||
F = fun() ->
|
||||
mnesia:delete({session, SID}),
|
||||
dec_session_counter(jlib:nameprep(Server))
|
||||
mnesia:dirty_update_counter(session_counter,
|
||||
jlib:nameprep(Server), -1)
|
||||
end,
|
||||
mnesia:sync_dirty(F),
|
||||
JID = jlib:make_jid(User, Server, Resource),
|
||||
@@ -263,6 +267,7 @@ init([]) ->
|
||||
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(
|
||||
@@ -316,7 +321,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}),
|
||||
@@ -373,7 +378,9 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
|
||||
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,
|
||||
@@ -381,12 +388,22 @@ clean_table_from_bad_node(Node) ->
|
||||
[{'==', {node, '$1'}, Node}],
|
||||
['$_']}]),
|
||||
lists:foreach(fun(E) ->
|
||||
{_, LServer} = E#session.us,
|
||||
dec_session_counter(LServer),
|
||||
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).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
@@ -481,7 +498,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;
|
||||
_ ->
|
||||
@@ -557,9 +574,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),
|
||||
@@ -618,14 +640,11 @@ check_for_sessions_to_replace(User, Server, Resource) ->
|
||||
check_max_sessions(LUser, LServer).
|
||||
|
||||
check_existing_resources(LUser, LServer, LResource) ->
|
||||
USR = {LUser, LServer, LResource},
|
||||
%% A connection exist with the same resource. We replace it:
|
||||
SIDs = mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{sid = '$1', usr = USR, _ = '_'}, [], ['$1']}]),
|
||||
SIDs = get_resource_sessions(LUser, LServer, LResource),
|
||||
if
|
||||
SIDs == [] -> ok;
|
||||
true ->
|
||||
%% A connection exist with the same resource. We replace it:
|
||||
MaxSID = lists:max(SIDs),
|
||||
lists:foreach(
|
||||
fun({_, Pid} = S) when S /= MaxSID ->
|
||||
@@ -634,6 +653,15 @@ check_existing_resources(LUser, LServer, LResource) ->
|
||||
end, SIDs)
|
||||
end.
|
||||
|
||||
is_existing_resource(LUser, LServer, LResource) ->
|
||||
[] /= get_resource_sessions(LUser, LServer, LResource).
|
||||
|
||||
get_resource_sessions(User, Server, Resource) ->
|
||||
USR = {jlib:nodeprep(User), jlib:nameprep(Server), jlib:resourceprep(Resource)},
|
||||
mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{sid = '$1', usr = USR, _ = '_'}, [], ['$1']}]).
|
||||
|
||||
check_max_sessions(LUser, LServer) ->
|
||||
%% If the max number of sessions for a given is reached, we replace the
|
||||
%% first one
|
||||
@@ -663,34 +691,6 @@ get_max_user_sessions(LUser, Host) ->
|
||||
_ -> ?MAX_USER_SESSIONS
|
||||
end.
|
||||
|
||||
inc_session_counter(LServer) ->
|
||||
F = fun() ->
|
||||
case mnesia:wread({session_counter, LServer}) of
|
||||
[C] ->
|
||||
Count = C#session_counter.count + 1,
|
||||
C2 = C#session_counter{count = Count},
|
||||
mnesia:write(C2);
|
||||
_ ->
|
||||
mnesia:write(#session_counter{vhost = LServer,
|
||||
count = 1})
|
||||
end
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
dec_session_counter(LServer) ->
|
||||
F = fun() ->
|
||||
case mnesia:wread({session_counter, LServer}) of
|
||||
[C] ->
|
||||
Count = C#session_counter.count - 1,
|
||||
C2 = C#session_counter{count = Count},
|
||||
mnesia:write(C2);
|
||||
_ ->
|
||||
error
|
||||
end
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
process_iq(From, To, Packet) ->
|
||||
@@ -724,6 +724,16 @@ 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.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% ejabberd commands
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 23 Aug 2006 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -87,7 +87,13 @@ start(Module, SockMod, Socket, Opts) ->
|
||||
end,
|
||||
ReceiverMod:become_controller(Receiver, Pid);
|
||||
{error, _Reason} ->
|
||||
SockMod:close(Socket)
|
||||
SockMod:close(Socket),
|
||||
case ReceiverMod of
|
||||
ejabberd_receiver ->
|
||||
ReceiverMod:close(Receiver);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end;
|
||||
independent ->
|
||||
ok;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 31 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -177,6 +177,13 @@ init([]) ->
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
CacheTabSupervisor =
|
||||
{cache_tab_sup,
|
||||
{cache_tab_sup, start_link, []},
|
||||
permanent,
|
||||
infinity,
|
||||
supervisor,
|
||||
[cache_tab_sup]},
|
||||
{ok, {{one_for_one, 10, 1},
|
||||
[Hooks,
|
||||
NodeGroups,
|
||||
@@ -196,6 +203,7 @@ init([]) ->
|
||||
IQSupervisor,
|
||||
STUNSupervisor,
|
||||
FrontendSocketSupervisor,
|
||||
CacheTabSupervisor,
|
||||
Listener]}}.
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 21 Mar 2007 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 18 Jul 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
|
||||
+41
-12
@@ -5,7 +5,7 @@
|
||||
%%% Created : 27 Jan 2006 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -41,10 +41,10 @@ update() ->
|
||||
case update_info() of
|
||||
{ok, Dir, _UpdatedBeams, _Script, LowLevelScript, _Check} ->
|
||||
Eval =
|
||||
release_handler_1:eval_script(
|
||||
eval_script(
|
||||
LowLevelScript, [],
|
||||
[{ejabberd, "", filename:join(Dir, "..")}]),
|
||||
?INFO_MSG("eval: ~p~n", [Eval]),
|
||||
?DEBUG("eval: ~p~n", [Eval]),
|
||||
Eval;
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
@@ -58,15 +58,26 @@ update(ModulesToUpdate) ->
|
||||
[A || A <- UpdatedBeamsAll, B <- ModulesToUpdate, A == B],
|
||||
{_, LowLevelScript, _} = build_script(Dir, UpdatedBeamsNow),
|
||||
Eval =
|
||||
release_handler_1:eval_script(
|
||||
eval_script(
|
||||
LowLevelScript, [],
|
||||
[{ejabberd, "", filename:join(Dir, "..")}]),
|
||||
?INFO_MSG("eval: ~p~n", [Eval]),
|
||||
?DEBUG("eval: ~p~n", [Eval]),
|
||||
Eval;
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
%% OTP R14B03 and older provided release_handler_1:eval_script/3
|
||||
%% But OTP R14B04 and newer provide release_handler_1:eval_script/5
|
||||
%% Dialyzer reports a call to missing function; don't worry.
|
||||
eval_script(Script, Apps, LibDirs) ->
|
||||
case lists:member({eval_script, 5}, release_handler_1:module_info(exports)) of
|
||||
true ->
|
||||
release_handler_1:eval_script(Script, Apps, LibDirs, [], []);
|
||||
false ->
|
||||
release_handler_1:eval_script(Script, Apps, LibDirs)
|
||||
end.
|
||||
|
||||
%% Get information about the modified modules
|
||||
update_info() ->
|
||||
Dir = filename:dirname(code:which(ejabberd)),
|
||||
@@ -84,7 +95,7 @@ update_info() ->
|
||||
update_info(Dir, Files) ->
|
||||
Beams = lists:sort(get_beams(Files)),
|
||||
UpdatedBeams = get_updated_beams(Beams),
|
||||
?INFO_MSG("beam files: ~p~n", [UpdatedBeams]),
|
||||
?DEBUG("beam files: ~p~n", [UpdatedBeams]),
|
||||
{Script, LowLevelScript, Check} = build_script(Dir, UpdatedBeams),
|
||||
{ok, Dir, UpdatedBeams, Script, LowLevelScript, Check}.
|
||||
|
||||
@@ -118,21 +129,39 @@ get_new_version(Module) ->
|
||||
|
||||
get_current_version(Module) ->
|
||||
Attrs = Module:module_info(attributes),
|
||||
{value, {vsn, CurVsn}} = lists:keysearch(vsn, 1, Attrs),
|
||||
CurVsn.
|
||||
case lists:keysearch(vsn, 1, Attrs) of
|
||||
{value, {vsn, CurVsn}} -> CurVsn;
|
||||
_ -> unknown_version
|
||||
end.
|
||||
|
||||
%% @spec(Dir::string(), UpdatedBeams::[atom()]) -> {Script,LowLevelScript,Check}
|
||||
build_script(Dir, UpdatedBeams) ->
|
||||
Script = make_script(UpdatedBeams),
|
||||
?INFO_MSG("script: ~p~n", [Script]),
|
||||
LowLevelScript = make_low_level_script(UpdatedBeams, Script),
|
||||
?INFO_MSG("low level script: ~p~n", [LowLevelScript]),
|
||||
Check =
|
||||
release_handler_1:check_script(
|
||||
LowLevelScript,
|
||||
[{ejabberd, "", filename:join(Dir, "..")}]),
|
||||
?INFO_MSG("check: ~p~n", [Check]),
|
||||
{Script, LowLevelScript, Check}.
|
||||
Check1 = case Check of
|
||||
ok ->
|
||||
%% This clause is for OTP R14B03 and older.
|
||||
%% Newer Dialyzer reports a never match pattern; don't worry.
|
||||
?DEBUG("script: ~p~n", [Script]),
|
||||
?DEBUG("low level script: ~p~n", [LowLevelScript]),
|
||||
?DEBUG("check: ~p~n", [Check]),
|
||||
ok;
|
||||
{ok, []} ->
|
||||
?DEBUG("script: ~p~n", [Script]),
|
||||
?DEBUG("low level script: ~p~n", [LowLevelScript]),
|
||||
?DEBUG("check: ~p~n", [Check]),
|
||||
ok;
|
||||
_ ->
|
||||
?ERROR_MSG("script: ~p~n", [Script]),
|
||||
?ERROR_MSG("low level script: ~p~n", [LowLevelScript]),
|
||||
?ERROR_MSG("check: ~p~n", [Check]),
|
||||
error
|
||||
end,
|
||||
{Script, LowLevelScript, Check1}.
|
||||
|
||||
%% Copied from Erlang/OTP file: lib/sasl/src/systools.hrl
|
||||
-record(application,
|
||||
|
||||
@@ -26,7 +26,7 @@ EFLAGS += -pz ..
|
||||
|
||||
# make debug=true to compile Erlang module with debug informations.
|
||||
ifdef debug
|
||||
EFLAGS+=+debug_info
|
||||
EFLAGS+=+debug_info +export_all
|
||||
endif
|
||||
|
||||
ERLSHLIBS = ../ejabberd_zlib_drv.so
|
||||
|
||||
@@ -29,8 +29,8 @@ LD=link.exe
|
||||
LD_FLAGS=-release -nologo -incremental:no -dll "$(EI_DIR)\lib\ei_md.lib" "$(EI_DIR)\lib\erl_interface_md.lib" "$(ZLIB_LIB)" MSVCRT.LIB kernel32.lib advapi32.lib gdi32.lib user32.lib comctl32.lib comdlg32.lib shell32.lib
|
||||
|
||||
$(DLL) : $(OBJECT)
|
||||
$(LD) $(LD_FLAGS) -out:$(DLL) $(OBJECT)
|
||||
$(LD) $(LD_FLAGS) -out:$@ $<
|
||||
|
||||
$(OBJECT) : $(SOURCE)
|
||||
$(CC) $(CC_FLAGS) -c -Fo$(OBJECT) $(SOURCE)
|
||||
$(CC) $(CC_FLAGS) -c -Fo$@ $<
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 19 Jan 2006 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -63,7 +63,7 @@ init([]) ->
|
||||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, ejabberd_zlib_drv}, [binary]),
|
||||
Port = open_port({spawn, "ejabberd_zlib_drv"}, [binary]),
|
||||
{ok, Port}.
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ enable_zlib(SockMod, Socket) ->
|
||||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, ejabberd_zlib_drv}, [binary]),
|
||||
Port = open_port({spawn, "ejabberd_zlib_drv"}, [binary]),
|
||||
{ok, #zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}}.
|
||||
|
||||
disable_zlib(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}) ->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
* ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
@@ -23,6 +23,15 @@
|
||||
#include <erl_driver.h>
|
||||
#include <zlib.h>
|
||||
|
||||
/*
|
||||
* R15B changed several driver callbacks to use ErlDrvSizeT and
|
||||
* ErlDrvSSizeT typedefs instead of int.
|
||||
* This provides missing typedefs on older OTP versions.
|
||||
*/
|
||||
#if ERL_DRV_EXTENDED_MAJOR_VERSION < 2
|
||||
typedef int ErlDrvSizeT;
|
||||
typedef int ErlDrvSSizeT;
|
||||
#endif
|
||||
|
||||
#define BUF_SIZE 1024
|
||||
|
||||
@@ -32,6 +41,15 @@ typedef struct {
|
||||
z_stream *i_stream;
|
||||
} ejabberd_zlib_data;
|
||||
|
||||
static void* zlib_alloc(void* data, unsigned int items, unsigned int size)
|
||||
{
|
||||
return (void*) driver_alloc(items*size);
|
||||
}
|
||||
|
||||
static void zlib_free(void* data, void* addr)
|
||||
{
|
||||
driver_free(addr);
|
||||
}
|
||||
|
||||
static ErlDrvData ejabberd_zlib_drv_start(ErlDrvPort port, char *buff)
|
||||
{
|
||||
@@ -39,18 +57,18 @@ static ErlDrvData ejabberd_zlib_drv_start(ErlDrvPort port, char *buff)
|
||||
(ejabberd_zlib_data *)driver_alloc(sizeof(ejabberd_zlib_data));
|
||||
d->port = port;
|
||||
|
||||
d->d_stream = (z_stream *)malloc(sizeof(z_stream));
|
||||
d->d_stream = (z_stream *)driver_alloc(sizeof(z_stream));
|
||||
|
||||
d->d_stream->zalloc = (alloc_func)0;
|
||||
d->d_stream->zfree = (free_func)0;
|
||||
d->d_stream->zalloc = zlib_alloc;
|
||||
d->d_stream->zfree = zlib_free;
|
||||
d->d_stream->opaque = (voidpf)0;
|
||||
|
||||
deflateInit(d->d_stream, Z_DEFAULT_COMPRESSION);
|
||||
|
||||
d->i_stream = (z_stream *)malloc(sizeof(z_stream));
|
||||
d->i_stream = (z_stream *)driver_alloc(sizeof(z_stream));
|
||||
|
||||
d->i_stream->zalloc = (alloc_func)0;
|
||||
d->i_stream->zfree = (free_func)0;
|
||||
d->i_stream->zalloc = zlib_alloc;
|
||||
d->i_stream->zfree = zlib_free;
|
||||
d->i_stream->opaque = (voidpf)0;
|
||||
|
||||
inflateInit(d->i_stream);
|
||||
@@ -65,10 +83,10 @@ static void ejabberd_zlib_drv_stop(ErlDrvData handle)
|
||||
ejabberd_zlib_data *d = (ejabberd_zlib_data *)handle;
|
||||
|
||||
deflateEnd(d->d_stream);
|
||||
free(d->d_stream);
|
||||
driver_free(d->d_stream);
|
||||
|
||||
inflateEnd(d->i_stream);
|
||||
free(d->i_stream);
|
||||
driver_free(d->i_stream);
|
||||
|
||||
driver_free((char *)handle);
|
||||
}
|
||||
@@ -89,10 +107,10 @@ static void ejabberd_zlib_drv_stop(ErlDrvData handle)
|
||||
}
|
||||
|
||||
|
||||
static int ejabberd_zlib_drv_control(ErlDrvData handle,
|
||||
static ErlDrvSSizeT ejabberd_zlib_drv_control(ErlDrvData handle,
|
||||
unsigned int command,
|
||||
char *buf, int len,
|
||||
char **rbuf, int rlen)
|
||||
char *buf, ErlDrvSizeT len,
|
||||
char **rbuf, ErlDrvSizeT rlen)
|
||||
{
|
||||
ejabberd_zlib_data *d = (ejabberd_zlib_data *)handle;
|
||||
int err;
|
||||
@@ -178,7 +196,19 @@ ErlDrvEntry ejabberd_zlib_driver_entry = {
|
||||
NULL, /* handle */
|
||||
ejabberd_zlib_drv_control, /* F_PTR control, port_command callback */
|
||||
NULL, /* F_PTR timeout, reserved */
|
||||
NULL /* F_PTR outputv, reserved */
|
||||
NULL, /* F_PTR outputv, reserved */
|
||||
/* Added in Erlang/OTP R15B: */
|
||||
NULL, /* ready_async */
|
||||
NULL, /* flush */
|
||||
NULL, /* call */
|
||||
NULL, /* event */
|
||||
ERL_DRV_EXTENDED_MARKER, /* extended_marker */
|
||||
ERL_DRV_EXTENDED_MAJOR_VERSION, /* major_version */
|
||||
ERL_DRV_EXTENDED_MINOR_VERSION, /* minor_version */
|
||||
0, /* driver_flags */
|
||||
NULL, /* handle2 */
|
||||
NULL, /* process_exit */
|
||||
NULL /* stop_select */
|
||||
};
|
||||
|
||||
DRIVER_INIT(ejabberd_zlib_drv) /* must match name in driver_entry */
|
||||
|
||||
@@ -51,18 +51,45 @@
|
||||
#FIREWALL_WINDOW=
|
||||
|
||||
#.
|
||||
#' PROCESSES: Maximum number of Erlang processes
|
||||
#' INET_DIST_INTERFACE: IP address where this Erlang node listens other nodes
|
||||
#
|
||||
# This communication is used by ejabberdctl command line tool,
|
||||
# and in a cluster of several ejabberd nodes.
|
||||
# Notice that the IP address must be specified in the Erlang syntax.
|
||||
#
|
||||
# Default: {127,0,0,1}
|
||||
#
|
||||
INET_DIST_INTERFACE={127,0,0,1}
|
||||
|
||||
#.
|
||||
#' ERL_EPMD_ADDRESS: IP addresses where epmd listens for connections
|
||||
#
|
||||
# IMPORTANT: This option works only in Erlang/OTP R14B03 and newer.
|
||||
#
|
||||
# This environment variable may be set to a comma-separated
|
||||
# list of IP addresses, in which case the epmd daemon
|
||||
# will listen only on the specified address(es) and on the
|
||||
# loopback address (which is implicitly added to the list if it
|
||||
# has not been specified). The default behaviour is to listen on
|
||||
# all available IP addresses.
|
||||
#
|
||||
# Default: 0.0.0.0
|
||||
#
|
||||
#ERL_EPMD_ADDRESS=127.0.0.1
|
||||
|
||||
#.
|
||||
#' ERL_PROCESSES: Maximum number of Erlang processes
|
||||
#
|
||||
# Erlang consumes a lot of lightweight processes. If there is a lot of activity
|
||||
# on ejabberd so that the maximum number of processes is reached, people will
|
||||
# experiment greater latency times. As these processes are implemented in
|
||||
# experience greater latency times. As these processes are implemented in
|
||||
# Erlang, and therefore not related to the operating system processes, you do
|
||||
# not have to worry about allowing a huge number of them.
|
||||
#
|
||||
# Default: 250000
|
||||
# Maximum: 268435456
|
||||
#
|
||||
#PROCESSES=250000
|
||||
#ERL_PROCESSES=250000
|
||||
|
||||
#.
|
||||
#' ERL_MAX_ETS_TABLES: Maximum number of ETS and Mnesia tables
|
||||
@@ -106,9 +133,9 @@
|
||||
# The same as previous, but erlang will use long hostname
|
||||
# (see erl (1) manual for details)
|
||||
#
|
||||
# Default: ejabberd
|
||||
# Default: ejabberd@localhost
|
||||
#
|
||||
#ERLANG_NODE=ejabberd
|
||||
#ERLANG_NODE=ejabberd@localhost
|
||||
|
||||
#.
|
||||
#' EJABBERD_PID_PATH: ejabberd PID file
|
||||
|
||||
+130
-24
@@ -57,29 +57,34 @@ fi
|
||||
|
||||
# check the proper system user is used
|
||||
ID=`id -g`
|
||||
GIDS=`id -G`
|
||||
EJID=`id -g $INSTALLUSER`
|
||||
EXEC_CMD="false"
|
||||
if [ $ID -eq 0 ] ; then
|
||||
EXEC_CMD="su ${INSTALLUSER} -c"
|
||||
fi
|
||||
for GID in $GIDS; do
|
||||
if [ $GID -eq 0 ] ; then
|
||||
EXEC_CMD="su ${INSTALLUSER} -p -c"
|
||||
fi
|
||||
done
|
||||
if [ "$ID" -eq "$EJID" ] ; then
|
||||
EXEC_CMD="sh -c"
|
||||
fi
|
||||
if [ "$EXEC_CMD" = "false" ] ; then
|
||||
echo "This command can only be run by root or the user $INSTALLUSER" >&2
|
||||
exit 1
|
||||
exit 4
|
||||
fi
|
||||
|
||||
NAME=-name
|
||||
[ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && NAME=-sname
|
||||
|
||||
if [ "$FIREWALL_WINDOW" = "" ] ; then
|
||||
KERNEL_OPTS=""
|
||||
else
|
||||
KERNEL_OPTS="-kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
|
||||
KERNEL_OPTS=""
|
||||
if [ "$FIREWALL_WINDOW" != "" ] ; then
|
||||
KERNEL_OPTS="${KERNEL_OPTS} -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
|
||||
fi
|
||||
if [ "$INET_DIST_INTERFACE" != "" ] ; then
|
||||
KERNEL_OPTS="${KERNEL_OPTS} -kernel inet_dist_use_interface \"${INET_DIST_INTERFACE}\""
|
||||
fi
|
||||
|
||||
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS $KERNEL_OPTS"
|
||||
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS"
|
||||
|
||||
# define additional environment variables
|
||||
if [ "$EJABBERDDIR" = "" ]; then
|
||||
@@ -91,7 +96,7 @@ fi
|
||||
if [ "$EJABBERD_PRIV_PATH" = "" ]; then
|
||||
EJABBERD_PRIV_PATH=$EJABBERDDIR/priv
|
||||
fi
|
||||
if [ "$EJABBRD_BIN_PATH" = "" ]; then
|
||||
if [ "$EJABBERD_BIN_PATH" = "" ]; then
|
||||
EJABBERD_BIN_PATH=$EJABBERD_PRIV_PATH/bin
|
||||
fi
|
||||
if [ "$EJABBERD_SO_PATH" = "" ]; then
|
||||
@@ -111,6 +116,10 @@ HOME=$SPOOLDIR
|
||||
# create the home dir with the proper user if doesn't exist, because it stores cookie file
|
||||
[ -d $HOME ] || $EXEC_CMD "mkdir -p $HOME"
|
||||
|
||||
# Change to a directory readable by INSTALLUSER to
|
||||
# prevent "File operation error: eacces." messages
|
||||
cd $HOME
|
||||
|
||||
# export global variables
|
||||
export EJABBERD_CONFIG_PATH
|
||||
export EJABBERD_MSGS_PATH
|
||||
@@ -120,6 +129,7 @@ export EJABBERD_BIN_PATH
|
||||
export EJABBERD_DOC_PATH
|
||||
export EJABBERD_PID_PATH
|
||||
export ERL_CRASH_DUMP
|
||||
export ERL_EPMD_ADDRESS
|
||||
export ERL_INETRC
|
||||
export ERL_MAX_PORTS
|
||||
export ERL_MAX_ETS_TABLES
|
||||
@@ -133,11 +143,15 @@ export EXEC_CMD
|
||||
# start server
|
||||
start ()
|
||||
{
|
||||
checknodenameusage
|
||||
[ "$?" -eq 0 ] && echo "\nERROR: The node '$ERLANG_NODE' is already running." && return 1
|
||||
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME $ERLANG_NODE \
|
||||
-noinput -detached \
|
||||
-pa $EJABBERD_EBIN_PATH \
|
||||
-mnesia dir \"\\\"$SPOOLDIR\\\"\" \
|
||||
$KERNEL_OPTS \
|
||||
-s ejabberd \
|
||||
-sasl sasl_error_logger \\{file,\\\"$SASL_LOG_PATH\\\"\\} \
|
||||
$ERLANG_OPTS $ARGS \"$@\""
|
||||
@@ -150,7 +164,7 @@ debug ()
|
||||
echo ""
|
||||
echo "IMPORTANT: we will attempt to attach an INTERACTIVE shell"
|
||||
echo "to an already running ejabberd node."
|
||||
echo "If an ERROR is printed, it means the connection was not succesfull."
|
||||
echo "If an ERROR is printed, it means the connection was not successful."
|
||||
echo "You can interact with the ejabberd node if you know how to use it."
|
||||
echo "Please be extremely cautious with your actions,"
|
||||
echo "and exit immediately if you are not completely sure."
|
||||
@@ -159,19 +173,28 @@ debug ()
|
||||
echo " control+c, control+c"
|
||||
echo ""
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||
echo "Press any key to continue"
|
||||
read foo
|
||||
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
|
||||
read foo
|
||||
fi
|
||||
echo ""
|
||||
TTY=`tty | sed -e 's/.*\///g'`
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME debug-${ERLANG_NODE} \
|
||||
$NAME debug-${TTY}-${ERLANG_NODE} \
|
||||
-remsh $ERLANG_NODE \
|
||||
-hidden \
|
||||
$KERNEL_OPTS \
|
||||
$ERLANG_OPTS $ARGS \"$@\""
|
||||
}
|
||||
|
||||
# start interactive server
|
||||
live ()
|
||||
{
|
||||
checknodenameusage
|
||||
[ "$?" -eq 0 ] && echo "\nERROR: The node '$ERLANG_NODE' is already running." && return 1
|
||||
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo ""
|
||||
echo "IMPORTANT: ejabberd is going to start in LIVE (interactive) mode."
|
||||
@@ -184,13 +207,18 @@ live ()
|
||||
echo " q(). and press the Enter key"
|
||||
echo ""
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||
echo "Press any key to continue"
|
||||
read foo
|
||||
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
|
||||
read foo
|
||||
fi
|
||||
echo ""
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME $ERLANG_NODE \
|
||||
-pa $EJABBERD_EBIN_PATH \
|
||||
-mnesia dir \"\\\"$SPOOLDIR\\\"\" \
|
||||
$KERNEL_OPTS \
|
||||
-s ejabberd \
|
||||
$ERLANG_OPTS $ARGS \"$@\""
|
||||
}
|
||||
@@ -217,20 +245,89 @@ help ()
|
||||
ctl ()
|
||||
{
|
||||
COMMAND=$@
|
||||
|
||||
# Control number of connections identifiers
|
||||
# using flock if available. Expects a linux-style
|
||||
# flock that can lock a file descriptor.
|
||||
MAXCONNID=100
|
||||
CONNLOCKDIR=@LOCALSTATEDIR@/lock/ejabberdctl
|
||||
FLOCK='/usr/bin/flock'
|
||||
if [ ! -x "$FLOCK" ] || [ ! -d "$CONNLOCKDIR" ] ; then
|
||||
JOT='/usr/bin/jot'
|
||||
if [ ! -x "$JOT" ] ; then
|
||||
# no flock or jot, simply invoke ctlexec()
|
||||
CTL_CONN="ctl-${ERLANG_NODE}"
|
||||
ctlexec $CTL_CONN $COMMAND
|
||||
result=$?
|
||||
else
|
||||
# no flock, but at least there is jot
|
||||
RAND=`jot -r 1 0 $MAXCONNID`
|
||||
CTL_CONN="ctl-${RAND}-${ERLANG_NODE}"
|
||||
ctlexec $CTL_CONN $COMMAND
|
||||
result=$?
|
||||
fi
|
||||
else
|
||||
# we have flock so we get a lock
|
||||
# on one of a limited number of
|
||||
# conn names -- this allows
|
||||
# concurrent invocations using a bound
|
||||
# number of atoms
|
||||
for N in $(seq 1 $MAXCONNID); do
|
||||
CTL_CONN="ejabberdctl-$N"
|
||||
CTL_LOCKFILE="$CONNLOCKDIR/$CTL_CONN"
|
||||
(
|
||||
exec 8>"$CTL_LOCKFILE"
|
||||
if flock --nb 8; then
|
||||
ctlexec $CTL_CONN $COMMAND
|
||||
ssresult=$?
|
||||
# segregate from possible flock exit(1)
|
||||
ssresult=$(expr $ssresult \* 10)
|
||||
exit $ssresult
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
)
|
||||
result=$?
|
||||
if [ $result -eq 1 ]; then
|
||||
# means we errored out in flock
|
||||
# rather than in the exec - stay in the loop
|
||||
# trying other conn names...
|
||||
badlock=1
|
||||
else
|
||||
badlock=""
|
||||
break;
|
||||
fi
|
||||
done
|
||||
result=$(expr $result / 10)
|
||||
fi
|
||||
|
||||
if [ "$badlock" ];then
|
||||
echo "Ran out of connections to try. Your ejabberd processes" >&2
|
||||
echo "may be stuck or this is a very busy server. For very" >&2
|
||||
echo "busy servers, consider raising MAXCONNID in ejabberdctl">&2
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
case $result in
|
||||
0) :;;
|
||||
1) :;;
|
||||
2) help;;
|
||||
3) help;;
|
||||
esac
|
||||
return $result
|
||||
}
|
||||
|
||||
ctlexec ()
|
||||
{
|
||||
CONN_NAME=$1; shift
|
||||
COMMAND=$@
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME ctl-${ERLANG_NODE} \
|
||||
$NAME ${CONN_NAME} \
|
||||
-noinput \
|
||||
-hidden \
|
||||
-pa $EJABBERD_EBIN_PATH \
|
||||
$KERNEL_OPTS \
|
||||
-s ejabberd_ctl -extra $ERLANG_NODE $COMMAND"
|
||||
result=$?
|
||||
case $result in
|
||||
0) :;;
|
||||
1) :;;
|
||||
2) help;;
|
||||
3) help;;
|
||||
esac
|
||||
return $result
|
||||
}
|
||||
|
||||
# display ctl usage
|
||||
@@ -255,7 +352,7 @@ wait_for_status()
|
||||
status=4
|
||||
while [ $status -ne $1 ]; do
|
||||
sleep $3
|
||||
let timeout=timeout-1
|
||||
timeout=$(($timeout - 1))
|
||||
[ $timeout -eq 0 ] && {
|
||||
status=$1
|
||||
} || {
|
||||
@@ -271,6 +368,15 @@ wait_for_status()
|
||||
return $status
|
||||
}
|
||||
|
||||
# check node name is used or not
|
||||
# $? --> 0=alreadyused 1=available
|
||||
checknodenameusage ()
|
||||
{
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME $ERLANG_NODE \
|
||||
-s erlang halt | grep -c duplicate_name > /dev/null "
|
||||
}
|
||||
|
||||
case $ARGS in
|
||||
' start') start;;
|
||||
' debug') debug;;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user