Compare commits
1408 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a6e5fb269c | |||
| 96c99b6427 | |||
| 3fd5513549 | |||
| 804549415d | |||
| fa08db7091 | |||
| df0754ade9 | |||
| d3c4eab46f | |||
| 1c62edc53c | |||
| 17099eaaca | |||
| f3f80ea0ea | |||
| 8b45ee5208 | |||
| 1dc1795f2a | |||
| a435c4b123 | |||
| 151994c8d5 | |||
| 1aa36cb05c | |||
| e6e146c0f7 | |||
| adc37a8cc6 | |||
| b017702a15 | |||
| d6d58d13f7 | |||
| 8bcde56409 | |||
| e1e75b2852 | |||
| d6d3190b47 | |||
| fed00a0c17 | |||
| 4fcff0b6b9 | |||
| d1f1958dd4 | |||
| adc4a7f67c | |||
| ad6218cdf2 | |||
| edda5e1747 | |||
| 2f5dc0d35d | |||
| dbe71153dc | |||
| 8ddbf1c1c5 | |||
| 7d3633e91c | |||
| fef8c3a1f3 | |||
| 7355e810a8 | |||
| 6eb5b0d298 | |||
| e895b12aea | |||
| afad6c75a0 | |||
| 4a4a7afe22 | |||
| 24f5c964cd | |||
| 1b7cc33a7f | |||
| aa8c9bf0a5 | |||
| 92887e96bd | |||
| 4f101c318f | |||
| 4fcbd5894e | |||
| cd7beb9492 | |||
| 91fc4a6fbb | |||
| 158aa06760 | |||
| 9c2f0681f0 | |||
| cd2d207877 | |||
| 5ab3561700 | |||
| 19ab0174e4 | |||
| 82551036c2 | |||
| d09acc8231 | |||
| 98a78624cc | |||
| d923c70082 | |||
| 06d4889586 | |||
| ff8bd0c2ec | |||
| 12d6f3364b | |||
| 86859e5234 | |||
| b0755c7cfb | |||
| c5cf9b4e6d | |||
| 8760eef677 | |||
| 0b1f3ca148 | |||
| 0d7e69a65e | |||
| 466481d126 | |||
| b8d74ab2a9 | |||
| 1d8a69d94f | |||
| 72e3e0e6ad | |||
| 3ccbf1115c | |||
| c1e6c7d821 | |||
| 01c160251a | |||
| 8d4945aa7f | |||
| 06e3503976 | |||
| 1d244c7d9b | |||
| d3d17e8041 | |||
| bf5938ee1d | |||
| 8ad81a3db8 | |||
| ef67ec4286 | |||
| b2f06226b5 | |||
| 84b0e6333d | |||
| 9c9f278a96 | |||
| 7248ee1dbc | |||
| 75b6162b44 | |||
| 75510a4b1b | |||
| 4a4f3eb8c5 | |||
| 8168526f01 | |||
| 09b4e36457 | |||
| dfd7936ea6 | |||
| 970f74288b | |||
| b8e9c6a7a2 | |||
| 6c08c53d74 | |||
| 82296c277a | |||
| 806d5497c4 | |||
| b0bf89e319 | |||
| 9d9a4d1bc7 | |||
| c55aa9be7b | |||
| 4d2408c9c9 | |||
| bd25bd465c | |||
| 6898553afd | |||
| a8dd91512c | |||
| 05d95f2dd7 | |||
| f839b1c6ff | |||
| b8026d8df7 | |||
| c611361bbe | |||
| 7fe05e67a7 | |||
| 0c7ce8b7fe | |||
| 34fc5d82b3 | |||
| 7436ef1fb1 | |||
| 099f7d6521 | |||
| 545162a283 | |||
| a2d60a1977 | |||
| 33745954a9 | |||
| 028d158e13 | |||
| b684e90292 | |||
| 191187a279 | |||
| 80483d8fd1 | |||
| 4689b0d861 | |||
| c73aeca393 | |||
| f893669d91 | |||
| 3c549bca4e | |||
| 198298df2c | |||
| 5a876dfd33 | |||
| 9cf43ffc9e | |||
| 8d2def619d | |||
| 25a6493e1b | |||
| 71bfa17354 | |||
| dedd2a7f59 | |||
| 415a50921f | |||
| 8cd0894ad5 | |||
| 33feca74ec | |||
| 102dabdefc | |||
| 38bf3bfc1c | |||
| afec527f5f | |||
| 8a135262db | |||
| 3b2af5dfc5 | |||
| 4caf2c8674 | |||
| 6cc950d76f | |||
| bf8e09038e | |||
| 9a55ced505 | |||
| eaa17dab90 | |||
| 62a4e20ae7 | |||
| e0bda563e6 | |||
| 61e4fde247 | |||
| 7f899ee179 | |||
| 75a85beb80 | |||
| 0b7bb342cf | |||
| 8f9bf47d78 | |||
| 76b0300505 | |||
| 15749a2193 | |||
| 60d83f937d | |||
| 7060791e4e | |||
| 72395ba497 | |||
| 4a2005f7cc | |||
| 90c711162b | |||
| 7111ecc1b7 | |||
| e1cc8a90fa | |||
| 96d1400792 | |||
| 975e7f47ee | |||
| 2f681b05f3 | |||
| a3dba06baf | |||
| 16a97ab903 | |||
| d8f339bceb | |||
| 798d7bce1b | |||
| a98193ea6e | |||
| 70a2200888 | |||
| 3391c9cad7 | |||
| d5cfdc7f1f | |||
| caf64c035f | |||
| 089a00fc3f | |||
| bec67378aa | |||
| 9bb4f124ad | |||
| 722fdd6bc7 | |||
| d34dab4822 | |||
| ad357c2f59 | |||
| 2d0d46e296 | |||
| d523901ddc | |||
| 41fc44e55f | |||
| befb4fc7ea | |||
| 54557cb867 | |||
| 2d3efdf983 | |||
| 159529d539 | |||
| e3d875d09f | |||
| ea84f802e5 | |||
| 20c4919eda | |||
| 9cbf08700d | |||
| ff455f1a42 | |||
| a4728d14d5 | |||
| f2925d8efc | |||
| 4b0a7fe307 | |||
| 980d9c37ea | |||
| a40e9c2626 | |||
| 897141c9e1 | |||
| d20b6dccb3 | |||
| fe909a45e9 | |||
| bb4a87806f | |||
| 7ca1c0387c | |||
| 8c4e4e5f91 | |||
| 93787bee73 | |||
| d3efcf8682 | |||
| 268e80ece7 | |||
| a33c389b9b | |||
| 9ce44cd0a7 | |||
| be8e1bfc35 | |||
| 7b2b9412ad | |||
| 75f2e775ab | |||
| 0859667a8c | |||
| 563344d219 | |||
| a13c9185f7 | |||
| 1eeff9b0df | |||
| 942c6e4dd0 | |||
| 8b7d529519 | |||
| a47caa8c54 | |||
| aee3e30232 | |||
| 1bbb41682a | |||
| d448b6c955 | |||
| 74f86bef6a | |||
| ce0219c569 | |||
| 53cc032251 | |||
| a8bf889e79 | |||
| 4e2bd18c7f | |||
| 27872c6022 | |||
| c6e0cff144 | |||
| a8ef64ab45 | |||
| 9afaec40f5 | |||
| 8520b76483 | |||
| b8f04aae6f | |||
| c9db1f691c | |||
| ef572c815f | |||
| dad3297c9c | |||
| 312574cbc5 | |||
| 008a0a29cd | |||
| f9e2466867 | |||
| 26550efd91 | |||
| 7be6e33ea4 | |||
| 7aa48e265a | |||
| 2ef06678c0 | |||
| 2aa56cd86e | |||
| 33116be0ae | |||
| d94f6b45bf | |||
| 81f4e71efa | |||
| 5a3fe4e348 | |||
| c2dface515 | |||
| 690c56ca6d | |||
| aaa69a8c36 | |||
| ec0e0ef8a6 | |||
| 0b8a761a61 | |||
| 0ccbd7e3f2 | |||
| 549f1029a6 | |||
| da7e53fe3c | |||
| 3e9de2ec79 | |||
| d146ef873d | |||
| d8d20d5b88 | |||
| 1ab92d1159 | |||
| abf069da9e | |||
| f4f949bd72 | |||
| f4507a088a | |||
| 6b46b8f794 | |||
| 7d93cad452 | |||
| 860d8525ee | |||
| 351635d0aa | |||
| 614f13714c | |||
| 642b18edcb | |||
| 03239c662e | |||
| fbb84c8256 | |||
| e710ac51eb | |||
| 6ce29e7ecb | |||
| e84d853bc3 | |||
| 5e0f2b8560 | |||
| 01b6cd3aba | |||
| 2ebfd4090a | |||
| e4a1eb4370 | |||
| e3afec9465 | |||
| 29fbe6d8e1 | |||
| 5c3611fe32 | |||
| 4a1d8c2cd2 | |||
| b9c6f6e627 | |||
| d456578b3c | |||
| 659d546897 | |||
| bacecae3dd | |||
| 5168f68946 | |||
| 9a32615122 | |||
| 48dcc5180a | |||
| 32868e534e | |||
| 773c54f912 | |||
| 82e8048a8a | |||
| 633b467a22 | |||
| b0ae3d14aa | |||
| 2d8bfb1a15 | |||
| 30366dbe98 | |||
| 55bd17d6f5 | |||
| f310292da4 | |||
| 81546f3270 | |||
| 91dee14ad6 | |||
| 231d44ffa3 | |||
| 70c247d357 | |||
| 1579bf2d18 | |||
| d9ac399c74 | |||
| 378b8a60c6 | |||
| 70cdcfcae1 | |||
| a473935782 | |||
| 16f0873488 | |||
| 17b4aaa1f7 | |||
| eddbad2c76 | |||
| 2e932dd85c | |||
| 3bc7127743 | |||
| d6a69dbca5 | |||
| 864b0ec149 | |||
| abdbb347eb | |||
| 651ee5eb19 | |||
| e1f5fb798c | |||
| 14349890e3 | |||
| 81555d8def | |||
| cc76869da5 | |||
| 6523258b53 | |||
| 1cbe821145 | |||
| 61d8bf9440 | |||
| d1f5fb4aa9 | |||
| 0960637aa4 | |||
| a73d451576 | |||
| ce9ce8293b | |||
| 82a8dc7b3a | |||
| 404b9a4a11 | |||
| f4f2f46f50 | |||
| 6c74c67069 | |||
| 645ddcb749 | |||
| 8647ca5f0b | |||
| ee861e650d | |||
| 349c44fcc0 | |||
| 78b8307a1f | |||
| 7c580bbeed | |||
| 6b7d73dcd5 | |||
| c57f726ecb | |||
| 56b66ab64f | |||
| 403690a498 | |||
| f7dc4df784 | |||
| 819dbdbf59 | |||
| 2293bd6855 | |||
| caf07d09bf | |||
| b2d67df8d0 | |||
| 73c992c5af | |||
| a46f02a136 | |||
| 62b3b31b8d | |||
| d4480c1361 | |||
| 2e75faefae | |||
| 9bde1dc9af | |||
| afac34de88 | |||
| 1cbfdce457 | |||
| cb953f949f | |||
| 52deb16676 | |||
| 89a98be605 | |||
| 813022aec1 | |||
| 0d5f20cae1 | |||
| 4b5b98b465 | |||
| 556892aebf | |||
| 7a3aa8f97d | |||
| 1bd43bbd2d | |||
| 77d5eabd00 | |||
| 9050aa1de0 | |||
| 445691bab1 | |||
| d6722e353f | |||
| b2d96d0753 | |||
| 7f1759ab0a | |||
| 3cb128963c | |||
| 555a3113a1 | |||
| dffb9cdaf9 | |||
| 6e65f0694e | |||
| bee01cfd1e | |||
| da866ed326 | |||
| b0b98e8c63 | |||
| 83e17a7c65 | |||
| 771da0e1d6 | |||
| 75d8d8a43e | |||
| 80610e62f8 | |||
| fa9b8d63f9 | |||
| d4316558de | |||
| 690857e0d4 | |||
| 73705a3b2e | |||
| 7510a54f81 | |||
| 8235c9eea1 | |||
| 8cd32f9706 | |||
| b28b9686fe | |||
| 72b9d8b250 | |||
| 6abfea6d8d | |||
| d599aba9ca | |||
| 02f19c0c1d | |||
| 3181685158 | |||
| f93c6c2b35 | |||
| a134626849 | |||
| 31198d6c63 | |||
| 92dc45ad76 | |||
| 8a5b08e3f0 | |||
| 1a368436b4 | |||
| 59a19ca87a | |||
| e653598cd0 | |||
| 891c9610e9 | |||
| 9265c23e1a | |||
| 11130806f9 | |||
| 0d065dd021 | |||
| f5dc8d2f78 | |||
| 07e459d577 | |||
| aa791ad0c4 | |||
| 17fc992ba9 | |||
| 115392ff2d | |||
| b1545d1d7d | |||
| 9e2d614b41 | |||
| 917c5edfe5 | |||
| 1285bd52c9 | |||
| 597c1c87d4 | |||
| 58bed2cbff | |||
| 13fad04d14 | |||
| eb2ad7e699 | |||
| 511f73812d | |||
| a7d9fa7301 | |||
| bb77c39553 | |||
| a1a6bd79ac | |||
| cdb379a22c | |||
| 9336356efd | |||
| 8e9888c411 | |||
| 027418aba5 | |||
| f36da63a3a | |||
| 5bc9860f5d | |||
| c237570bc2 | |||
| 0dcbe10a22 | |||
| 581f011b28 | |||
| fb2c74c4aa | |||
| 8dbd6007ed | |||
| c9faa5bde7 | |||
| de6675a433 | |||
| 81d364a97a | |||
| 02b7ba50fe | |||
| e64d69350f | |||
| 2b48732de4 | |||
| 29e626bf80 | |||
| 2e13a56ca9 | |||
| 976baccceb | |||
| f8412d7c5d | |||
| 5a8d99232c | |||
| fe70217703 | |||
| 20f6e4714e | |||
| b76eba62ea | |||
| 5b6351577a | |||
| 894f0abdfa | |||
| c89944fe12 | |||
| fe1a4908b5 | |||
| d60ef4c212 | |||
| 5161a9d4e8 | |||
| eb0dc4f4ca | |||
| 6b9a523dc9 | |||
| 3e97054390 | |||
| 69d10702ee | |||
| 455107ff5f | |||
| 5ca3f221cc | |||
| 7ffe06c77f | |||
| bdea5d3598 | |||
| a07eb3e023 | |||
| c3ef12debb | |||
| 739412119e | |||
| 90d9ed1ee4 | |||
| 7d96d831c1 | |||
| b2eb9bc512 | |||
| 71fc696f81 | |||
| 234280ddc7 | |||
| 7a76cabe26 | |||
| a610d8b699 | |||
| 98a78b7955 | |||
| 493d3bc7aa | |||
| ac6ae9e8dd | |||
| a6c0a3d59b | |||
| 0b5bcaf74a | |||
| 3ee28ef7cc | |||
| 3f68689367 | |||
| 6fc578f2ee | |||
| cd4c730b9c | |||
| b0b66be747 | |||
| e04a690303 | |||
| bc44a3584f | |||
| 5e986442a2 | |||
| 19ad0eb238 | |||
| fe4944e031 | |||
| a51df8db6e | |||
| b605295f9d | |||
| dd69d47543 | |||
| 3b7449fb92 | |||
| 0514ccb047 | |||
| cab6ee9d86 | |||
| 63032e1397 | |||
| 8cef084bd2 | |||
| 6942d0bcc3 | |||
| 979c2b5a74 | |||
| a62a9b6460 | |||
| 67da88f0bd | |||
| 4fd7ec27f5 | |||
| 19a8b79342 | |||
| be111b6749 | |||
| b6711e407e | |||
| 3bcc60066e | |||
| 8d783bae15 | |||
| e0bdbcf063 | |||
| f32332cdfc | |||
| 399bb31e13 | |||
| eae5d1b764 | |||
| 1459cb8a56 | |||
| 53638d094a | |||
| 8d3fd94d7d | |||
| e27dda3ff1 | |||
| 1142cdad1b | |||
| 2028698336 | |||
| 5d1d797330 | |||
| a67539075f | |||
| 75053cf40d | |||
| cbf06b4785 | |||
| cc7ecfb39e | |||
| 04943ca469 | |||
| 89f99c4251 | |||
| 1a6c162371 | |||
| ead2655a62 | |||
| 526912756b | |||
| aa176917fa | |||
| be25c5ada7 | |||
| d2dee01f7c | |||
| b9e79aac6c | |||
| 2e9f3d3308 | |||
| 39e18e3738 | |||
| baef586581 | |||
| db17504c82 | |||
| 85e59c14d2 | |||
| d48b4f2438 | |||
| 6925f388e7 | |||
| bc1b4163a2 | |||
| 4e459b9466 | |||
| e3e888b8de | |||
| b1dfb4b5f2 | |||
| 2f372261d3 | |||
| 2187bccc38 | |||
| 6c0c30c032 | |||
| 166e742b3c | |||
| 76447a8810 | |||
| 0232f5958f | |||
| e042fdb111 | |||
| 01855b4e32 | |||
| 9275a0020e | |||
| fbc8466c3c | |||
| bd69e3ad6c | |||
| 3b963c4f18 | |||
| 72b2209c3e | |||
| 75c5803431 | |||
| d03eacd160 | |||
| c13df5645e | |||
| a76d3d46fc | |||
| e5535c80da | |||
| 41484213fd | |||
| a4cf03dbf0 | |||
| 8935da5b5c | |||
| 9c12790011 | |||
| bc963e7855 | |||
| 877a864ba3 | |||
| 033bec01e3 | |||
| 5f89f481e4 | |||
| 95057c1182 | |||
| f6e4d95c6f | |||
| 4409c49494 | |||
| f32b394a96 | |||
| c09df6cf93 | |||
| 497911fc5d | |||
| 46ac8f97eb | |||
| 0964724b2a | |||
| ccc684b095 | |||
| 2f075b7b77 | |||
| e1dd9ed875 | |||
| e7d70933ac | |||
| 611a9c0136 | |||
| 834ca8a08d | |||
| 34bc4da8d8 | |||
| 3727e2426d | |||
| 7aa5e8f435 | |||
| 6129065cfa | |||
| 86eebc21b4 | |||
| 56f5a7846f | |||
| bb4dae26da | |||
| 2afce7822f | |||
| e249a21f83 | |||
| 2331e23f49 | |||
| 110819525f | |||
| 3f6579ffbc | |||
| 92ec42565e | |||
| f84a1c88cf | |||
| a1205f347f | |||
| 674ee7f5f6 | |||
| 4d1dfe8914 | |||
| dc4d72cf77 | |||
| 57ffba34c2 | |||
| 2f97ef8b81 | |||
| 4548f2faf0 | |||
| f01dd3e942 | |||
| 53a5b602dc | |||
| b49dd8b8c4 | |||
| 4c147c5f29 | |||
| 9820880cd5 | |||
| 8713465df1 | |||
| cb9b97411b | |||
| 069ad5332d | |||
| 1d7daab4e8 | |||
| 6cdfec1284 | |||
| ab77bb5466 | |||
| c8f5585f3b | |||
| e117ce1b7b | |||
| b0d5ac0b33 | |||
| 5bb22101e2 | |||
| f70ebd6983 | |||
| 5cb10cf9fa | |||
| 3259c1c2ea | |||
| 58a4b594db | |||
| 7471d49b5a | |||
| 67cced15b9 | |||
| 7fffae5b1a | |||
| bda56552eb | |||
| d2104ba4e1 | |||
| 3581b137e1 | |||
| 44abf1ff28 | |||
| 7814527e0d | |||
| 57d5bc693c | |||
| b85b63018d | |||
| fd990cf4d8 | |||
| da50d95873 | |||
| 2f899ba76f | |||
| d11a715eda | |||
| 6d9094ceca | |||
| d50fc6728e | |||
| c316bfae25 | |||
| 37a23e69d7 | |||
| 13412129ec | |||
| 94257c3e4b | |||
| 4dc48da638 | |||
| 002104c62e | |||
| 3844883321 | |||
| fb64d79379 | |||
| 9d2fd3e52c | |||
| 7d497615a1 | |||
| fe40651b0d | |||
| 3c36cd64e3 | |||
| 7d97830ad7 | |||
| a1fe809197 | |||
| 236423d058 | |||
| c8c7169465 | |||
| 64f8cf8a9e | |||
| c99de764bc | |||
| c9463e3308 | |||
| 87beae3298 | |||
| 3e6acdfb4a | |||
| ebc82f2968 | |||
| 9890cc89b6 | |||
| 62ad40b1e4 | |||
| ca1ac76108 | |||
| ceb377621e | |||
| d70e92d781 | |||
| c0a0bae91e | |||
| e7c93ae68d | |||
| 55986739c9 | |||
| 8539a7d7ea | |||
| 23d008cd79 | |||
| 5a82e3ac70 | |||
| 4117a5e856 | |||
| 6cf078ae83 | |||
| 27be3b400e | |||
| fa4ebab036 | |||
| df2878739c | |||
| cab98b3fd5 | |||
| 60ffcd78f1 | |||
| 6557136426 | |||
| 50b0ef4db9 | |||
| 18074abfdd | |||
| d429ba812c | |||
| 0d09816c39 | |||
| 4caf4da9a6 | |||
| 961640bd2c | |||
| 5d92dce21d | |||
| 50f5356c89 | |||
| 3156cdf10e | |||
| 7a5ec47b58 | |||
| b22e8b706a | |||
| 0ddf0d10a5 | |||
| 0ed8ae5975 | |||
| 1283a59465 | |||
| ad1aec6cd1 | |||
| 7d7177a6af | |||
| f5e6c9a2a3 | |||
| 7cd5a04087 | |||
| bfff7d721d | |||
| aaff63da58 | |||
| 95a3cc88cd | |||
| 70c1a3f60b | |||
| c24af4ca63 | |||
| e31b2582b6 | |||
| 479e5cb107 | |||
| 887a65aa18 | |||
| e4a6d20328 | |||
| f5e1479afe | |||
| a10493976e | |||
| 4caac4a322 | |||
| e98df7acb1 | |||
| f20d2bb2ff | |||
| a5e4f34dbb | |||
| 1c9760f6d2 | |||
| 2b1ea7d85b | |||
| 1677603e59 | |||
| a95286d511 | |||
| 6e18e292f3 | |||
| 114dd4d9e7 | |||
| 0495112aab | |||
| f3483529a7 | |||
| ffc4f3dc48 | |||
| a9fba750fa | |||
| 13220d44f4 | |||
| 0c9e0168d5 | |||
| db4fe25274 | |||
| e948aafeca | |||
| 4639c0faf0 | |||
| 82cd0adb79 | |||
| 1bcd481fdc | |||
| 768ea20a3a | |||
| bb1ab19997 | |||
| dd159242e3 | |||
| f8886ee36c | |||
| 67af2908f9 | |||
| 9df6e1fe0a | |||
| a658348ac1 | |||
| 75ca3d8975 | |||
| b431a9099e | |||
| 7ed6614ad4 | |||
| 4fba4db32f | |||
| a0554183c5 | |||
| b1ad7ad0ba | |||
| 0537eea929 | |||
| cfc9089e51 | |||
| bb926f8f87 | |||
| c2f242b4a6 | |||
| 3f1c8f98ed | |||
| 7a3ce2e81a | |||
| 8c0cc95a55 | |||
| 38dfef923c | |||
| 89bceb959a | |||
| 8deb2b57af | |||
| 60915a7f21 | |||
| 69869f0446 | |||
| 1eb2b1f047 | |||
| a89b400c04 | |||
| 6f3aeead07 | |||
| 3da20486c1 | |||
| d911cd1124 | |||
| a7bd529dac | |||
| 857c87499a | |||
| 381dff1afc | |||
| 378a6b2ff2 | |||
| 5b782ad5a1 | |||
| 93ed18c2e2 | |||
| 95e96cc346 | |||
| 0e2cd7a8d0 | |||
| 332b474a9e | |||
| 7434234c27 | |||
| 848e72bcab | |||
| eb26edf5f5 | |||
| 440299656f | |||
| 4ac69edfe7 | |||
| 9ccdcfc849 | |||
| 514d5aab14 | |||
| 2a09bdad13 | |||
| dfa966a576 | |||
| 573ff2564b | |||
| 929022bc0f | |||
| 21a36819b0 | |||
| efd99f5b28 | |||
| 913684e5a5 | |||
| 40e06b55c0 | |||
| 182f4b008b | |||
| c3df4f3eb0 | |||
| 2c1b8b0930 | |||
| 3cca748bd5 | |||
| 2a05ca5f2b | |||
| 3173d9167f | |||
| f7b5afec51 | |||
| bc355eebaa | |||
| 78f9586839 | |||
| 040892d1a2 | |||
| 71cd4917d8 | |||
| b989297725 | |||
| cf6d842d45 | |||
| 733e84a4a8 | |||
| 9ac9a84ca7 | |||
| 4f369d048d | |||
| c1a7e30f12 | |||
| 4dcfde7737 | |||
| 0838514c27 | |||
| 29f3ef4716 | |||
| a8e5437271 | |||
| a363d988a7 | |||
| e1968b6b52 | |||
| 99a6f12026 | |||
| eca650efac | |||
| bde4b7b057 | |||
| a1e9b55ead | |||
| 1c4cc57e3f | |||
| 26cd3a78d2 | |||
| 3ed3c94a15 | |||
| ff338a0a62 | |||
| 5138a9709e | |||
| ae93cf2c28 | |||
| 1c2d639179 | |||
| 2078f9ae95 | |||
| 1fea1e3f4e | |||
| b2bd51d3e3 | |||
| 80a7f5123d | |||
| 8746175f1d | |||
| 236d8fa254 | |||
| 3872907f38 | |||
| 98f79f0c47 | |||
| 795992fb30 | |||
| 96504ffd88 | |||
| 5e7b217d6c | |||
| f07513c74b | |||
| 22fffb32ca | |||
| abcb3f22a8 | |||
| f7a5689c45 | |||
| bebc5a137a | |||
| 5625f7d984 | |||
| c889491e2f | |||
| cd09381efd | |||
| 306cff729c | |||
| 4083267cc9 | |||
| 12573ed8d2 | |||
| 6b4651fad6 | |||
| 0095f4fbf9 | |||
| 372658d299 | |||
| 227514c20c | |||
| 9a6c869cb8 | |||
| dce7b1430b | |||
| 162fbc2ef8 | |||
| 13b8940606 | |||
| f1b9f1fb63 | |||
| 861cc87272 | |||
| e5fb89731c | |||
| 2565a670b1 | |||
| 74cd5ffb02 | |||
| c3303ce364 | |||
| 2042ab090d | |||
| 13a231f6fa | |||
| 503defac73 | |||
| 2d41c81890 | |||
| 036095f37d | |||
| a2bb22a0e7 | |||
| 65926460b2 | |||
| f4e35033e7 | |||
| 75793f7cf4 | |||
| e36c530732 | |||
| 53edf861e0 | |||
| 24afead423 | |||
| 2f5211ab74 | |||
| b168cd7cf2 | |||
| 667fc07ec6 | |||
| e5a26b388f | |||
| 479b04a6a3 | |||
| b12b18d59f | |||
| b3955fca3a | |||
| bac5c30380 | |||
| 4ce2890af0 | |||
| 169a0471b9 | |||
| 6bdf4aa960 | |||
| 36676f0719 | |||
| bb1fca058d | |||
| b4c161e04a | |||
| 376741c9a1 | |||
| 04e86829e3 | |||
| 75dbcd5c68 | |||
| 283aa52b31 | |||
| 29b2da42f5 | |||
| 330a4c9452 | |||
| fa23b83dbb | |||
| 81f4644622 | |||
| 095cd6ce9b | |||
| f5eb9e3c9f | |||
| 98f93104a7 | |||
| 3940a6bab3 | |||
| de78508f45 | |||
| fab29f4cf0 | |||
| adfca08e43 | |||
| 35e8b95928 | |||
| f5091aa1ae | |||
| bb08207569 | |||
| 71e0d7d8e2 | |||
| 3b27670d4d | |||
| 944dd1cc7f | |||
| ba3a45452e | |||
| f7c5a4a15b | |||
| 4d5bfe2ee8 | |||
| a1a6469ed0 | |||
| fcf66c2e75 | |||
| b4f0bb65c6 | |||
| 938a4007b3 | |||
| 6470e6cc25 | |||
| 9769a17866 | |||
| 19355e947e | |||
| d9a8c89b5a | |||
| 6688b4ea78 | |||
| d05565c6e0 | |||
| ec26a28cd2 | |||
| 05755b9b09 | |||
| 007cafa7cd | |||
| a9ce2fc38a | |||
| 0175932d86 | |||
| 26cdea53c7 | |||
| e585b07d76 | |||
| 6eb5b544b4 | |||
| 9c0495b563 | |||
| 2c25db28c4 | |||
| 14464ab6cc | |||
| a4b2d032f9 | |||
| 3078c28d1a | |||
| cf90c8176e | |||
| 0a8bcf6530 | |||
| bf0dbeb8b3 | |||
| b453ee5e1f | |||
| 2e8fe6a1ab | |||
| e0be566491 | |||
| 936b2d4188 | |||
| 9a204593e1 | |||
| 6aa4bb9f08 | |||
| 562e63a3b0 | |||
| 663e29af89 | |||
| 4f6b67b190 | |||
| 1b85310f1a | |||
| 6aa3706bec | |||
| 92ad67a814 | |||
| 14399e3d57 | |||
| a321ab3653 | |||
| ce3fa18fb6 | |||
| 4064e933fe | |||
| f5868a0952 | |||
| 35c1c3774c | |||
| 628b03f3c0 | |||
| c6f3fbb82c | |||
| 504cc7b7dd | |||
| e6205e8f26 | |||
| 620a50223c | |||
| 646adbf186 | |||
| 73f2fc76d9 | |||
| 553969f0fd | |||
| ee142c5fe1 | |||
| 97dfcb9fb0 | |||
| 97ee31e751 | |||
| 338af10aaf | |||
| 020a3e6d6b | |||
| 5e05ad03bb | |||
| 8d988d1bd2 | |||
| f3db058fef | |||
| b49a15efeb | |||
| be5b35e999 | |||
| 30697ca0a1 | |||
| 0e19ca68b4 | |||
| f82131af15 | |||
| 16ca8d178d | |||
| cdac156f2e | |||
| ba335346f2 | |||
| 82b3790f85 | |||
| 52c0fd8f09 | |||
| e37b26e068 | |||
| 6ac4157fc1 | |||
| b9cbb7a72b | |||
| 0aca6920a7 | |||
| a6e8e10a3d | |||
| bfbc69e8f9 | |||
| 13c76c12f8 | |||
| 08ebf50480 | |||
| a6b5e6360b | |||
| b49f8a81a8 | |||
| ab5b66facc | |||
| 35c6ea68cc | |||
| b74cb8828b | |||
| a1b84c6fa3 | |||
| 3e3d01063d | |||
| c19cc61c48 | |||
| 5854edafdd | |||
| 2c33e72eea | |||
| 24ab5026c9 | |||
| c74ef80f79 | |||
| eeb11046d5 | |||
| 66f3f0f3de | |||
| a1bce24461 | |||
| 1c2d0afd95 | |||
| 6e52ca3f4e | |||
| 1d1f72fdc2 | |||
| d25bd8c1c6 | |||
| 3e0827deea | |||
| 481c7ea4e6 | |||
| 646477be08 | |||
| 3e2c626696 | |||
| 38ce8b494e | |||
| 292e2f6b69 | |||
| 94c3a384b1 | |||
| 5f07b4bf9b | |||
| 68754c4b5d | |||
| ff4ccc1d85 | |||
| d84a2f8215 | |||
| 86738e965f | |||
| 818f28d85f | |||
| 5f7356c16c | |||
| b0b708ddea | |||
| 4adf23b4e9 | |||
| 2f29e761c2 | |||
| 8aede81188 | |||
| c151da684d | |||
| bf4e927142 | |||
| 7cb0b1a911 | |||
| 6cbae7025c | |||
| 202bece16d | |||
| f07df01185 | |||
| 702978fa4b | |||
| b79473b2f5 | |||
| 80b59441d5 | |||
| f422ec457a | |||
| 5048b6bdaf | |||
| 01dc4611b9 | |||
| 5495516c30 | |||
| 2f8112b108 | |||
| 229c24e1f2 | |||
| 832d59803a | |||
| 197639a34b | |||
| bb229a3a0c | |||
| eca818be5a | |||
| 33896adf9d | |||
| f5223b1643 | |||
| ecc2bbd752 | |||
| d019405d51 | |||
| 7d62dff7e5 | |||
| dd8e4c620b | |||
| 77a62670d3 | |||
| 8e8b78a4c3 | |||
| 249ee4b318 | |||
| 226bcd1360 | |||
| 4956ed11f4 | |||
| b44a3c7650 | |||
| fa83147926 | |||
| 14afea1a20 | |||
| 4fdc4a2e06 | |||
| 0864e8a24f | |||
| 6af85aea18 | |||
| 0d4155b902 | |||
| 91193bf928 | |||
| b57248e822 | |||
| 487b80b9b0 | |||
| 818a70bdd7 | |||
| 940f5be35e | |||
| 88fd7b226a | |||
| 2f6cea2be3 | |||
| b4a1b4c8fb | |||
| 2f3963417f | |||
| 8de03275c3 | |||
| 60f52b2cd8 | |||
| dd101c99e5 | |||
| a45e6da3d0 | |||
| 2b6f45e4c1 | |||
| 332de67fe2 | |||
| ff3fd02115 | |||
| 437c9d07f5 | |||
| 9abe47f350 | |||
| 7a884ced32 | |||
| 0ace8bda8c | |||
| 15bc238297 | |||
| 1b94c7a8b3 | |||
| c31f99937f | |||
| 4258b99b2b | |||
| 29c0e17ca2 | |||
| d51d9e9fff | |||
| d5f2c41f26 | |||
| 7eb395b87d | |||
| 19d7d79229 | |||
| 526dc54173 | |||
| a136192db2 | |||
| 5dbc4f9954 | |||
| 447d3818f5 | |||
| 7c45f8f053 | |||
| 497d4d26bb | |||
| db6a8c2e05 | |||
| a16230c9ca | |||
| 0bbbf468f0 | |||
| 9bcba6c8b8 | |||
| a1fe76fb5b | |||
| dc613df3b1 | |||
| 45a8a0fb53 | |||
| aa210166c4 | |||
| 43a3fef7bb | |||
| 55bebb0f62 | |||
| 18ae44f930 | |||
| dc3899dfd9 | |||
| b6b4b0cdf9 | |||
| 8be73967fd | |||
| 2e26b6bf8b | |||
| b473862da6 | |||
| 0a9581a375 | |||
| 5bd67495ed | |||
| 23509c1688 | |||
| a63f548528 | |||
| ee8ed2828a | |||
| 35daf5df78 | |||
| 3ca2d4dab3 | |||
| c12b1870b7 | |||
| fd36726357 | |||
| 8dc1bb0659 | |||
| 38413dc236 | |||
| ef94ad36cd | |||
| e40e4298ca | |||
| 1d3947c0da | |||
| 69349925a3 | |||
| 921c7ede0d | |||
| e202ec009e | |||
| ced30acb20 | |||
| 3debaf99d8 | |||
| 2908e5da19 | |||
| 5afe7cfafb | |||
| d7829a76ce | |||
| ec779c001c | |||
| 028509c3a9 | |||
| fd5e312ed9 | |||
| 6710d66a58 | |||
| 1d44abfc8a | |||
| 0141779b9c | |||
| 92b1e9d9ac | |||
| 43b59911e2 | |||
| 240e37c387 | |||
| 39f71ab2ab | |||
| 6e37389145 | |||
| 554a9a72f1 | |||
| fd967d6976 | |||
| b451f07801 | |||
| 5ca560d0bd | |||
| 13fcbd6413 | |||
| 8782597c1a | |||
| ab4750ec1e | |||
| b519fdd3cb | |||
| 08cbaf8ccf | |||
| 6b2a838e24 | |||
| 854b448536 | |||
| 2195e433dc | |||
| d13be09524 | |||
| e34d6e3678 | |||
| 79247dad41 | |||
| 61691ac47d | |||
| 3e756f5d1c | |||
| 026c4a2711 | |||
| cd3556f195 | |||
| d0a6ae7855 | |||
| b245d68568 | |||
| 2c4effe6ce | |||
| 5ed381fc58 | |||
| 4c08e65fab | |||
| 4dde341c9d | |||
| ed3cae6f11 | |||
| 56c33e994d | |||
| d6820f2c2d | |||
| cf4b7b36ee | |||
| d23ebd354b | |||
| 66c60c4e6d | |||
| 19b803bb03 | |||
| 982f61774d | |||
| 7982492f50 | |||
| 7af7afc30e | |||
| ab2dc5877f | |||
| 592c98fd7a | |||
| facf17e689 | |||
| 1db9642e4c | |||
| 4df4527e77 | |||
| dbf0bb209d | |||
| 9a7c6b8be1 | |||
| 36fc43c364 | |||
| f80758f0de | |||
| 25ef60a2c1 | |||
| 5bfdb71b4c | |||
| 890b1c4f60 | |||
| 4046ecc99e | |||
| fe297e1dea | |||
| f0c0b2230a | |||
| 23ec55975e | |||
| 54da31e805 | |||
| b0f29b20db | |||
| c13ed8c625 | |||
| 79463d7f7d | |||
| 77ff657351 | |||
| 85412f0a41 | |||
| 54a6992ae0 | |||
| 2c35514537 | |||
| 7fea7b30b2 | |||
| f0d55c9a3f | |||
| 3336d66fc4 | |||
| 19d27258d9 | |||
| 58e4b98d61 | |||
| c73e87e9c8 | |||
| 6482c11f92 | |||
| f4a952032a | |||
| dd5a7bcd54 | |||
| 34f6de0c84 | |||
| 2678342c4a | |||
| 0f53fea903 | |||
| 9b1e5928bd | |||
| 31ef821e46 | |||
| 3119badb2b | |||
| acae6e20de | |||
| 7a17baccac | |||
| 4956259124 | |||
| a5f70e15a2 | |||
| 2df100213b | |||
| e989d41d50 | |||
| edc0a9ac87 | |||
| 5334a2d965 | |||
| 7c10f3422a | |||
| ea62092d6e | |||
| e977d099e2 | |||
| bf82bac328 | |||
| 25bd2bc7fe | |||
| 93b26d947c | |||
| 8290026940 | |||
| 4badd3b6ac | |||
| 69a2194efe | |||
| b5c8b99272 | |||
| 35926b96b4 | |||
| d1cf37bbec | |||
| 4c61d254cc | |||
| 9c68810c77 | |||
| 4417608b27 | |||
| a105dcb060 | |||
| 0cf0992f78 | |||
| 842ebfcca0 | |||
| 376ee2ed90 | |||
| 2f1397c50e | |||
| cdbb000638 | |||
| 8a413018c2 | |||
| 03f2de75f5 | |||
| 38c0f3cdc8 | |||
| 4d95ac08dd | |||
| fc77b48344 | |||
| ff500d8297 | |||
| e2ee44617c | |||
| 25009ff9f4 | |||
| bc51bd0dbd | |||
| 8a27b5446d | |||
| 2cc2039ad8 | |||
| 04f6a2be37 | |||
| 2538001b08 | |||
| e8f630b93a | |||
| 49210e3099 | |||
| 96c0ff44f9 | |||
| 56bccce713 | |||
| 89be1c2ede | |||
| c77f7e727b | |||
| 36080fb682 | |||
| 98f51dc91f | |||
| d2524b20d5 | |||
| 068c93e562 | |||
| e0e58711bb | |||
| 2cbd61f268 | |||
| 4827db4f56 | |||
| 1a44fe29b4 | |||
| e032a8c54f | |||
| 214ef31053 | |||
| 978adbbd94 | |||
| e6535dcc67 | |||
| d3ddf10839 | |||
| 69805f36fa | |||
| 6a3436b1c4 | |||
| 62f9f6e6c5 | |||
| 96aaba738b | |||
| f83fc51960 | |||
| e1575e3177 | |||
| 3d28a7837d | |||
| 44b4c29d08 | |||
| 9f2fda6060 | |||
| 3bfb2b5cc1 | |||
| 82b97b9639 | |||
| d01c24bd23 | |||
| 163d185776 | |||
| 6305ddca18 | |||
| b82b66fdf0 | |||
| b425093813 | |||
| f30d2b9f1c | |||
| 9e96043255 | |||
| bd300e870c | |||
| e06f533f6b | |||
| 7088ba88a9 | |||
| 0313adaec6 | |||
| 50b1e4c36f | |||
| a94caf218f | |||
| ab2b70f189 | |||
| 3190c0ed6c | |||
| 461a5eb315 | |||
| 01ef834b82 | |||
| 0211c8145d | |||
| 4a9892fa15 | |||
| 71bfefa788 | |||
| 071c858055 | |||
| 0244963935 | |||
| 3adb238b31 | |||
| e3100110f0 | |||
| 5224a796cc | |||
| 285b3858dd | |||
| 53bea055aa | |||
| 129b0e99cf | |||
| c0e4af9ac0 | |||
| ccd94bb734 | |||
| e1b3bd611d | |||
| 6ab7e339bd | |||
| 67a87af459 | |||
| aaecdc4b8a | |||
| 78466384c4 | |||
| ca7a0813b4 | |||
| bc0d8613ab | |||
| 0434c1424a | |||
| 3f8a303286 | |||
| 5ceffdd5a7 | |||
| 01da93bf70 | |||
| 028a3c2ded | |||
| 1698b8ec04 | |||
| 9e018532c8 | |||
| 24cb7cb039 | |||
| 7c28aba6a1 | |||
| 2309f9b8bc | |||
| ddcb94649d | |||
| 5f7e16eac3 | |||
| da034b3090 | |||
| 12d515a8fe | |||
| f5f3c85112 | |||
| d8153b702a | |||
| 9a4f5bb2d9 | |||
| 7624284070 | |||
| c32cbd90e8 | |||
| c068d20588 | |||
| c74ab439ef | |||
| 44c77364ce | |||
| 0dbbf53fbe | |||
| 052d006c88 | |||
| 0e91ea9e5f | |||
| 5803c51633 | |||
| 35a7342416 | |||
| e9d7ac68da | |||
| 56a0c8ed1d | |||
| 4eaa8e19c9 | |||
| 414948d822 | |||
| 02e6bf8759 | |||
| 8baed08641 | |||
| 4e39f4cab1 | |||
| 9f0d79da9a | |||
| 80fcd2eb56 | |||
| 5aead42964 | |||
| ab75683bc9 | |||
| d8c3aae412 | |||
| 5e78c53572 | |||
| 44d3e844a3 | |||
| 85c2097ece | |||
| cb20c9b0d7 | |||
| d9a493561b | |||
| 70956ece12 | |||
| adaf392156 | |||
| 37b99639d4 | |||
| a8ab6c1568 | |||
| 4a1e45070e | |||
| b33ac47228 | |||
| 6e91399922 | |||
| 75a8be230b | |||
| 07b1210b50 | |||
| 532e8ee228 | |||
| 574dbbfd08 | |||
| 332fb55e3a | |||
| 5610d00b4d | |||
| 8bfccb42a9 | |||
| 7ca5bebab3 | |||
| d5aa4be7e5 | |||
| 1a687a4f1a | |||
| 13b78b1ad2 | |||
| 48cf5cb84a | |||
| 5317dd64d7 | |||
| a25609f66b | |||
| d6e6432a46 | |||
| 094fa47f9f | |||
| 6f7cf2e58b | |||
| b08ae07347 | |||
| ffbf8d5faa | |||
| f55274c7fb | |||
| b9074097da | |||
| 83a33726ce | |||
| 8c33e12616 | |||
| 87218e1447 | |||
| 06965000a5 | |||
| cc033b3b98 | |||
| 290040ad9d | |||
| 264e72830b | |||
| d05c2ee8a1 | |||
| b32aba27c1 | |||
| 22e79490ff | |||
| 1a311a30b5 | |||
| 999f3233bb | |||
| 07651d712f | |||
| 6f931ce4fd | |||
| e93e846e16 | |||
| 4e2e68a3fb | |||
| e95df7999f | |||
| 2a43998444 | |||
| 06eea52ad8 | |||
| 389b5e6448 | |||
| a19f280fcc | |||
| e4646a6788 | |||
| 58bfea2ad1 | |||
| ad4a1c2a59 |
+1
-1
@@ -2,7 +2,6 @@
|
||||
# You can add personal rules in your file .git/info/exclude
|
||||
*.swp
|
||||
*~
|
||||
/contrib/extract_translations/extract_translations.beam
|
||||
/doc/*.aux
|
||||
/doc/*.haux
|
||||
/doc/*.html
|
||||
@@ -29,6 +28,7 @@
|
||||
/src/autom4te.cache
|
||||
/src/config.log
|
||||
/src/config.status
|
||||
/src/configure
|
||||
/src/ejabberd.init
|
||||
/src/ejabberdctl.example
|
||||
/src/eldap/ELDAPv3.asn1db
|
||||
|
||||
@@ -8,19 +8,15 @@ Quickstart guide
|
||||
To compile ejabberd you need:
|
||||
- GNU Make
|
||||
- GCC
|
||||
- Libexpat 1.95 or higher
|
||||
- Erlang/OTP R10B-9 or higher. Recommended: R13B04 and R14B04.
|
||||
- Erlang/OTP R12B-5 or higher. Recommended: R12B-5, R13B04 and R14B01.
|
||||
Avoid R14A and R14B.
|
||||
- exmpp 0.9.6 or higher
|
||||
- OpenSSL 0.9.8 or higher, for STARTTLS, SASL and SSL encryption.
|
||||
- Zlib 1.2.3 or higher, for Stream Compression support
|
||||
(XEP-0138). Optional.
|
||||
- Erlang mysql library. Optional. MySQL authentication/storage.
|
||||
- Erlang pgsql library. Optional. PostgreSQL authentication/storage.
|
||||
- PAM library. Optional. For Pluggable Authentication Modules (PAM).
|
||||
- GNU Iconv 1.8 or higher, for the IRC Transport
|
||||
(mod_irc). Optional. Not needed on systems with GNU Libc.
|
||||
- ESASL library. Optional. For SASL GSSAPI authentication.
|
||||
- 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
|
||||
@@ -29,6 +25,12 @@ To compile ejabberd, go to the directory src/ and execute the commands:
|
||||
./configure
|
||||
make
|
||||
|
||||
If you get an error like:
|
||||
./configure: No such file or directory
|
||||
the solution is to first execute:
|
||||
aclocal
|
||||
autoconf
|
||||
|
||||
To install ejabberd, run this command with system administrator rights
|
||||
(root user):
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
% List of ejabberd-modules to add for ejabberd packaging (source archive and installer)
|
||||
%
|
||||
% HTTP-binding:
|
||||
%https://svn.process-one.net/ejabberd-modules/http_bind/trunk
|
||||
%https://svn.process-one.net/ejabberd-modules/mod_http_fileserver/trunk
|
||||
+3
-17
@@ -1,5 +1,3 @@
|
||||
# $Id$
|
||||
|
||||
SHELL = /bin/bash
|
||||
|
||||
CONTRIBUTED_MODULES = ""
|
||||
@@ -21,11 +19,10 @@ release:
|
||||
@echo "\newcommand{\version}{"`sed '/vsn/!d;s/\(.*\)"\(.*\)"\(.*\)/\2/' ../src/ejabberd.app`"}" >> version.tex
|
||||
@echo -n "% Contributed modules (automatically generated)." > contributed_modules.tex
|
||||
@echo -e "$(CONTRIBUTED_MODULES)" >> contributed_modules.tex
|
||||
@echo "% mod_admin_p1 commands list."
|
||||
|
||||
html: guide.html dev.html features.html commercial.html
|
||||
html: guide.html dev.html features.html
|
||||
|
||||
pdf: guide.pdf features.pdf commercial.pdf
|
||||
pdf: guide.pdf features.pdf
|
||||
|
||||
clean:
|
||||
rm -f *.aux
|
||||
@@ -38,6 +35,7 @@ clean:
|
||||
rm -f *.out
|
||||
rm -f *.pdf
|
||||
rm -f *.toc
|
||||
rm -f version.tex
|
||||
[ ! -f contributed_modules.tex ] || rm contributed_modules.tex
|
||||
|
||||
distclean: clean
|
||||
@@ -61,15 +59,3 @@ guide.pdf: guide.tex
|
||||
|
||||
features.pdf: features.tex
|
||||
pdflatex features.tex
|
||||
|
||||
commercial.html: commercial.tex
|
||||
./mod_admin_p1_commands.sh
|
||||
hevea -fix -pedantic commercial.tex
|
||||
|
||||
commercial.pdf: commercial.tex
|
||||
./mod_admin_p1_commands.sh
|
||||
pdflatex commercial.tex
|
||||
pdflatex commercial.tex
|
||||
pdflatex commercial.tex
|
||||
makeindex commercial.idx
|
||||
pdflatex commercial.tex
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
@author Mickael Remond <mickael.remond@process-one.net>
|
||||
[http://www.process-one.net/]
|
||||
@copyright 2012 ProcessOne
|
||||
@version {@vsn}, {@date} {@time}
|
||||
@title ejabberd Development API Documentation
|
||||
|
||||
@doc
|
||||
== Introduction ==
|
||||
|
||||
TODO: Insert content from Jerome documentation.
|
||||
-6184
File diff suppressed because it is too large
Load Diff
-413
@@ -1,413 +0,0 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Ejabberd 2.1.11 Developers Guide
|
||||
</TITLE>
|
||||
|
||||
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
|
||||
<META name="GENERATOR" content="hevea 1.10">
|
||||
<STYLE type="text/css">
|
||||
.li-itemize{margin:1ex 0ex;}
|
||||
.li-enumerate{margin:1ex 0ex;}
|
||||
.dd-description{margin:0ex 0ex 1ex 4ex;}
|
||||
.dt-description{margin:0ex;}
|
||||
.toc{list-style:none;}
|
||||
.thefootnotes{text-align:left;margin:0ex;}
|
||||
.dt-thefootnotes{margin:0em;}
|
||||
.dd-thefootnotes{margin:0em 0em 0em 2em;}
|
||||
.footnoterule{margin:1em auto 1em 0px;width:50%;}
|
||||
.caption{padding-left:2ex; padding-right:2ex; margin-left:auto; margin-right:auto}
|
||||
.title{margin:2ex auto;text-align:center}
|
||||
.center{text-align:center;margin-left:auto;margin-right:auto;}
|
||||
.flushleft{text-align:left;margin-left:0ex;margin-right:auto;}
|
||||
.flushright{text-align:right;margin-left:auto;margin-right:0ex;}
|
||||
DIV TABLE{margin-left:inherit;margin-right:inherit;}
|
||||
PRE{text-align:left;margin-left:0ex;margin-right:auto;}
|
||||
BLOCKQUOTE{margin-left:4ex;margin-right:4ex;text-align:left;}
|
||||
TD P{margin:0px;}
|
||||
.boxed{border:1px solid black}
|
||||
.textboxed{border:1px solid black}
|
||||
.vbar{border:none;width:2px;background-color:black;}
|
||||
.hbar{border:none;height:2px;width:100%;background-color:black;}
|
||||
.hfill{border:none;height:1px;width:200%;background-color:black;}
|
||||
.vdisplay{border-collapse:separate;border-spacing:2px;width:auto; empty-cells:show; border:2px solid red;}
|
||||
.vdcell{white-space:nowrap;padding:0px;width:auto; border:2px solid green;}
|
||||
.display{border-collapse:separate;border-spacing:2px;width:auto; border:none;}
|
||||
.dcell{white-space:nowrap;padding:0px;width:auto; border:none;}
|
||||
.dcenter{margin:0ex auto;}
|
||||
.vdcenter{border:solid #FF8000 2px; margin:0ex auto;}
|
||||
.minipage{text-align:left; margin-left:0em; margin-right:auto;}
|
||||
.marginpar{border:solid thin black; width:20%; text-align:left;}
|
||||
.marginparleft{float:left; margin-left:0ex; margin-right:1ex;}
|
||||
.marginparright{float:right; margin-left:1ex; margin-right:0ex;}
|
||||
.theorem{text-align:left;margin:1ex auto 1ex 0ex;}
|
||||
.part{margin:2ex auto;text-align:center}
|
||||
</STYLE>
|
||||
</HEAD>
|
||||
<BODY >
|
||||
<!--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.11 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">
|
||||
|
||||
<IMG SRC="logo.png" ALT="logo.png">
|
||||
|
||||
|
||||
</DIV><BLOCKQUOTE CLASS="quotation"><I>I can thoroughly recommend ejabberd for ease of setup –
|
||||
Kevin Smith, Current maintainer of the Psi project</I></BLOCKQUOTE><!--TOC section Contents-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR -->Contents</H2><!--SEC END --><UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc1">1  Key Features</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc2">2  Additional Features</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc3">3  How it Works</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc4">3.1  Router</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc5">3.2  Local Router</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc6">3.3  Session Manager</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc7">3.4  S2S Manager</A>
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc8">4  Authentication</A>
|
||||
<UL CLASS="toc">
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc9">4.0.1  External</A>
|
||||
</LI></UL>
|
||||
</UL>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc10">5  XML Representation</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc11">6  Module <TT>xml</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc12">7  Module <TT>xml_stream</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc13">8  Modules</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc14">8.1  Module gen_iq_handler</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc15">8.2  Services</A>
|
||||
</LI></UL>
|
||||
</LI></UL><P>Introduction
|
||||
<A NAME="intro"></A></P><P><TT>ejabberd</TT> is a free and open source instant messaging server written in <A HREF="http://www.erlang.org/">Erlang/OTP</A>.</P><P><TT>ejabberd</TT> is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</P><P><TT>ejabberd</TT> is designed to be a rock-solid and feature rich XMPP server.</P><P><TT>ejabberd</TT> is suitable for small deployments, whether they need to be scalable or not, as well as extremely big deployments.</P><!--TOC section Key Features-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc1">1</A>  Key Features</H2><!--SEC END --><P>
|
||||
<A NAME="keyfeatures"></A>
|
||||
</P><P><TT>ejabberd</TT> is:
|
||||
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Cross-platform: <TT>ejabberd</TT> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.</LI><LI CLASS="li-itemize">Distributed: You can run <TT>ejabberd</TT> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.</LI><LI CLASS="li-itemize">Fault-tolerant: You can deploy an <TT>ejabberd</TT> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced ‘on the fly’.</LI><LI CLASS="li-itemize">Administrator Friendly: <TT>ejabberd</TT> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Comprehensive documentation.
|
||||
</LI><LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </LI><LI CLASS="li-itemize">Web Administration.
|
||||
</LI><LI CLASS="li-itemize">Shared Roster Groups.
|
||||
</LI><LI CLASS="li-itemize">Command line administration tool. </LI><LI CLASS="li-itemize">Can integrate with existing authentication mechanisms.
|
||||
</LI><LI CLASS="li-itemize">Capability to send announce messages.
|
||||
</LI></UL></LI><LI CLASS="li-itemize">Internationalized: <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Translated to 25 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
|
||||
</LI></UL></LI><LI CLASS="li-itemize">Open Standards: <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Fully XMPP compliant.
|
||||
</LI><LI CLASS="li-itemize">XML-based protocol.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.ejabberd.im/protocols">Many protocols supported</A>.
|
||||
</LI></UL></LI></UL><!--TOC section Additional Features-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc2">2</A>  Additional Features</H2><!--SEC END --><P>
|
||||
<A NAME="addfeatures"></A>
|
||||
</P><P>Moreover, <TT>ejabberd</TT> comes with a wide range of other state-of-the-art features:
|
||||
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Modular
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Load only the modules you want.
|
||||
</LI><LI CLASS="li-itemize">Extend <TT>ejabberd</TT> with your own custom modules.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Security
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
SASL and STARTTLS for c2s and s2s connections.
|
||||
</LI><LI CLASS="li-itemize">STARTTLS and Dialback s2s connections.
|
||||
</LI><LI CLASS="li-itemize">Web Admin accessible via HTTPS secure access.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Databases
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Internal database for fast deployment (Mnesia).
|
||||
</LI><LI CLASS="li-itemize">Native MySQL support.
|
||||
</LI><LI CLASS="li-itemize">Native PostgreSQL support.
|
||||
</LI><LI CLASS="li-itemize">ODBC data storage support.
|
||||
</LI><LI CLASS="li-itemize">Microsoft SQL Server support. </LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Authentication
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Internal Authentication.
|
||||
</LI><LI CLASS="li-itemize">PAM, LDAP and ODBC. </LI><LI CLASS="li-itemize">External Authentication script.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Others
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Support for virtual hosting.
|
||||
</LI><LI CLASS="li-itemize">Compressing XML streams with Stream Compression (<A HREF="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</A>).
|
||||
</LI><LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</A>).
|
||||
</LI><LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with support for clustering and HTML logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</A>.
|
||||
</LI><LI CLASS="li-itemize">Support for web clients: <A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> and <A HREF="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</A> services.
|
||||
</LI><LI CLASS="li-itemize">IRC transport.
|
||||
</LI><LI CLASS="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
|
||||
</LI></UL>
|
||||
</LI></UL><!--TOC section How it Works-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc3">3</A>  How it Works</H2><!--SEC END --><P>
|
||||
<A NAME="howitworks"></A></P><P>A XMPP domain is served by one or more <TT>ejabberd</TT> nodes. These nodes can
|
||||
be run on different machines that are connected via a network. They all must
|
||||
have the ability to connect to port 4369 of all another nodes, and must have
|
||||
the same magic cookie (see Erlang/OTP documentation, in other words the file
|
||||
<TT>~ejabberd/.erlang.cookie</TT> must be the same on all nodes). This is
|
||||
needed because all nodes exchange information about connected users, S2S
|
||||
connections, registered services, etc…</P><P>Each <TT>ejabberd</TT> node have following modules:
|
||||
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
router;
|
||||
</LI><LI CLASS="li-itemize">local router.
|
||||
</LI><LI CLASS="li-itemize">session manager;
|
||||
</LI><LI CLASS="li-itemize">S2S manager;
|
||||
</LI></UL><!--TOC subsection Router-->
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc4">3.1</A>  Router</H3><!--SEC END --><P>This module is the main router of XMPP packets on each node. It routes
|
||||
them based on their destinations domains. It has two tables: local and global
|
||||
routes. First, domain of packet destination searched in local table, and if it
|
||||
found, then the packet is routed to appropriate process. If no, then it
|
||||
searches in global table, and is routed to the appropriate <TT>ejabberd</TT> node or
|
||||
process. If it does not exists in either tables, then it sent to the S2S
|
||||
manager.</P><!--TOC subsection Local Router-->
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc5">3.2</A>  Local Router</H3><!--SEC END --><P>This module routes packets which have a destination domain equal to this server
|
||||
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 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
|
||||
open S2S connection from the domain of the packet source to the domain of
|
||||
packet destination already exists. If it is open on another node, then it
|
||||
routes the packet to S2S manager on that node, if it is open on this node, then
|
||||
it is routed to the process that serves this connection, and if a connection
|
||||
does not exist, then it is opened and registered.</P><!--TOC section Authentication-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc8">4</A>  Authentication</H2><!--SEC END --><!--TOC subsubsection External-->
|
||||
<H4 CLASS="subsubsection"><!--SEC ANCHOR --><A NAME="htoc9">4.0.1</A>  External</H4><!--SEC END --><P>
|
||||
<A NAME="externalauth"></A>
|
||||
</P><P>The external authentication script follows
|
||||
<A HREF="http://www.erlang.org/doc/tutorial/c_portdriver.html">the erlang port driver API</A>.</P><P>That script is supposed to do theses actions, in an infinite loop:
|
||||
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
read from stdin: AABBBBBBBBB.....
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
A: 2 bytes of length data (a short in network byte order)
|
||||
</LI><LI CLASS="li-itemize">B: a string of length found in A that contains operation in plain text
|
||||
operation are as follows:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
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
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
A: the number 2 (coded as a short, which is bytes length of following result)
|
||||
</LI><LI CLASS="li-itemize">B: the result code (coded as a short), should be 1 for success/valid, or 0 for failure/invalid
|
||||
</LI></UL>
|
||||
</LI></UL><P>Example python script
|
||||
</P><PRE CLASS="verbatim">#!/usr/bin/python
|
||||
|
||||
import sys
|
||||
from struct import *
|
||||
|
||||
def from_ejabberd():
|
||||
input_length = sys.stdin.read(2)
|
||||
(size,) = unpack('>h', input_length)
|
||||
return sys.stdin.read(size).split(':')
|
||||
|
||||
def to_ejabberd(bool):
|
||||
answer = 0
|
||||
if bool:
|
||||
answer = 1
|
||||
token = pack('>hh', 2, answer)
|
||||
sys.stdout.write(token)
|
||||
sys.stdout.flush()
|
||||
|
||||
def auth(username, server, password):
|
||||
return True
|
||||
|
||||
def isuser(username, server):
|
||||
return True
|
||||
|
||||
def setpass(username, server, password):
|
||||
return True
|
||||
|
||||
while True:
|
||||
data = from_ejabberd()
|
||||
success = False
|
||||
if data[0] == "auth":
|
||||
success = auth(data[1], data[2], data[3])
|
||||
elif data[0] == "isuser":
|
||||
success = isuser(data[1], data[2])
|
||||
elif data[0] == "setpass":
|
||||
success = setpass(data[1], data[2], data[3])
|
||||
to_ejabberd(success)
|
||||
</PRE><!--TOC section XML Representation-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc10">5</A>  XML Representation</H2><!--SEC END --><P>
|
||||
<A NAME="xmlrepr"></A></P><P>Each XML stanza is represented as the following tuple:
|
||||
</P><PRE CLASS="verbatim">XMLElement = {xmlelement, Name, Attrs, [ElementOrCDATA]}
|
||||
Name = string()
|
||||
Attrs = [Attr]
|
||||
Attr = {Key, Val}
|
||||
Key = string()
|
||||
Val = string()
|
||||
ElementOrCDATA = XMLElement | CDATA
|
||||
CDATA = {xmlcdata, string()}
|
||||
</PRE><P>E. g. this stanza:
|
||||
</P><PRE CLASS="verbatim"><message to='test@conference.example.org' type='groupchat'>
|
||||
<body>test</body>
|
||||
</message>
|
||||
</PRE><P>is represented as the following structure:
|
||||
</P><PRE CLASS="verbatim">{xmlelement, "message",
|
||||
[{"to", "test@conference.example.org"},
|
||||
{"type", "groupchat"}],
|
||||
[{xmlelement, "body",
|
||||
[],
|
||||
[{xmlcdata, "test"}]}]}}
|
||||
</PRE><!--TOC section Module <TT>xml</TT>-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc11">6</A>  Module <TT>xml</TT></H2><!--SEC END --><P>
|
||||
<A NAME="xmlmod"></A></P><DL CLASS="description"><DT CLASS="dt-description">
|
||||
</DT><DD CLASS="dd-description"><CODE>element_to_string(El) -> string()</CODE>
|
||||
<PRE CLASS="verbatim">El = XMLElement
|
||||
</PRE>Returns string representation of XML stanza <TT>El</TT>.</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>crypt(S) -> string()</CODE>
|
||||
<PRE CLASS="verbatim">S = string()
|
||||
</PRE>Returns string which correspond to <TT>S</TT> with encoded XML special
|
||||
characters.</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>remove_cdata(ECList) -> EList</CODE>
|
||||
<PRE CLASS="verbatim">ECList = [ElementOrCDATA]
|
||||
EList = [XMLElement]
|
||||
</PRE><TT>EList</TT> is a list of all non-CDATA elements of ECList.</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>get_path_s(El, Path) -> Res</CODE>
|
||||
<PRE CLASS="verbatim">El = XMLElement
|
||||
Path = [PathItem]
|
||||
PathItem = PathElem | PathAttr | PathCDATA
|
||||
PathElem = {elem, Name}
|
||||
PathAttr = {attr, Name}
|
||||
PathCDATA = cdata
|
||||
Name = string()
|
||||
Res = string() | XMLElement
|
||||
</PRE>If <TT>Path</TT> is empty, then returns <TT>El</TT>. Else sequentially
|
||||
consider elements of <TT>Path</TT>. Each element is one of:
|
||||
<DL CLASS="description"><DT CLASS="dt-description">
|
||||
</DT><DD CLASS="dd-description"><CODE>{elem, Name}</CODE> <TT>Name</TT> is name of subelement of
|
||||
<TT>El</TT>, if such element exists, then this element considered in
|
||||
following steps, else returns empty string.
|
||||
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>{attr, Name}</CODE> If <TT>El</TT> have attribute <TT>Name</TT>, then
|
||||
returns value of this attribute, else returns empty string.
|
||||
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>cdata</CODE> Returns CDATA of <TT>El</TT>.
|
||||
</DD></DL></DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description">TODO:
|
||||
<PRE CLASS="verbatim"> get_cdata/1, get_tag_cdata/1
|
||||
get_attr/2, get_attr_s/2
|
||||
get_tag_attr/2, get_tag_attr_s/2
|
||||
get_subtag/2
|
||||
</PRE></DD></DL><!--TOC section Module <TT>xml_stream</TT>-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc12">7</A>  Module <TT>xml_stream</TT></H2><!--SEC END --><P>
|
||||
<A NAME="xmlstreammod"></A></P><DL CLASS="description"><DT CLASS="dt-description">
|
||||
</DT><DD CLASS="dd-description"><CODE>parse_element(Str) -> XMLElement | {error, Err}</CODE>
|
||||
<PRE CLASS="verbatim">Str = string()
|
||||
Err = term()
|
||||
</PRE>Parses <TT>Str</TT> using XML parser, returns either parsed element or error
|
||||
tuple.
|
||||
</DD></DL><!--TOC section Modules-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc13">8</A>  Modules</H2><!--SEC END --><P>
|
||||
<A NAME="emods"></A></P><!--TOC subsection Module gen_iq_handler-->
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc14">8.1</A>  Module gen_iq_handler</H3><!--SEC END --><P>
|
||||
<A NAME="geniqhandl"></A></P><P>The module <CODE>gen_iq_handler</CODE> allows to easily write handlers for IQ packets
|
||||
of particular XML namespaces that addressed to server or to users bare JIDs.</P><P>In this module the following functions are defined:
|
||||
</P><DL CLASS="description"><DT CLASS="dt-description">
|
||||
</DT><DD CLASS="dd-description"><CODE>add_iq_handler(Component, Host, NS, Module, Function, Type)</CODE>
|
||||
<PRE CLASS="verbatim">Component = Module = Function = atom()
|
||||
Host = NS = string()
|
||||
Type = no_queue | one_queue | parallel
|
||||
</PRE>Registers function <CODE>Module:Function</CODE> as handler for IQ packets on
|
||||
virtual host <CODE>Host</CODE> that contain child of namespace <CODE>NS</CODE> in
|
||||
<CODE>Component</CODE>. Queueing discipline is <CODE>Type</CODE>. There are at least
|
||||
two components defined:
|
||||
<DL CLASS="description"><DT CLASS="dt-description">
|
||||
</DT><DD CLASS="dd-description"><CODE>ejabberd_local</CODE> Handles packets that addressed to server JID;
|
||||
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>ejabberd_sm</CODE> Handles packets that addressed to users bare JIDs.
|
||||
</DD></DL>
|
||||
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>remove_iq_handler(Component, Host, NS)</CODE>
|
||||
<PRE CLASS="verbatim">Component = atom()
|
||||
Host = NS = string()
|
||||
</PRE>Removes IQ handler on virtual host <CODE>Host</CODE> for namespace <CODE>NS</CODE> from
|
||||
<CODE>Component</CODE>.
|
||||
</DD></DL><P>Handler function must have the following type:
|
||||
</P><DL CLASS="description"><DT CLASS="dt-description">
|
||||
</DT><DD CLASS="dd-description"><CODE>Module:Function(From, To, IQ)</CODE>
|
||||
<PRE CLASS="verbatim">From = To = jid()
|
||||
</PRE></DD></DL><PRE CLASS="verbatim">-module(mod_cputime).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
process_local_iq/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-define(NS_CPUTIME, "ejabberd:cputime").
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_CPUTIME,
|
||||
?MODULE, process_local_iq, IQDisc).
|
||||
|
||||
stop(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_CPUTIME).
|
||||
|
||||
process_local_iq(From, To, {iq, ID, Type, XMLNS, SubEl}) ->
|
||||
case Type of
|
||||
set ->
|
||||
{iq, ID, error, XMLNS,
|
||||
[SubEl, ?ERR_NOT_ALLOWED]};
|
||||
get ->
|
||||
CPUTime = element(1, erlang:statistics(runtime))/1000,
|
||||
SCPUTime = lists:flatten(io_lib:format("~.3f", CPUTime)),
|
||||
{iq, ID, result, XMLNS,
|
||||
[{xmlelement, "query",
|
||||
[{"xmlns", ?NS_CPUTIME}],
|
||||
[{xmlelement, "cputime", [], [{xmlcdata, SCPUTime}]}]}]}
|
||||
end.
|
||||
</PRE><!--TOC subsection Services-->
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc15">8.2</A>  Services</H3><!--SEC END --><P>
|
||||
<A NAME="services"></A></P><PRE CLASS="verbatim">-module(mod_echo).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, init/1, stop/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
MyHost = gen_mod:get_opt(host, Opts, "echo." ++ Host),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
spawn(?MODULE, init, [MyHost])).
|
||||
|
||||
init(Host) ->
|
||||
ejabberd_router:register_local_route(Host),
|
||||
loop(Host).
|
||||
|
||||
loop(Host) ->
|
||||
receive
|
||||
{route, From, To, Packet} ->
|
||||
ejabberd_router:route(To, From, Packet),
|
||||
loop(Host);
|
||||
stop ->
|
||||
ejabberd_router:unregister_route(Host),
|
||||
ok;
|
||||
_ ->
|
||||
loop(Host)
|
||||
end.
|
||||
|
||||
stop(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
Proc ! stop,
|
||||
{wait, Proc}.
|
||||
</PRE><!--CUT END -->
|
||||
<!--HTMLFOOT-->
|
||||
<!--ENDHTML-->
|
||||
<!--FOOTER-->
|
||||
<HR SIZE=2><BLOCKQUOTE CLASS="quote"><EM>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
|
||||
</EM><A HREF="http://hevea.inria.fr/index.html"><EM>H</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>V</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>A</EM></A><EM>.</EM></BLOCKQUOTE></BODY>
|
||||
</HTML>
|
||||
@@ -37,24 +37,20 @@
|
||||
\newcommand{\modecho}{\module{mod\_echo}}
|
||||
\newcommand{\modirc}{\module{mod\_irc}}
|
||||
\newcommand{\modlast}{\module{mod\_last}}
|
||||
\newcommand{\modlastodbc}{\module{mod\_last\_odbc}}
|
||||
\newcommand{\modmuc}{\module{mod\_muc}}
|
||||
\newcommand{\modmuclog}{\module{mod\_muc\_log}}
|
||||
\newcommand{\modoffline}{\module{mod\_offline}}
|
||||
\newcommand{\modofflineodbc}{\module{mod\_offline\_odbc}}
|
||||
\newcommand{\modprivacy}{\module{mod\_privacy}}
|
||||
\newcommand{\modprivate}{\module{mod\_private}}
|
||||
\newcommand{\modpubsub}{\module{mod\_pubsub}}
|
||||
\newcommand{\modregister}{\module{mod\_register}}
|
||||
\newcommand{\modroster}{\module{mod\_roster}}
|
||||
\newcommand{\modrosterodbc}{\module{mod\_roster\_odbc}}
|
||||
\newcommand{\modservicelog}{\module{mod\_service\_log}}
|
||||
\newcommand{\modsharedroster}{\module{mod\_shared\_roster}}
|
||||
\newcommand{\modstats}{\module{mod\_stats}}
|
||||
\newcommand{\modtime}{\module{mod\_time}}
|
||||
\newcommand{\modvcard}{\module{mod\_vcard}}
|
||||
\newcommand{\modvcardldap}{\module{mod\_vcard\_ldap}}
|
||||
\newcommand{\modvcardodbc}{\module{mod\_vcard\_odbc}}
|
||||
\newcommand{\modversion}{\module{mod\_version}}
|
||||
|
||||
%% Title page
|
||||
|
||||
@@ -15,4 +15,4 @@ clean:
|
||||
|
||||
docs:
|
||||
erl -noshell -run edoc_run application \
|
||||
"'$(APPNAME)'" '"$(SRCDIR)"' '[{dir,"$(DOCDIR)"},{packages, false},{todo,true},{private,true},{def,{vsn,"$(VSN)"}},{stylesheet,"process-one.css"},{overview,"$(DOCDIR)/overview.edoc"}]' -s init stop
|
||||
"'$(APPNAME)'" '"$(SRCDIR)"' '[{dir,"$(DOCDIR)"},{packages, false},{todo,false},{private,true},{def,{vsn,"$(VSN)"}},{stylesheet,"process-one.css"},{overview,"$(DOCDIR)/overview.edoc"}]' -s init stop
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 976 B |
Binary file not shown.
|
After Width: | Height: | Size: 679 B |
@@ -0,0 +1,19 @@
|
||||
.comment {color: brown}
|
||||
.export {color: darkorchid}
|
||||
.import {color: darkorchid}
|
||||
.string {color: maroon}
|
||||
.builtin {color: dodgerblue}
|
||||
.guard {color: mediumturquoise}
|
||||
.function {color: blue}
|
||||
.variable {color: green}
|
||||
.macro {color: mediumpurple}
|
||||
.record {color: orangered}
|
||||
.call {color: purple}
|
||||
.attribute {color: firebrick}
|
||||
.error{color: red}
|
||||
.l {
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
color: gray;
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : escobar_hiliter.erl
|
||||
%%% Author : Mats Cronqvist <mats.cronqvist@gmail.com>
|
||||
%%% Description :
|
||||
%%%
|
||||
%%% Created : 6 Jun 2005 by Mats Cronqvist <mats.cronqvist@gmail.com>
|
||||
%%%
|
||||
%%%
|
||||
%%% Escobar, Copyright (c) 2005-2009 Mats Cronqvist
|
||||
%%%
|
||||
%%% MIT License:
|
||||
%%%
|
||||
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%%% of this software and associated documentation files (the "Software"), to deal
|
||||
%%% in the Software without restriction, including without limitation the rights
|
||||
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%%% copies of the Software, and to permit persons to whom the Software is
|
||||
%%% furnished to do so, subject to the following conditions:
|
||||
%%%
|
||||
%%% The above copyright notice and this permission notice shall be included in
|
||||
%%% all copies or substantial portions of the Software.
|
||||
%%%
|
||||
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
%%% THE SOFTWARE.
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(escobar_hilite).
|
||||
|
||||
-export([file/1,tree/1,string/1,out/2]).
|
||||
|
||||
-define(LOG(T),escobar:log(process_info(self()),T)).
|
||||
|
||||
-import(erl_syntax,
|
||||
[get_ann/1,add_ann/2,subtrees/1,update_tree/2,type/1,get_pos/1,
|
||||
application/2,application_arguments/1,application_operator/1,
|
||||
arity_qualifier_argument/1,arity_qualifier_body/1,
|
||||
atom_name/1,atom_value/1,
|
||||
attribute/2,attribute_name/1,attribute_arguments/1,
|
||||
clause/3,clause_patterns/1,clause_guard/1,clause_body/1,
|
||||
comment/2,comment_text/1,comment_padding/1,
|
||||
function/2,function_name/1,function_clauses/1,function_arity/1,
|
||||
integer_literal/1,
|
||||
list/1,list_elements/1,
|
||||
macro/2,macro_name/1,macro_arguments/1,
|
||||
module_qualifier_body/1, module_qualifier_argument/1,
|
||||
string_value/1,string_literal/1,
|
||||
variable_literal/1,variable_name/1,
|
||||
record_access/3, record_access_argument/1,
|
||||
record_access_field/1,record_access_type/1,
|
||||
record_expr/3, record_expr_argument/1,
|
||||
record_expr_fields/1, record_expr_type/1,
|
||||
record_index_expr/2, record_index_expr_field/1,
|
||||
record_index_expr_type/1,
|
||||
form_list/1,
|
||||
get_precomments/1,get_postcomments/1,has_comments/1,
|
||||
copy_comments/2,remove_comments/1]).
|
||||
|
||||
-import(prettypr,
|
||||
[above/2,follow/2,beside/2,empty/0,
|
||||
null_text/1,break/1,floating/3,text/1,floating/1]).
|
||||
|
||||
-import(lists,[flatten/1,duplicate/2,keysearch/3,member/2,usort/1,reverse/1]).
|
||||
|
||||
-import(filename,[join/1,basename/1]).
|
||||
|
||||
out(File,HtmlString) ->
|
||||
{ok,FD}=file:open(File,[write]),
|
||||
try
|
||||
io:fwrite(FD,"<html>",[]),
|
||||
io:fwrite(FD,"<link rel=\"stylesheet\" type=\"text/css\"",[]),
|
||||
io:fwrite(FD,"href=\"escobar.css\"></link><body><pre>",[]),
|
||||
io:fwrite(FD,"~s",[HtmlString]),
|
||||
io:fwrite(FD,"</pre></body></html>",[])
|
||||
after
|
||||
file:close(FD)
|
||||
end.
|
||||
|
||||
string(Str) ->
|
||||
tree(form_list(scan_and_parse(Str,1,[]))).
|
||||
|
||||
scan_and_parse([],_Line,Forms) -> reverse(Forms);
|
||||
scan_and_parse(Text,Line,Forms) ->
|
||||
{done,{ok,Toks,NLine},Cont} = erl_scan:tokens([],Text,Line),
|
||||
{ok,Form} = erl_parse:parse_form(Toks),
|
||||
scan_and_parse(Cont,NLine,[Form|Forms]).
|
||||
|
||||
file(FileName) ->
|
||||
case filelib:is_regular(FileName) of
|
||||
true ->
|
||||
case filename:extension(FileName) of
|
||||
".beam"-> tree(get_tree_beam(FileName));
|
||||
".erl" -> tree(get_comm_tree_erl(FileName));
|
||||
".hrl" -> tree(get_comm_tree_erl(FileName));
|
||||
X -> erlang:error({unknown_extension,X})
|
||||
end;
|
||||
false->
|
||||
erlang:error({no_file,FileName})
|
||||
end.
|
||||
|
||||
get_comm_tree_erl(Filename) ->
|
||||
Comms = erl_comment_scan:file(Filename),
|
||||
Forms = get_forms_erl(Filename),
|
||||
erl_recomment:recomment_forms(Forms,Comms).
|
||||
|
||||
get_tree_beam(Filename) ->
|
||||
case beam_lib:chunks(Filename,["Abst"]) of
|
||||
{ok,{_,[{"Abst",AChunk}]}} ->
|
||||
{_,Forms} = binary_to_term(AChunk),
|
||||
form_list(Forms);
|
||||
_ ->
|
||||
erlang:error({no_debuginfo,Filename})
|
||||
end.
|
||||
|
||||
get_forms_erl(Filename) ->
|
||||
{ok,Fs} = epp_dodger:parse_file(Filename,[{no_fail, true}]),
|
||||
Fs.
|
||||
|
||||
%%% # tree
|
||||
%%% turn a syntax tree into html by annotating and pretty-printing
|
||||
%%% with a hook function
|
||||
|
||||
tree(Tree) ->
|
||||
pout(ann(type(Tree),Tree)).
|
||||
|
||||
%%lists:foldl(fun(Form,Acc) -> [pout(ann(Form))|Acc] end, [], Tree).
|
||||
|
||||
pout(Form) ->
|
||||
erl_prettypr:format(Form,[{hook,fun tag/3},{paper,90},{ribbon,650}]).
|
||||
|
||||
%%% ## formatting
|
||||
%%% ### 'tag' - the format hook function
|
||||
tag(Node,Ctxt,Cont) ->
|
||||
Tags = get_ann(Node),
|
||||
case member(has_comment,Tags) of
|
||||
true ->
|
||||
PreC = get_precomments(Node),
|
||||
PostC = get_postcomments(Node),
|
||||
Nod = remove_comments(Node),
|
||||
Doc0 = tagit(Tags--[has_comment],Cont(Nod,Ctxt)),
|
||||
postcomment(precomment(Doc0,PreC),PostC);
|
||||
false ->
|
||||
Doc0 = Cont(Node,Ctxt),
|
||||
tagit(Tags,Doc0)
|
||||
end.
|
||||
|
||||
tagit([],Doc0) ->
|
||||
Doc0;
|
||||
tagit(["binary"],{beside,_,{beside,Doc,_}}) ->
|
||||
beside(floating(text("<<")),beside(Doc,floating(text(">>"))));
|
||||
tagit([Tag],Doc0) ->
|
||||
beside(null_text(Tag),beside(Doc0,null_text(etag(Tag)))).
|
||||
|
||||
etag("<"++Tag) -> "</"++hd(string:tokens(Tag," "))++">".
|
||||
|
||||
%%%### comment stuff
|
||||
precomment(Doc,PreC) ->
|
||||
above(floating(break(stack(PreC)), -1, -1), Doc).
|
||||
postcomment(Doc,PostC) ->
|
||||
beside(Doc, floating(break(stack(PostC)), 1, 0)).
|
||||
|
||||
stack([]) -> empty();
|
||||
stack([Comm|Comms]) ->
|
||||
Doc = maybe_pad(stack_comment_lines(comment_text(Comm)),Comm),
|
||||
case Comms of
|
||||
[] -> Doc;
|
||||
_ -> above(Doc, stack(Comms))
|
||||
end.
|
||||
|
||||
maybe_pad(Doc,Comm) ->
|
||||
case comment_padding(Comm) of
|
||||
I when is_integer(I), 0 < I -> beside(text(duplicate(I,$ )), Doc);
|
||||
_ -> Doc
|
||||
end.
|
||||
|
||||
%%% stolen with pride from erl_prettypr
|
||||
%%% Stack lines of text above each other and prefix each string in
|
||||
%%% the list with a single `%' character.
|
||||
|
||||
stack_comment_lines([S | Ss]) ->
|
||||
D = tagit([dehtml('span', [{class,comment}])],text("%"++debracket(S))),
|
||||
case Ss of
|
||||
[] -> D;
|
||||
_ -> above(D, stack_comment_lines(Ss))
|
||||
end;
|
||||
stack_comment_lines([]) ->
|
||||
empty().
|
||||
|
||||
%%% annotate nodes that should be hilited
|
||||
%%% the annotation is put on the subtree that should be marked up
|
||||
%%% the annotation is;
|
||||
%%% has_comments|Markup
|
||||
%%% if a node already has an annotation the new one is dropped, except
|
||||
%%% if either the new or the old one is has_comments
|
||||
|
||||
ann(binary,Tree) ->
|
||||
new_tree(Tree,add_anno("binary",Tree));
|
||||
ann(application,Tree) ->
|
||||
Op = application_operator(Tree),
|
||||
Args = application_arguments(Tree),
|
||||
new_tree(Tree,application(add_anno(mu(application,Tree),Op),Args));
|
||||
ann(attribute,Tree) ->
|
||||
Name = attribute_name(Tree),
|
||||
case atom_value(Name) of
|
||||
export ->
|
||||
AQs = list_elements(hd(attribute_arguments(Tree))),
|
||||
Args = [list([add_anno(mu(export,Tree),AQ) || AQ <- AQs])];
|
||||
import ->
|
||||
[ImportMod,ImportFAs] = attribute_arguments(Tree),
|
||||
AQs = list_elements(ImportFAs),
|
||||
Args = [ImportMod,list([add_anno(mu(import,Tree),AQ) || AQ <- AQs])];
|
||||
define ->
|
||||
[Macro|Rest] = attribute_arguments(Tree),
|
||||
case type(Macro) of
|
||||
application ->
|
||||
Op = application_operator(Macro),
|
||||
As = application_arguments(Macro),
|
||||
Args = [application(add_anno(mu(macro,Tree),Op),As)|Rest];
|
||||
_ ->
|
||||
Args = [add_anno(mu(macro,Tree),Macro)|Rest]
|
||||
end;
|
||||
record ->
|
||||
[Rec|Rest] = attribute_arguments(Tree),
|
||||
Args = [add_anno(mu(record,Tree),Rec)|Rest];
|
||||
_ ->
|
||||
Args = attribute_arguments(Tree)
|
||||
end,
|
||||
new_tree(Tree,attribute(add_anno(mu(attribute,Tree),Name),Args));
|
||||
ann(record_access,Tree) ->
|
||||
Arg = record_access_argument(Tree),
|
||||
Type = record_access_type(Tree),
|
||||
Field = record_access_field(Tree),
|
||||
new_tree(Tree,record_access(Arg,add_anno(mu(record,Tree),Type),Field));
|
||||
ann(record_expr,Tree) ->
|
||||
Arg = record_expr_argument(Tree),
|
||||
Type = record_expr_type(Tree),
|
||||
Fields = record_expr_fields(Tree),
|
||||
new_tree(Tree,record_expr(Arg,add_anno(mu(record,Tree),Type),Fields));
|
||||
ann(record_index_expr,Tree) ->
|
||||
Type = record_index_expr_type(Tree),
|
||||
Field = record_index_expr_field(Tree),
|
||||
new_tree(Tree,record_index_expr(add_anno(mu(record,Tree),Type),Field));
|
||||
ann(function,Tree) ->
|
||||
Name = function_name(Tree),
|
||||
Clauses = function_clauses(Tree),
|
||||
new_tree(Tree,function(add_anno(mu(function,Tree),Name),Clauses));
|
||||
ann(macro,Tree) ->
|
||||
Name = macro_name(Tree),
|
||||
Args = macro_arguments(Tree),
|
||||
new_tree(Tree,macro(add_anno(mu(macro,Tree),Name), Args));
|
||||
ann(string,OTree) ->
|
||||
Tree = erl_syntax:string(debracket(string_value(OTree))),
|
||||
new_tree(OTree,add_anno(mu(string,OTree),Tree));
|
||||
ann(variable,Tree) ->
|
||||
new_tree(Tree,add_anno(mu(variable,Tree),Tree));
|
||||
ann(text,Tree) ->
|
||||
new_tree(Tree,add_anno(mu(error,Tree),Tree));
|
||||
ann(comment,OTree) ->
|
||||
Pad = comment_padding(OTree),
|
||||
Text = [debracket(S) || S <- comment_text(OTree)],
|
||||
Tree = comment(Pad,Text),
|
||||
new_tree(OTree,add_anno(mu(comment,OTree),Tree));
|
||||
ann(_Typ,Tree) ->
|
||||
new_tree(Tree,Tree).
|
||||
|
||||
new_tree(OTree,NTree) ->
|
||||
Tree =
|
||||
case has_comments(OTree) of
|
||||
true -> add_ann(has_comment,copy_comments(OTree,NTree));
|
||||
false -> NTree
|
||||
end,
|
||||
SubTrees = subtrees(Tree),
|
||||
case [[ann(type(SubT),SubT) || SubT<-Group] || Group<-SubTrees] of
|
||||
[] -> Tree;
|
||||
NSubtrees -> update_tree(Tree,NSubtrees)
|
||||
end.
|
||||
|
||||
debracket([]) -> [];
|
||||
debracket([$>|Str]) -> ">"++debracket(Str);
|
||||
debracket([$<|Str]) -> "<"++debracket(Str);
|
||||
debracket([C|Str]) -> [C|debracket(Str)].
|
||||
|
||||
add_anno(nil,Tree) -> Tree;
|
||||
add_anno(Ann,Tree) ->
|
||||
case get_ann(Tree) of
|
||||
[] -> add_ann(Ann,Tree);
|
||||
[has_comment] -> add_ann(Ann,Tree);
|
||||
_OAnn -> Tree
|
||||
end.
|
||||
|
||||
%%%### the markups
|
||||
mu(application,Node) ->
|
||||
Op = application_operator(Node),
|
||||
Ar = length(application_arguments(Node)),
|
||||
case type(Op) of
|
||||
%% variable ->
|
||||
%% dehtml('span', [{class,variable}]);
|
||||
atom ->
|
||||
case is_guard_or_builtin(atom_value(Op),Ar) of
|
||||
guard -> dehtml('span', [{class,guard}]);
|
||||
builtin->dehtml('span', [{class,builtin}]);
|
||||
neither->dehtml('span', [{class,call}])
|
||||
end;
|
||||
module_qualifier ->
|
||||
Mod = module_qualifier_argument(Op),
|
||||
Fun = module_qualifier_body(Op),
|
||||
case {type(Mod),type(Fun)} of
|
||||
{atom,atom} ->
|
||||
case atom_value(Mod) of
|
||||
erlang -> dehtml('span', [{class,builtin}]);
|
||||
_ -> dehtml('span', [{class,call}])
|
||||
end;
|
||||
_ ->
|
||||
nil
|
||||
end;
|
||||
_ ->
|
||||
nil
|
||||
end;
|
||||
mu(function = Class, {tree, function, _, {function, _, [{tree, clause, _, {clause, Vars, _, _}} | _]}}) ->
|
||||
dehtml('span', [{class,Class}, {arity,length(Vars)}]);
|
||||
mu(Class,_Node) ->
|
||||
dehtml('span', [{class,Class}]).
|
||||
|
||||
dehtml(Tag,Atts) ->
|
||||
flatten([$<,str(Tag),$ ,[[str(A),"=\"",str(V),"\" "]||{A,V}<-Atts],$>]).
|
||||
|
||||
str(I) when is_integer(I) -> integer_to_list(I);
|
||||
str(A) when is_atom(A) -> atom_to_list(A);
|
||||
str(L) when is_list(L) -> L.
|
||||
|
||||
is_guard_or_builtin(atom,1) ->guard;
|
||||
is_guard_or_builtin(binary,1) ->guard;
|
||||
is_guard_or_builtin(constant,1) ->guard;
|
||||
is_guard_or_builtin(float,1) ->guard;
|
||||
is_guard_or_builtin(function,1) ->guard;
|
||||
is_guard_or_builtin(function,2) ->guard;
|
||||
is_guard_or_builtin(integer,1) ->guard;
|
||||
is_guard_or_builtin(list,1) ->guard;
|
||||
is_guard_or_builtin(number,1) ->guard;
|
||||
is_guard_or_builtin(pid,1) ->guard;
|
||||
is_guard_or_builtin(port,1) ->guard;
|
||||
is_guard_or_builtin(reference,1) ->guard;
|
||||
is_guard_or_builtin(tuple,1) ->guard;
|
||||
is_guard_or_builtin(record,2) ->guard;
|
||||
is_guard_or_builtin(record,3) ->guard;
|
||||
is_guard_or_builtin(F,A) ->
|
||||
case erlang:function_exported(erlang,F,A) orelse
|
||||
erlang:is_builtin(erlang,F,A) of
|
||||
true -> builtin;
|
||||
false-> neither
|
||||
end.
|
||||
@@ -0,0 +1,54 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : escobar_run.erl
|
||||
%%% Author : Badlop <badlop@process-one.net>
|
||||
%%% Purpose : Frontend to run Escobar
|
||||
%%% Created : 16 Apr 2008 by Badlop <badlop@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(escobar_run).
|
||||
|
||||
%%% Download ejabberd_hilite.erl from http://code.google.com/p/erl-escobar/
|
||||
|
||||
%%% Example calls:
|
||||
%%% escobar_run:file("escobar_run.erl", "../ejascobar/").
|
||||
%%% escobar_run:dir(".", "../ejascobar/").
|
||||
%%% escobar_run:dir(".", "../doc/api/").
|
||||
%%% find ./ -type f -name '*.html' -exec sed -i 's/class="function" >\([a-z0-9]*\)</class="function" id="\1">\1</;' {} \;
|
||||
|
||||
-export([file/2, file/1, dir/1]).
|
||||
|
||||
file([F, OutDir]) ->
|
||||
file(F, OutDir).
|
||||
|
||||
file(F, OutDir) ->
|
||||
String = escobar_hilite:file(F),
|
||||
FB = filename:basename(F),
|
||||
FilenameHTML = filename:join(OutDir, FB ++ ".html"),
|
||||
escobar_hilite:out(FilenameHTML, String).
|
||||
|
||||
dir([SrcDir, OutDir]) ->
|
||||
SrcDirAbs = filename:absname(SrcDir),
|
||||
OutDirAbs = filename:absname(OutDir),
|
||||
Files = get_files([SrcDirAbs]),
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
case filename:extension(F) of
|
||||
".erl" ->
|
||||
file(F, OutDirAbs);
|
||||
".hrl" ->
|
||||
file(F, OutDirAbs);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
Files).
|
||||
|
||||
get_files([]) ->
|
||||
[];
|
||||
get_files([FHead | FTail]) ->
|
||||
case catch file:list_dir(FHead) of
|
||||
{ok, Files} ->
|
||||
FilesHead = [filename:join(FHead, FilesN) || FilesN <- Files],
|
||||
get_files(FilesHead ++ FTail);
|
||||
{error, enotdir} ->
|
||||
[FHead] ++ get_files(FTail)
|
||||
end.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 318 B |
@@ -0,0 +1,522 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : funrelg.erl
|
||||
%%% Author : Badlop <badlop@process-one.net>
|
||||
%%% Purpose : Function Relation Graph
|
||||
%%% Created : 3 Apr 2007 by Badlop <badlop@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(funrelg).
|
||||
|
||||
-author('badlop@process-one.net').
|
||||
|
||||
-export([dir/1, file/1, g/0, g/1, gc/0, gc/1, d/0]).
|
||||
|
||||
-record(mfa, {mod, func, arity}).
|
||||
-record(arcs, {id, from, to, occ}).
|
||||
|
||||
%% The functions that are not in export and don't call anybody are not drawn
|
||||
%% The calls to this module that use Mod:Func are drawn as external calls
|
||||
|
||||
%%-----------------------------
|
||||
%% Module handlers
|
||||
%%-----------------------------
|
||||
|
||||
dir([SrcDir, OutDir]) ->
|
||||
SrcDirAbs = filename:absname(SrcDir),
|
||||
OutDirAbs = filename:absname(OutDir),
|
||||
Files = get_files([SrcDirAbs]),
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
file(F, OutDirAbs)
|
||||
end,
|
||||
Files).
|
||||
|
||||
file([SrcDir, OutDir]) ->
|
||||
SrcFilAbs = filename:absname(SrcDir),
|
||||
OutDirAbs = filename:absname(OutDir),
|
||||
file(SrcFilAbs, OutDirAbs).
|
||||
|
||||
file(SrcFile, OutDir) ->
|
||||
case {filename:extension(SrcFile), filename:basename(SrcFile)} of
|
||||
%% The file must by *.erl, and the first character must be a-z
|
||||
{".erl", [FirstChar | _]} when (FirstChar >= 97) and (FirstChar =< 122) ->
|
||||
make_file(SrcFile, OutDir),
|
||||
d();
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
get_files([]) ->
|
||||
[];
|
||||
get_files([FHead | FTail]) ->
|
||||
case catch file:list_dir(FHead) of
|
||||
{ok, Files} ->
|
||||
FilesHead = [filename:join(FHead, FilesN) || FilesN <- Files],
|
||||
get_files(FilesHead ++ FTail);
|
||||
{error, enotdir} ->
|
||||
[FHead] ++ get_files(FTail)
|
||||
end.
|
||||
|
||||
make_file(File, OutDir) ->
|
||||
FB = filename:basename(File),
|
||||
FileDot = filename:join(OutDir, FB ++ ".dot"),
|
||||
Text = gc(File),
|
||||
{ok,FO}=file:open(FileDot,[write]),
|
||||
try
|
||||
io:fwrite(FO,"~s",[Text])
|
||||
after
|
||||
file:close(FO)
|
||||
end,
|
||||
FileSvg = filename:join(OutDir, filename:basename(File, ".erl") ++ ".svg"),
|
||||
case os:cmd("dot -Tsvg " ++ FileDot ++ " -o " ++ FileSvg) of
|
||||
"" -> ok;
|
||||
ShellResult ->
|
||||
io:format("Trying to run 'dot', we got this result:~n ~s~n"
|
||||
"Remember that you need to have Graphviz 'dot' installed.~n", [ShellResult])
|
||||
end,
|
||||
ok.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
|
||||
g() ->
|
||||
g("a.erl").
|
||||
g(Filename) ->
|
||||
gc(Filename),
|
||||
halt().
|
||||
|
||||
gc() ->
|
||||
gc("a.erl").
|
||||
gc(Filename) ->
|
||||
{ok, File} = epp_dodger:parse_file(Filename),
|
||||
|
||||
ets:new(arcs, [set, public, named_table, {keypos, 2}]),
|
||||
ets:new(mfa_conversion, [set, public, named_table, {keypos, 1}]),
|
||||
ets:new(counters, [set, public, named_table]),
|
||||
ets:insert(counters, {arcs_id, 0}),
|
||||
|
||||
ModuleName = get_module_name(File),
|
||||
Exports = lists:usort(get_exports(File)),
|
||||
Functions = get_functions(File),
|
||||
FunctionsParsed = lists:usort(parse_functions(Functions)), % side effects: stores on ets
|
||||
Privates = FunctionsParsed -- Exports,
|
||||
%%Externals = get_externals(),
|
||||
|
||||
MFAs = get_func_calls(),
|
||||
|
||||
AppModules1 = os:cmd("ls -1 ../doc/devdoc/*.html | tr \".\" \" \" | tr \"/\" \" \" | awk '{print $3}'"),
|
||||
AppModules = [list_to_atom(Str) || Str <- string:tokens(AppModules1, [10])],
|
||||
|
||||
ExternalTypes1 = [mfa_to_externaltype(MFA, ModuleName, AppModules, FunctionsParsed) || MFA <- MFAs],
|
||||
ExternalTypes = lists:usort(ExternalTypes1),
|
||||
AppExternals = [Function || {Type, Function} <- ExternalTypes, Type == app],
|
||||
ExmppExternals = [Function || {Type, Function} <- ExternalTypes, Type == exmpp],
|
||||
OTPExternals = [Function || {Type, Function} <- ExternalTypes, Type == other],
|
||||
|
||||
textize(ModuleName, Exports, Privates, AppExternals, ExmppExternals, OTPExternals).
|
||||
|
||||
get_arcs_id() ->
|
||||
ets:update_counter(counters, arcs_id, 1).
|
||||
|
||||
insert_arc_normal(FromFunc, FromArity, ToFunc, ToArity) ->
|
||||
From = #mfa{mod = -1, func = FromFunc, arity = FromArity},
|
||||
To = #mfa{mod = -1, func = ToFunc, arity = ToArity},
|
||||
insert_arc(From, To).
|
||||
|
||||
insert_arc_external(FromFunc, FromArity, ToMod, ToFunc, ToArity) ->
|
||||
From = #mfa{mod = -1, func = FromFunc, arity = FromArity},
|
||||
To = #mfa{mod = ToMod, func = ToFunc, arity = ToArity},
|
||||
insert_arc(From, To).
|
||||
|
||||
insert_arc(From, To) ->
|
||||
Match = #arcs{
|
||||
from = From,
|
||||
to = To,
|
||||
id='$1',
|
||||
_='_'},
|
||||
Select = [{Match, [], ['$1']}],
|
||||
Ids = ets:select(arcs, Select),
|
||||
case Ids of
|
||||
[] ->
|
||||
ets:insert(arcs,
|
||||
#arcs{
|
||||
id = get_arcs_id(),
|
||||
from = From,
|
||||
to = To,
|
||||
occ = 1
|
||||
}
|
||||
);
|
||||
[Id] ->
|
||||
[Arc] = ets:lookup(arcs, Id),
|
||||
Arc2 = Arc#arcs{occ = Arc#arcs.occ+1},
|
||||
ets:insert(arcs, Arc2)
|
||||
end.
|
||||
|
||||
read_arc(Id) ->
|
||||
ets:lookup(arcs, Id).
|
||||
|
||||
d() ->
|
||||
ets:delete(arcs),
|
||||
ets:delete(mfa_conversion),
|
||||
ets:delete(counters).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% Get
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
get_module_name(File) ->
|
||||
Attributes = get_elements(attribute, File),
|
||||
get_attribute(module, Attributes).
|
||||
|
||||
get_exports(File) ->
|
||||
Attributes = get_elements(attribute, File),
|
||||
Exports = get_attribute(export, Attributes),
|
||||
parse_export(Exports).
|
||||
|
||||
get_functions(File) ->
|
||||
get_elements(function, File).
|
||||
|
||||
get_elements(Type, L) -> get_elements(Type, L, []).
|
||||
get_elements(_, [], Res) -> Res;
|
||||
get_elements(Type, [{tree, Type, _, A} | L], Res) ->
|
||||
get_elements(Type, L, Res++[A]);
|
||||
get_elements(Type, [_ | L], Res) ->
|
||||
get_elements(Type, L, Res).
|
||||
|
||||
get_attribute(_, []) -> [];
|
||||
get_attribute(Name, [{attribute, {tree, atom, _, Name}, [Al]} | As]) ->
|
||||
case Name of
|
||||
module ->
|
||||
{tree, atom, _, Mn} = Al,
|
||||
Mn;
|
||||
export ->
|
||||
{tree, list, _, {list, Exports, _}} = Al,
|
||||
Exports++get_attribute(Name, As);
|
||||
_ -> ok
|
||||
end;
|
||||
get_attribute(Name, [_ | As]) ->
|
||||
get_attribute(Name, As).
|
||||
|
||||
parse_export(Ex) ->
|
||||
lists:foldl(
|
||||
fun(Aq, Res) ->
|
||||
{tree, arity_qualifier, _, {arity_qualifier, Atom, Inte}} = Aq,
|
||||
{tree, atom, _, An} = Atom,
|
||||
{tree, integer, _, In} = Inte,
|
||||
Res++[{An, In}]
|
||||
end,
|
||||
[],
|
||||
Ex).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% Parse
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
parse_functions(Functions) ->
|
||||
lists:map(
|
||||
fun(E) ->
|
||||
{function, {_, _, _, Fn}, Clauses} = E,
|
||||
Arity = get_function_arity(Clauses),
|
||||
parse_clauses(Fn, Clauses),
|
||||
{Fn, Arity}
|
||||
end,
|
||||
Functions).
|
||||
|
||||
parse_clauses(Fn, Clauses) ->
|
||||
lists:foreach(
|
||||
fun(E) ->
|
||||
{tree, clause, L, C} = E,
|
||||
{attr, _Ln, _, _} = L,
|
||||
{clause, Parameters, _, Contents} = C,
|
||||
Fa = length(Parameters),
|
||||
parse_contents({Fn, Fa}, Contents)
|
||||
end,
|
||||
Clauses).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% OLD
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
parse_contents(F, Contents) ->
|
||||
{_, Res} = lists:foldl(
|
||||
fun(C, {Fu, R}) ->
|
||||
{Fu, R++parse_content(F, C)}
|
||||
end,
|
||||
{F, []},
|
||||
Contents),
|
||||
Res.
|
||||
|
||||
parse_content(F, {tree, application, L, A}) ->
|
||||
{attr, _Ln, _, _} = L,
|
||||
parse_application(F, A);
|
||||
parse_content(F, {tree, list, _, {list, [L], Ls}}) ->
|
||||
RL = parse_content(F, L),
|
||||
RL++parse_content(F, Ls);
|
||||
parse_content(F, {tree, tuple, _, T}) ->
|
||||
parse_contents(F, T);
|
||||
parse_content(F, {tree, match_expr, _, M}) ->
|
||||
{match_expr, _, Value} = M,
|
||||
parse_content(F, Value);
|
||||
parse_content(F, {tree, try_expr, _, T}) ->
|
||||
{try_expr, As, Bs, Cs, Ds} = T,
|
||||
RAs = parse_contents(F, As),
|
||||
RBs = parse_contents(F, Bs),
|
||||
RDs = parse_contents(F, Ds),
|
||||
RAs++RBs++RDs++parse_contents(F, Cs);
|
||||
parse_content(F, {tree, block_expr, _, Ts}) ->
|
||||
parse_contents(F, Ts);
|
||||
parse_content(F, {tree, catch_expr, _, C}) ->
|
||||
parse_content(F, C);
|
||||
parse_content(F, {tree, case_expr, _, C}) ->
|
||||
{case_expr, Case, Cases} = C,
|
||||
R2 = parse_content(F, Case),
|
||||
R2++parse_contents(F, Cases);
|
||||
parse_content(F, {tree, if_expr, _, Cases}) ->
|
||||
parse_contents(F, Cases);
|
||||
parse_content(F, {tree, disjunction, _, Cases}) ->
|
||||
parse_contents(F, Cases);
|
||||
parse_content(F, {tree, conjunction, _, Cases}) ->
|
||||
parse_contents(F, Cases);
|
||||
parse_content(F, {tree, list_comp, _, L}) ->
|
||||
{list_comp, A, Bs} = L,
|
||||
RA = parse_content(F, A),
|
||||
RA++parse_contents(F, Bs);
|
||||
parse_content(F, {tree, generator, _, G}) ->
|
||||
{generator, _, Gen} = G,
|
||||
parse_content(F, Gen);
|
||||
parse_content(F, {tree, clause, _, C}) ->
|
||||
{clause, Clause, When, Second} = C,
|
||||
R2 = parse_contents(F, Clause),
|
||||
R3 = parse_content(F, When),
|
||||
R2++R3++parse_contents(F, Second);
|
||||
parse_content(F, {tree, fun_expr, _, E}) ->
|
||||
parse_contents(F, E);
|
||||
parse_content(F, {tree, record_expr, _, E}) ->
|
||||
{record_expr, _, _, Records} = E,
|
||||
parse_contents(F, Records);
|
||||
parse_content(F, {tree, record_field, _, R}) ->
|
||||
{record_field, F1, F2} = R,
|
||||
R2 = parse_content(F, F1),
|
||||
R2++parse_content(F, F2);
|
||||
parse_content(F, {tree, receive_expr, _, E}) ->
|
||||
{receive_expr, E1, _, _} = E,
|
||||
parse_contents(F, E1);
|
||||
parse_content(F, {tree, infix_expr, _, I}) ->
|
||||
{infix_expr, _Operator, T1, T2} = I,
|
||||
R = parse_content(F, T1),
|
||||
R++parse_content(F, T2);
|
||||
parse_content(_F, {tree, prefix_expr, _, _P}) ->
|
||||
[];
|
||||
parse_content(_F, {tree, class_qualifier, _, _}) -> [];
|
||||
parse_content(_F, {tree, implicit_fun, _, _}) -> [];
|
||||
parse_content(_F, {tree, record_access, _, _}) -> [];
|
||||
parse_content(_F, {tree, record_index_expr, _, _}) -> [];
|
||||
parse_content(_F, {tree, macro, _, _}) -> [];
|
||||
parse_content(_F, {tree, binary, _, _}) -> [];
|
||||
parse_content(_F, {tree, binary_generator, _, _}) -> [];
|
||||
parse_content(_F, {tree, binary_comp, _, _}) -> [];
|
||||
parse_content(_F, {integer, _, _I}) -> [];
|
||||
parse_content(_F, {float, _, _I}) -> [];
|
||||
parse_content(_F, {string, _, _S}) -> [];
|
||||
parse_content(_F, {char, _, _S}) -> [];
|
||||
parse_content(_F, {atom, _, _A}) -> [];
|
||||
parse_content(_F, {var, _, _V}) -> [];
|
||||
parse_content(_F, {nil, _}) -> [];
|
||||
parse_content(_F, none) -> [];
|
||||
parse_content(_F, C) -> io:format("Unknown content: ~p~n", [C]), [].
|
||||
|
||||
parse_application({Fn, Fa}, {application, {atom, _, Name}, Valores}) ->
|
||||
Arity = length(Valores),
|
||||
insert_arc_normal(Fn, Fa, Name, Arity),
|
||||
Ra = [{arc_normal, Fn, Name}],
|
||||
Ra++parse_contents({Fn, Fa}, Valores);
|
||||
parse_application({Fn, Fa}, {application, {tree, module_qualifier, _, M}, Tree2}) ->
|
||||
case M of
|
||||
{module_qualifier, {_, _, Tf1}, {_, _, Tf2}} ->
|
||||
ToArity = length(Tree2),
|
||||
insert_arc_external(Fn, Fa, Tf1, Tf2, ToArity),
|
||||
parse_contents({Fn, Fa}, Tree2);
|
||||
_Other ->
|
||||
%%io:format("Unknown application module_qualifier: ~p~n", [M]),
|
||||
parse_contents({Fn, Fa}, Tree2)
|
||||
end;
|
||||
parse_application({Fn, Fa}, {application, {tree, record_access, _, _}, Tree2}) ->
|
||||
parse_contents({Fn, Fa}, Tree2);
|
||||
parse_application({Fn, Fa}, {application, {var, _, _}, Tree2}) ->
|
||||
parse_contents({Fn, Fa}, Tree2);
|
||||
parse_application({Fn, Fa}, {application, Other, Tree2}) ->
|
||||
io:format("Unknown application tree: ~p~n", [Other]),
|
||||
parse_contents({Fn, Fa}, Tree2).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% Textize
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
textize(ModuleName, Exports, Privates, AppExternals, ExmppExternals, OTPExternals) ->
|
||||
ExportsS = textize_exports(Exports),
|
||||
PrivatesS = textize_exports(Privates),
|
||||
AppExternalsS = textize_externals(AppExternals),
|
||||
ExmppExternalsS = textize_externals(ExmppExternals),
|
||||
OTPExternalsS = textize_externals(OTPExternals),
|
||||
Arcs = textize_arcs(),
|
||||
io_lib:format(
|
||||
"digraph ~p {~n"++
|
||||
" label = \"Module ~p\";~n"++
|
||||
" labelloc = \"t\";~n"++
|
||||
" fontname = Helvetica;~n"++
|
||||
" rankdir = LR;~n"++
|
||||
" ratio = fill;~n"++
|
||||
%%" node [shape = box, style = filled, color = olivedrab1, fontsize=40];~s~n"++
|
||||
%%" node [shape = parallelogram, style = filled, color = rosybrown1, fontsize=40];~s~n"++
|
||||
%%" node [shape = ellipse, style = filled, color = slategray1, peripheries=3, fontsize=40];~n"++
|
||||
" node [shape = box, color=darkgreen, fillcolor=Honeydew, style=filled, href=\"PRI~p.html#\\N\"];~s~n"++
|
||||
" node [shape = box, color=darkgreen, fillcolor=PaleGreen, style=filled, href=\"EXP~p.html#\\N\"];~s~n"++
|
||||
" node [shape = box, color=darkgreen, fillcolor=Aquamarine, style=filled, href=\"APP\\N\"];~s~n"++
|
||||
" node [shape = box, color=darkblue, fillcolor=LightSkyBlue,style=filled, href=\"EXM\\N\"];~s~n"++
|
||||
" node [shape = box, color=darkred, fillcolor=PeachPuff, style=filled, href=\"OTP\\N\"];~s~n"++
|
||||
" node [shape = box];~n"++
|
||||
"~s"++
|
||||
"}~n",
|
||||
[ModuleName, ModuleName,
|
||||
ModuleName, PrivatesS,
|
||||
ModuleName, ExportsS,
|
||||
AppExternalsS,
|
||||
ExmppExternalsS,
|
||||
OTPExternalsS,
|
||||
Arcs]).
|
||||
|
||||
textize_exports(Exports) ->
|
||||
lists:foldl(
|
||||
fun({Func, Arity}, R) ->
|
||||
string:concat(R, io_lib:format(" \"~p/~p\";", [Func, Arity]))
|
||||
end,
|
||||
"",
|
||||
Exports).
|
||||
|
||||
textize_externals(Externals) ->
|
||||
lists:foldl(
|
||||
fun({Mod, Func, Arity}, R) ->
|
||||
string:concat(R, io_lib:format(" \"~p:~p/~p\";", [Mod, Func, Arity]));
|
||||
({Func, Arity}, R) ->
|
||||
string:concat(R, io_lib:format(" \"~p/~p\";", [Func, Arity]))
|
||||
end,
|
||||
"",
|
||||
Externals).
|
||||
|
||||
textize_arcs() ->
|
||||
Id = ets:first(arcs),
|
||||
textize_arcs(Id, []).
|
||||
|
||||
textize_arcs('$end_of_table', Res) -> Res;
|
||||
textize_arcs(Id, Res) ->
|
||||
[Arc] = read_arc(Id),
|
||||
From = mfa_prepare(Arc#arcs.from),
|
||||
To = mfa_prepare(Arc#arcs.to),
|
||||
Edge_attrs = check_loop(From, To)++check_occu(Arc#arcs.occ)++check_external(Arc#arcs.to),
|
||||
Res2 = io_lib:format(" ~s -> ~s~s;~n", [From, To, Edge_attrs]),
|
||||
Id_next = ets:next(arcs, Id),
|
||||
textize_arcs(Id_next, string:concat(Res2, Res)).
|
||||
|
||||
get_func_calls() ->
|
||||
Id = ets:first(arcs),
|
||||
get_func_calls(Id, []).
|
||||
|
||||
get_func_calls('$end_of_table', Res) -> Res;
|
||||
get_func_calls(Id, Res) ->
|
||||
[Arc] = read_arc(Id),
|
||||
MFA = Arc#arcs.to,
|
||||
Call = MFA,
|
||||
Id_next = ets:next(arcs, Id),
|
||||
get_func_calls(Id_next, [Call | Res]).
|
||||
|
||||
mfa_prepare(MFAinitial) ->
|
||||
MFA = try_mfa_conversion(MFAinitial),
|
||||
Mod = case MFA#mfa.mod of
|
||||
-1 -> "\"";
|
||||
M -> io_lib:format("\"~p:", [M])
|
||||
end,
|
||||
Func = io_lib:format("~p", [MFA#mfa.func]),
|
||||
Arity = case MFA#mfa.arity of
|
||||
-1 -> "\"";
|
||||
A -> io_lib:format("/~p\"", [A])
|
||||
end,
|
||||
string:concat(Mod, string:concat(Func, Arity)).
|
||||
|
||||
%% noexternal | app | exmpp | other
|
||||
mfa_to_externaltype(MFA, ModuleName, AppModules, Functions) ->
|
||||
case MFA of
|
||||
{mfa, -1, Fu, Ar} ->
|
||||
case {is_internal, lists:member({Fu, Ar}, Functions)} of
|
||||
{is_internal, true} ->
|
||||
{noexternal, {Fu, Ar}};
|
||||
{is_internal, false} ->
|
||||
store_mfa_conversion(MFA, {mfa, -1, Fu, Ar}),
|
||||
{other, {Fu, Ar}}
|
||||
end;
|
||||
{mfa, Mo, Fu, Ar} when Mo == ModuleName ->
|
||||
store_mfa_conversion(MFA, {mfa, -1, Fu, Ar}),
|
||||
{noexternal, {Fu, Ar}};
|
||||
{mfa, Mo, Fu, Ar} ->
|
||||
Type = case {is_app, lists:member(Mo, AppModules)} of
|
||||
{is_app, true} -> app;
|
||||
{is_app, false} ->
|
||||
case string:str(atom_to_list(Mo), "exmpp") of
|
||||
0 -> other;
|
||||
N when is_integer(N) -> exmpp
|
||||
end
|
||||
end,
|
||||
{Type, {Mo, Fu, Ar}}
|
||||
end.
|
||||
|
||||
get_function_arity([{tree, clause, _, {clause, Vars, _, _}} | _]) ->
|
||||
length(Vars);
|
||||
get_function_arity(E) ->
|
||||
io:format("Unknown function arity: ~p~n", [E]).
|
||||
|
||||
store_mfa_conversion(MFA1, MFA2) ->
|
||||
ets:insert(mfa_conversion, {MFA1, MFA2}).
|
||||
|
||||
try_mfa_conversion(MFA) ->
|
||||
case ets:lookup(mfa_conversion, MFA) of
|
||||
[{MFA,MFA2}] ->
|
||||
MFA2;
|
||||
[] ->
|
||||
MFA
|
||||
end.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% Utils
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
%%check_loop(I, I) -> " [color = sandybrown]";
|
||||
check_loop(I, I) -> "";
|
||||
check_loop(_, _) -> "".
|
||||
|
||||
check_occu(1) -> "";
|
||||
check_occu(Occ) -> io_lib:format(" [label=\"~p\"]", [Occ]).
|
||||
|
||||
check_external(To) ->
|
||||
case To#mfa.mod of
|
||||
-1 -> " [style = bold]";
|
||||
_ -> ""
|
||||
end.
|
||||
@@ -1,3 +1,25 @@
|
||||
div.navbar table tr td a[target="_parent"] {
|
||||
background: transparent url("ejabberd-devdoc.png") no-repeat;
|
||||
display: block;
|
||||
font-size: 0%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 192px;
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
div.navbar table tr td a img {
|
||||
border: 0;
|
||||
padding: 0 0 0 10;
|
||||
float: right;
|
||||
}
|
||||
|
||||
p i {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html, body {
|
||||
font-family: Verdana, sans-serif;
|
||||
color: #000;
|
||||
@@ -5,19 +27,20 @@ html, body {
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
color: #4a5389;
|
||||
border-bottom: solid 1px #000;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
font-size: 18px;
|
||||
text-align: right;
|
||||
color: #4a5389;
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
color: #900;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 717 B |
@@ -1,132 +0,0 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Ejabberd 2.1.11 Feature Sheet
|
||||
</TITLE>
|
||||
|
||||
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
|
||||
<META name="GENERATOR" content="hevea 1.10">
|
||||
<STYLE type="text/css">
|
||||
.li-itemize{margin:1ex 0ex;}
|
||||
.li-enumerate{margin:1ex 0ex;}
|
||||
.dd-description{margin:0ex 0ex 1ex 4ex;}
|
||||
.dt-description{margin:0ex;}
|
||||
.toc{list-style:none;}
|
||||
.thefootnotes{text-align:left;margin:0ex;}
|
||||
.dt-thefootnotes{margin:0em;}
|
||||
.dd-thefootnotes{margin:0em 0em 0em 2em;}
|
||||
.footnoterule{margin:1em auto 1em 0px;width:50%;}
|
||||
.caption{padding-left:2ex; padding-right:2ex; margin-left:auto; margin-right:auto}
|
||||
.title{margin:2ex auto;text-align:center}
|
||||
.center{text-align:center;margin-left:auto;margin-right:auto;}
|
||||
.flushleft{text-align:left;margin-left:0ex;margin-right:auto;}
|
||||
.flushright{text-align:right;margin-left:auto;margin-right:0ex;}
|
||||
DIV TABLE{margin-left:inherit;margin-right:inherit;}
|
||||
PRE{text-align:left;margin-left:0ex;margin-right:auto;}
|
||||
BLOCKQUOTE{margin-left:4ex;margin-right:4ex;text-align:left;}
|
||||
TD P{margin:0px;}
|
||||
.boxed{border:1px solid black}
|
||||
.textboxed{border:1px solid black}
|
||||
.vbar{border:none;width:2px;background-color:black;}
|
||||
.hbar{border:none;height:2px;width:100%;background-color:black;}
|
||||
.hfill{border:none;height:1px;width:200%;background-color:black;}
|
||||
.vdisplay{border-collapse:separate;border-spacing:2px;width:auto; empty-cells:show; border:2px solid red;}
|
||||
.vdcell{white-space:nowrap;padding:0px;width:auto; border:2px solid green;}
|
||||
.display{border-collapse:separate;border-spacing:2px;width:auto; border:none;}
|
||||
.dcell{white-space:nowrap;padding:0px;width:auto; border:none;}
|
||||
.dcenter{margin:0ex auto;}
|
||||
.vdcenter{border:solid #FF8000 2px; margin:0ex auto;}
|
||||
.minipage{text-align:left; margin-left:0em; margin-right:auto;}
|
||||
.marginpar{border:solid thin black; width:20%; text-align:left;}
|
||||
.marginparleft{float:left; margin-left:0ex; margin-right:1ex;}
|
||||
.marginparright{float:right; margin-left:1ex; margin-right:0ex;}
|
||||
.theorem{text-align:left;margin:1ex auto 1ex 0ex;}
|
||||
.part{margin:2ex auto;text-align:center}
|
||||
SPAN{width:20%; float:right; text-align:left; margin-left:auto;}
|
||||
</STYLE>
|
||||
</HEAD>
|
||||
<BODY >
|
||||
<!--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.11 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">
|
||||
|
||||
<IMG SRC="logo.png" ALT="logo.png">
|
||||
|
||||
|
||||
</DIV><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>I can thoroughly recommend ejabberd for ease of setup –
|
||||
Kevin Smith, Current maintainer of the Psi project</I></FONT></BLOCKQUOTE><P>Introduction
|
||||
<A NAME="intro"></A></P><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>I just tried out ejabberd and was impressed both by ejabberd itself and the language it is written in, Erlang. —
|
||||
Joeri</I></FONT></BLOCKQUOTE><P><TT>ejabberd</TT> is a <B><FONT SIZE=4><FONT COLOR="#001376">free and open source</FONT></FONT></B> instant messaging server written in <A HREF="http://www.erlang.org/">Erlang/OTP</A>.</P><P><TT>ejabberd</TT> is <B><FONT SIZE=4><FONT COLOR="#001376">cross-platform</FONT></FONT></B>, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</P><P><TT>ejabberd</TT> is designed to be a <B><FONT SIZE=4><FONT COLOR="#001376">rock-solid and feature rich</FONT></FONT></B> XMPP server.</P><P><TT>ejabberd</TT> is suitable for small deployments, whether they need to be <B><FONT SIZE=4><FONT COLOR="#001376">scalable</FONT></FONT></B> or not, as well as extremely big deployments.</P><!--TOC section Key Features-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc1"></A>Key Features</H2><!--SEC END --><P>
|
||||
<A NAME="keyfeatures"></A>
|
||||
</P><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>Erlang seems to be tailor-made for writing stable, robust servers. —
|
||||
Peter Saint-André, Executive Director of the Jabber Software Foundation</I></FONT></BLOCKQUOTE><P><TT>ejabberd</TT> is:
|
||||
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
<B><FONT SIZE=4><FONT COLOR="#001376">Cross-platform:</FONT></FONT></B> <TT>ejabberd</TT> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.</LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Distributed:</FONT></FONT></B> You can run <TT>ejabberd</TT> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.</LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Fault-tolerant:</FONT></FONT></B> You can deploy an <TT>ejabberd</TT> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced ‘on the fly’.</LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Administrator Friendly:</FONT></FONT></B> <TT>ejabberd</TT> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Comprehensive documentation.
|
||||
</LI><LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </LI><LI CLASS="li-itemize">Web Administration.
|
||||
</LI><LI CLASS="li-itemize">Shared Roster Groups.
|
||||
</LI><LI CLASS="li-itemize">Command line administration tool. </LI><LI CLASS="li-itemize">Can integrate with existing authentication mechanisms.
|
||||
</LI><LI CLASS="li-itemize">Capability to send announce messages.
|
||||
</LI></UL></LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Internationalized:</FONT></FONT></B> <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Translated to 25 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
|
||||
</LI></UL></LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Open Standards:</FONT></FONT></B> <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Fully XMPP compliant.
|
||||
</LI><LI CLASS="li-itemize">XML-based protocol.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.ejabberd.im/protocols">Many protocols supported</A>.
|
||||
</LI></UL></LI></UL><!--TOC section Additional Features-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc2"></A>Additional Features</H2><!--SEC END --><P>
|
||||
<A NAME="addfeatures"></A>
|
||||
</P><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>ejabberd is making inroads to solving the "buggy incomplete server" problem —
|
||||
Justin Karneges, Founder of the Psi and the Delta projects</I></FONT></BLOCKQUOTE><P>Moreover, <TT>ejabberd</TT> comes with a wide range of other state-of-the-art features:
|
||||
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Modular
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Load only the modules you want.
|
||||
</LI><LI CLASS="li-itemize">Extend <TT>ejabberd</TT> with your own custom modules.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Security
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
SASL and STARTTLS for c2s and s2s connections.
|
||||
</LI><LI CLASS="li-itemize">STARTTLS and Dialback s2s connections.
|
||||
</LI><LI CLASS="li-itemize">Web Admin accessible via HTTPS secure access.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Databases
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Internal database for fast deployment (Mnesia).
|
||||
</LI><LI CLASS="li-itemize">Native MySQL support.
|
||||
</LI><LI CLASS="li-itemize">Native PostgreSQL support.
|
||||
</LI><LI CLASS="li-itemize">ODBC data storage support.
|
||||
</LI><LI CLASS="li-itemize">Microsoft SQL Server support. </LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Authentication
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Internal Authentication.
|
||||
</LI><LI CLASS="li-itemize">PAM, LDAP and ODBC. </LI><LI CLASS="li-itemize">External Authentication script.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Others
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Support for virtual hosting.
|
||||
</LI><LI CLASS="li-itemize">Compressing XML streams with Stream Compression (<A HREF="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</A>).
|
||||
</LI><LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</A>).
|
||||
</LI><LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with support for clustering and HTML logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</A>.
|
||||
</LI><LI CLASS="li-itemize">Support for web clients: <A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> and <A HREF="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</A> services.
|
||||
</LI><LI CLASS="li-itemize">IRC transport.
|
||||
</LI><LI CLASS="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
|
||||
</LI></UL>
|
||||
</LI></UL><!--CUT END -->
|
||||
<!--HTMLFOOT-->
|
||||
<!--ENDHTML-->
|
||||
<!--FOOTER-->
|
||||
<HR SIZE=2><BLOCKQUOTE CLASS="quote"><EM>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
|
||||
</EM><A HREF="http://hevea.inria.fr/index.html"><EM>H</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>V</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>A</EM></A><EM>.</EM></BLOCKQUOTE></BODY>
|
||||
</HTML>
|
||||
-4516
File diff suppressed because it is too large
Load Diff
+412
-449
File diff suppressed because it is too large
Load Diff
@@ -1,146 +0,0 @@
|
||||
# Managing pubsub nodes through HTTP Atompub #
|
||||
|
||||
|
||||
## Configuration ##
|
||||
|
||||
These options will be used by the service to know how to build URLs. Using the previous configuration items the service should be accessed through `http://notify.push.bbc.co.uk:5280/pshb/<host>/<node>/`.
|
||||
|
||||
Also, in the ejabberd_http handler configuration, add the identified line.
|
||||
|
||||
{5280, ejabberd_http, [
|
||||
http_poll,
|
||||
web_admin,
|
||||
{request_handlers, [{["pshb"], pshb_http}]} % this should be added
|
||||
]}
|
||||
|
||||
It will automatically detect the version of mod_pubsub (odbc or mnesia) and call the appropriate module.
|
||||
|
||||
## Important notice ##
|
||||
|
||||
In the current version of the code, some security checks are not done :
|
||||
|
||||
* node creation uses the default `all` access_createnode acl, not checking for the actual configuration.
|
||||
|
||||
* most read operations are successfully executed without authentication. HOWEVER listing items can only be done when the node access_model is "open". In all other cases, the service returns 403. A finer grained authentication will be implemented.
|
||||
|
||||
|
||||
## Usage example with cURL ##
|
||||
|
||||
### Errors ###
|
||||
|
||||
HTTP status codes are used as intended. Additionally, the XMPP error stanza can also be set in the body :
|
||||
|
||||
$ curl -i -X POST -u cstar@localhost:encore -d @createnode.xml http://localhost:5280/pshb/localhost
|
||||
HTTP/1.1 409 Conflict
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Length: 95
|
||||
Content-type: application/xml
|
||||
|
||||
<error code='409' type='cancel'><conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>
|
||||
|
||||
or
|
||||
|
||||
$ curl -i -X DELETE -u cstar@localhost:encore http://localhost:5280/pshb/localhost/princely_musings
|
||||
HTTP/1.1 404 Not Found
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Length: 101
|
||||
Content-type: application/xml
|
||||
|
||||
<error code='404' type='cancel'><item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>
|
||||
|
||||
### Getting the service document ###
|
||||
|
||||
No authentication necessary. All nodes are listed.
|
||||
|
||||
$ curl -i http://host:port/pshb/domain/
|
||||
|
||||
### Getting items from a node ###
|
||||
|
||||
No authentication done, and all nodes are accessible.
|
||||
|
||||
$ curl -i http://host:port/pshb/domain/node/
|
||||
|
||||
|
||||
### Posting a new item ###
|
||||
|
||||
$ curl -u jid:password -i -X POST -d @entry.atom http://post:port/pshb/domain/node
|
||||
|
||||
User ability to post is based on node configuration.
|
||||
|
||||
### Editing a new item ###
|
||||
|
||||
$ curl -u jid:password -i -X POST -d @entry.atom http://post:port/pshb/domain/node/itemid
|
||||
|
||||
User ability to post is based on node configuration.
|
||||
|
||||
### Deleting an item ###
|
||||
|
||||
$ curl -u jid:password -i -X DELETE http://post:port/pshb/domain/node/itemid
|
||||
|
||||
User ability to post is based on node configuration.
|
||||
|
||||
|
||||
### Creating a new node ###
|
||||
|
||||
An instant node can be created if server configuration allows:
|
||||
|
||||
$ curl -X POST -u cstar@localhost:encore -d "" http://localhost:5280/pshb/localhost
|
||||
|
||||
or
|
||||
|
||||
$ curl -X POST -u cstar@localhost:encore -d "<pubsub><create node='princely_musings'/></pubsub>" http://localhost:5280/pshb/localhost
|
||||
|
||||
configure element (as per XEP-60) can be passed in the pubsub body.
|
||||
|
||||
$ cat createnode.xml
|
||||
<pubsub><create node='princely_musings' type='flat'/>
|
||||
<x xmlns='jabber:x:data' type='submit'>
|
||||
<field var='FORM_TYPE' type='hidden'>
|
||||
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
||||
</field>
|
||||
<field var='pubsub#title'><value>Princely Musings (Atom)</value></field>
|
||||
<field var='pubsub#max_payload_size'><value>1028</value></field>
|
||||
<field var='pubsub#type'><value>Atom</value></field>
|
||||
</x>
|
||||
</pubsub>
|
||||
|
||||
$ curl -i -X POST -u cstar@localhost:encore -d @createnode.xml http://localhost:5280/pshb/localhost
|
||||
HTTP/1.1 200 OK
|
||||
Content-Length: 130
|
||||
Content-Type: application/xml
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?><pubsub xmlns='http://jabber.org/protocol/pubsub'><create node='princely_musings'/></pubsub>
|
||||
|
||||
### Editing a node configuration ###
|
||||
|
||||
$ cat editnode.xml
|
||||
<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
|
||||
<configure node='princely_musings'>
|
||||
<x xmlns='jabber:x:data' type='submit'>
|
||||
<field var='FORM_TYPE' type='hidden'>
|
||||
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
||||
</field>
|
||||
<field var='pubsub#title'><value>Princely Musings (Atom)</value></field>
|
||||
<field var='pubsub#deliver_notifications'><value>1</value></field>
|
||||
<field var='pubsub#deliver_payloads'><value>1</value></field>
|
||||
<field var='pubsub#persist_items'><value>1</value></field>
|
||||
<field var='pubsub#max_items'><value>10</value></field>
|
||||
<field var='pubsub#item_expire'><value>604800</value></field>
|
||||
<field var='pubsub#access_model'><value>roster</value></field>
|
||||
</x>
|
||||
</configure>
|
||||
</pubsub>
|
||||
|
||||
|
||||
$ curl -i -X PUT -u cstar@localhost:encore -d @createnode.xml http://localhost:5280/pshb/localhost/princely_musings
|
||||
|
||||
|
||||
|
||||
### Deleting a node ###
|
||||
|
||||
A node is deleted by:
|
||||
|
||||
$ curl -X DELETE -u cstar@localhost:encore http://localhost:5280/pshb/localhost/princely_musings
|
||||
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ Peter Saint-Andr\'e, Executive Director of the Jabber Software Foundation}
|
||||
|
||||
\item \marking{Internationalized:} \ejabberd{} leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
|
||||
\begin{itemize}
|
||||
\item Translated to 25 languages. %%\improved{}
|
||||
\item Translated to 24 languages. %%\improved{}
|
||||
\item Support for \footahref{http://www.ietf.org/rfc/rfc3490.txt}{IDNA}.
|
||||
\end{itemize}
|
||||
|
||||
@@ -127,7 +127,6 @@ Moreover, \ejabberd{} comes with a wide range of other state-of-the-art features
|
||||
\item Users Directory based on users vCards.
|
||||
\item \txepref{0060}{Publish-Subscribe} component with support for \txepref{0163}{Personal Eventing via Pubsub}.
|
||||
\item Support for web clients: \txepref{0025}{HTTP Polling} and \txepref{0206}{HTTP Binding (BOSH)} services.
|
||||
\item IRC transport.
|
||||
\item Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
#!/usr/bin/env escript
|
||||
%% -*- erlang -*-
|
||||
|
||||
-record(cmd, {name, desc, longdesc, args, result}).
|
||||
|
||||
main(_) ->
|
||||
Dir = filename:absname(filename:join(["..", "src"])),
|
||||
FileIn = filename:join([Dir, "mod_admin_p1.erl"]),
|
||||
{ok, Forms1} = epp_dodger:parse_file(FileIn, [no_fail]),
|
||||
Comments = erl_comment_scan:file(FileIn),
|
||||
Forms = erl_recomment:recomment_forms(Forms1, Comments),
|
||||
Tree = erl_syntax:flatten_form_list(Forms),
|
||||
AuxFile = "mod_admin.tex",
|
||||
case file:open(AuxFile, [write]) of
|
||||
{ok, Fd} ->
|
||||
io:format(Fd, "\\newcommand{\\modadminsection}{\\begin{description}~n", []),
|
||||
process(Fd, Tree),
|
||||
io:format(Fd, "\\end{description}}~n", []),
|
||||
file:close(Fd),
|
||||
halt(0);
|
||||
{error, Why} ->
|
||||
io:format("failed to open file ~s: ~s",
|
||||
[AuxFile, file:format_error(Why)]),
|
||||
halt(1)
|
||||
end.
|
||||
|
||||
process(Fd, Tree) ->
|
||||
case erl_syntax:type(Tree) of
|
||||
record_expr ->
|
||||
case erl_syntax_lib:analyze_record_expr(Tree) of
|
||||
{record_expr, {ejabberd_commands, _}} ->
|
||||
Fs = erl_syntax:record_expr_fields(Tree),
|
||||
Cmd = lists:foldl(
|
||||
fun(F, C) ->
|
||||
Name = erl_syntax:record_field_name(F),
|
||||
Value = erl_syntax:record_field_value(F),
|
||||
case {erl_syntax:concrete(Name),
|
||||
catch erl_syntax:concrete(Value)} of
|
||||
{_, {'EXIT', _}} ->
|
||||
C;
|
||||
{name, V} ->
|
||||
C#cmd{name = V};
|
||||
{desc, V} ->
|
||||
C#cmd{desc = V};
|
||||
{longdesc, V} ->
|
||||
C#cmd{longdesc = V};
|
||||
{args, V} ->
|
||||
C#cmd{args = V};
|
||||
{result, V} ->
|
||||
C#cmd{result = V};
|
||||
_ ->
|
||||
C
|
||||
end
|
||||
end, #cmd{}, Fs),
|
||||
format_command(Fd, Cmd);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
case erl_syntax:subtrees(Tree) of
|
||||
[] ->
|
||||
ok;
|
||||
List ->
|
||||
lists:foreach(
|
||||
fun(Group) ->
|
||||
lists:foreach(
|
||||
fun(Subtree) ->
|
||||
process(Fd, Subtree)
|
||||
end, Group)
|
||||
end, List)
|
||||
end
|
||||
end.
|
||||
|
||||
-define(B(S), S).
|
||||
|
||||
format_command(Fd, #cmd{name = Cmd,
|
||||
desc = Desc,
|
||||
longdesc = _LongDesc,
|
||||
args = ArgsDef,
|
||||
result = _ResultDef}) ->
|
||||
io:format(Fd, "\\titem{~s ~s} ~s~n",
|
||||
[escape_underscores(atom_to_list(Cmd)),
|
||||
flatten_arguments(ArgsDef),
|
||||
escape_underscores(Desc)]).
|
||||
|
||||
flatten_arguments(Args) ->
|
||||
string:join(
|
||||
lists:map(
|
||||
fun({Name, _Type}) ->
|
||||
escape_underscores(io_lib:format("~s", [Name]))
|
||||
end, Args),
|
||||
" ").
|
||||
|
||||
escape_underscores(S) ->
|
||||
lists:flatten(
|
||||
[case C of
|
||||
$_ -> "\\_";
|
||||
_ -> C
|
||||
end || C <- S]).
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
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/
|
||||
@@ -1,58 +0,0 @@
|
||||
|
||||
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/
|
||||
@@ -6,7 +6,7 @@
|
||||
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
|
||||
http://redir.process-one.net/ejabberd-2.1.5
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
|
||||
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/
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.8
|
||||
|
||||
ejabberd 2.1.8 is the ninth release in ejabberd 2.1.x branch,
|
||||
and includes a PubSub regression bugfix.
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The change is:
|
||||
|
||||
- Fix issue on PubSub preventing publication of items (EJAB-1457)
|
||||
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -1,56 +0,0 @@
|
||||
|
||||
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/
|
||||
@@ -0,0 +1,108 @@
|
||||
|
||||
******************************************************************************
|
||||
* These are the preliminary release notes of ejabberd 3.0.0.
|
||||
*
|
||||
* The download page for preliminary releases is:
|
||||
* http://download.process-one.net/ejabberd/
|
||||
*
|
||||
* WARNING!!! PRELIMINARY DATABASE SCHEMA !!!
|
||||
* The database schema may change before ejabberd 3.0.0 is released,
|
||||
* and no migration code will be developed for this preliminary schema.
|
||||
* The only supported migrations are
|
||||
* from any ejabberd 0.9.0 ... 2.1.x to the final 3.0.0.
|
||||
* Don't use this preliminary ejabberd release for a production server.
|
||||
* You can test this release with a blank database or with a copy of your
|
||||
* production database, but don't let your users connect to this copy
|
||||
* because their changes may get lost when you upgrade to the final 3.0.0.
|
||||
******************************************************************************
|
||||
|
||||
|
||||
Release Notes
|
||||
ejabberd 3.0.0
|
||||
|
||||
ejabberd 3.0.0 is the first release in the brand new 3.x.x branch,
|
||||
and includes several minor bugfixes and a few improvements.
|
||||
|
||||
Read more details about the changes in:
|
||||
http://redir.process-one.net/ejabberd-3.0.0
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
ejabberd 3.0.0 includes three major changes:
|
||||
|
||||
* exmpp is now extensively used in ejabberd for parsing stanzas, and many more.
|
||||
|
||||
* gen_storage (abbreviated GS) provides a database abstraction layer,
|
||||
which supports storage in Mnesia and in ODBC databases.
|
||||
Several ejabberd modules use GS, like mod_roster and ejabberd_auth_storage.
|
||||
The schema of the tables stored by those modules have changed.
|
||||
ejabberd automatically creates mnesia and ODBC tables,
|
||||
and migrates them from a previous ejabberd version.
|
||||
|
||||
* Massive Hosting (abbreviated MH) is preliminary implemented in ejabberd,
|
||||
but it is still incomplete, untested and undocumented.
|
||||
New option static_modules.
|
||||
|
||||
|
||||
This is a more detailed list of changes since ejabberd 2.1.x releases:
|
||||
|
||||
* Requirements
|
||||
- Erlang/OTP R12B-5 or higher is required
|
||||
- exmpp 0.9.6 or higher is required, not optional
|
||||
- ESASL library is optional, for SASL GSSAPI authentication
|
||||
- Libexpat not required by ejabberd, it uses exmpp now (EJAB-1111)
|
||||
- GNU Iconv not required, because mod_irc isn't included anymore (EJAB-954)
|
||||
- Only database migration from ejabberd 0.9.0 up to 2.1.x is supported
|
||||
- ejabberd modules developed for previous releases need to be upgraded to exmpp
|
||||
|
||||
* Configuration
|
||||
- New option clusterid
|
||||
- New option that can't be used yet: static_modules
|
||||
- ejabberd_auth_storage: new option auth_storage
|
||||
- mod_muc_log: option spam_prevention is now link_nofollow (EJAB-1141)
|
||||
- mod_roster: new access option (EJAB-72)
|
||||
- S2S: allow definition of local address for outgoing connections (EJAB-418)
|
||||
|
||||
* Database
|
||||
- New gen_storage that provides a database storage abstraction layer (EJAB-1102)
|
||||
- mod_*_odbc are obsolete, use the normal ones with the option {backend, odbc}
|
||||
- New ejabberd_auth_storage that supports internal and odbc databases
|
||||
|
||||
* XEP support
|
||||
- Removed support for the deprecated XEP-0018 Invisible Presence (EJAB-810)
|
||||
- Update http_bind to XEP-0124 1.10 and XEP-0206 1.3
|
||||
- mod_multicast: service for XEP-0033: Extended Stanza Addressing (EJAB-265)
|
||||
- New ejabberd router for multicast packets (XEP-0033) (EJAB-329)
|
||||
- ejabberd_c2s uses XEP-0033 if mod_multicast is enabled (EJAB-267)
|
||||
- mod_muc uses XEP-0033 if mod_multicast is enabled (EJAB-266)
|
||||
|
||||
* Pubsub/PEP/Caps
|
||||
- Allow distinguish between leafs and items (EJAB-1027)
|
||||
- Enforce disco features results (EJAB-1033, EJAB-1228, EJAB-1238)
|
||||
- Enforce pubsub#presence_based_delivery (EJAB-1221)
|
||||
- Improve get_caps while still waiting for initial presence (EJAB-934)
|
||||
- Make nodetree_tree implicitly create parent node if required (EJAB-944)
|
||||
- Only use binary() instead of string() in Host and ServerHost (EJAB-1244)
|
||||
- Rename NodeId (pubsub_node.id) bindings to Nidx (EJAB-1000)
|
||||
- node_flat becomes default; it has the code instead of hometree (EJAB-1077)
|
||||
- pubsub#notify_sub node option (EJAB-1230)
|
||||
- pubsub#type node config and payload namespace (EJAB-1083)
|
||||
|
||||
* Miscellanea
|
||||
- Bugfix handling Service Discovery to contacts (EJAB-1207)
|
||||
- Bugfix: external contacts were offline after unavailable presence (EJAB-943)
|
||||
- Don't offer SASL auth before doing TLS if TLS is required
|
||||
- Experimental support for GSSAPI auth, requires esasl library (EJAB-831)
|
||||
- Replaced the full ejabberd_zlib into a simple exmpp_compress interface (EJAB-991)
|
||||
- Use binaries instead of lists where possible (EJAB-17)
|
||||
- Webadmin: provide list of registered pids/ports
|
||||
- Windows ejabberdctl.cmd only handles up to 4 arguments (EJAB-1216)
|
||||
- ejabberdctl: when starting, check it isn't already running
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -1,2 +0,0 @@
|
||||
% ejabberd version (automatically generated).
|
||||
\newcommand{\version}{3.0.0}
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# PROVIDE: ejabberd
|
||||
# REQUIRE: DAEMON
|
||||
# KEYWORD: shutdown
|
||||
#
|
||||
|
||||
HOME=/usr/pkg/jabber D=/usr/pkg/jabber/ejabberd export HOME
|
||||
|
||||
name="ejabberd"
|
||||
rcvar=$name
|
||||
|
||||
if [ -r /etc/rc.conf ]
|
||||
then
|
||||
. /etc/rc.conf
|
||||
else
|
||||
eval ${rcvar}=YES
|
||||
fi
|
||||
|
||||
# $flags from environment overrides ${rcvar}_flags
|
||||
if [ -n "${flags}" ]
|
||||
then
|
||||
eval ${rcvar}_flags="${flags}"
|
||||
fi
|
||||
|
||||
checkyesno()
|
||||
{
|
||||
eval _value=\$${1}
|
||||
case $_value in
|
||||
[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) return 0 ;;
|
||||
[Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0) return 1 ;;
|
||||
*)
|
||||
echo "\$${1} is not set properly."
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
cmd=${1:-start}
|
||||
case ${cmd} in
|
||||
force*)
|
||||
cmd=${cmd#force}
|
||||
eval ${rcvar}=YES
|
||||
;;
|
||||
esac
|
||||
|
||||
if checkyesno ${rcvar}
|
||||
then
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
|
||||
case ${cmd} in
|
||||
start)
|
||||
if [ -x $D/src ]; then
|
||||
echo "Starting ${name}."
|
||||
cd $D/src
|
||||
ERL_MAX_PORTS=32000 export ERL_MAX_PORTS
|
||||
ulimit -n $ERL_MAX_PORTS
|
||||
su jabber -c "/usr/pkg/bin/erl -sname ejabberd -s ejabberd -heart -detached -sasl sasl_error_logger '{file, \"ejabberd-sasl.log\"}' &" \
|
||||
1>/dev/null 2>&1
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
echo "rpc:call('ejabberd@`hostname -s`', init, stop, [])." | \
|
||||
su jabber -c "/usr/pkg/bin/erl -sname ejabberdstop"
|
||||
;;
|
||||
restart)
|
||||
echo "rpc:call('ejabberd@`hostname -s`', init, restart, [])." | \
|
||||
su jabber -c "/usr/pkg/bin/erl -sname ejabberdrestart"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo '1. fetch, compile, and install erlang'
|
||||
|
||||
if [ ! pkg_info erlang 1>/dev/null 2>&1 ]; then
|
||||
cd /usr/pkgsrc/lang/erlang
|
||||
make fetch-list|sh
|
||||
make
|
||||
make install
|
||||
fi
|
||||
if pkg_info erlang | grep -q erlang-9.1nb1; then
|
||||
else
|
||||
echo "erlang-9.1nb1 not installed" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
echo '2. install crypt_drv.so'
|
||||
|
||||
if [ ! -d /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib ] ; then
|
||||
mkdir -p /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib
|
||||
fi
|
||||
if [ ! -f /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib/crypto_drv.so ]; then
|
||||
cp work/otp*/lib/crypto/priv/*/*/crypto_drv.so \
|
||||
/usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib
|
||||
fi
|
||||
|
||||
|
||||
echo '3. compile and install elibcrypto.so'
|
||||
|
||||
if [ ! -f /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib/elibcrypto.so ]; then
|
||||
cd /usr/pkgsrc/lang/erlang/work/otp_src_R9B-1/lib/crypto/c_src
|
||||
ld -r -u CRYPTO_set_mem_functions -u MD5 -u MD5_Init -u MD5_Update \
|
||||
-u MD5_Final -u SHA1 -u SHA1_Init -u SHA1_Update -u SHA1_Final \
|
||||
-u des_set_key -u des_ncbc_encrypt -u des_ede3_cbc_encrypt \
|
||||
-L/usr/lib -lcrypto -o ../priv/obj/i386--netbsdelf/elibcrypto.o
|
||||
cc -shared \
|
||||
-L/usr/pkgsrc/lang/erlang/work/otp_src_R9B-1/lib/erl_interface/obj/i386--netbsdelf \
|
||||
-o ../priv/obj/i386--netbsdelf/elibcrypto.so \
|
||||
../priv/obj/i386--netbsdelf/elibcrypto.o -L/usr/lib -lcrypto
|
||||
cp ../priv/obj/i386--netbsdelf/elibcrypto.so \
|
||||
/usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib
|
||||
fi
|
||||
|
||||
|
||||
echo '4. compile and install ssl_esock'
|
||||
|
||||
if [ ! -f /usr/pkg/lib/erlang/lib/ssl-2.3.5/priv/bin/ssl_esock ]; then
|
||||
cd /usr/pkg/lib/erlang/lib/ssl-2.3.5/priv/obj/
|
||||
make
|
||||
fi
|
||||
|
||||
|
||||
echo '5. initial ejabberd configuration'
|
||||
|
||||
cd /usr/pkg/jabber/ejabberd/src
|
||||
./configure
|
||||
|
||||
|
||||
echo '6. edit ejabberd Makefiles'
|
||||
|
||||
for M in Makefile mod_*/Makefile; do
|
||||
if [ ! -f $M.orig ]; then
|
||||
mv $M $M.orig
|
||||
sed -e s%/usr/local%/usr/pkg%g < $M.orig > $M
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
echo '7. compile ejabberd'
|
||||
|
||||
gmake
|
||||
for A in mod_irc mod_muc mod_pubsub; do
|
||||
(cd $A; gmake)
|
||||
done
|
||||
|
||||
|
||||
echo ''
|
||||
echo 'now edit ejabberd.cfg'
|
||||
echo ''
|
||||
echo 'to start ejabberd: erl -sname ejabberd -s ejabberd'
|
||||
@@ -1,66 +0,0 @@
|
||||
% jabber.dbc.mtview.ca.us
|
||||
|
||||
override_acls.
|
||||
|
||||
{acl, admin, {user, "mrose", "jabber.dbc.mtview.ca.us"}}.
|
||||
|
||||
|
||||
{access, announce, [{allow, admin},
|
||||
{deny, all}]}.
|
||||
{access, c2s, [{deny, blocked},
|
||||
{allow, all}]}.
|
||||
{access, c2s_shaper, [{none, admin},
|
||||
{normal, all}]}.
|
||||
{access, configure, [{allow, admin},
|
||||
{deny, all}]}.
|
||||
{access, disco_admin, [{allow, admin},
|
||||
{deny, all}]}.
|
||||
{access, muc_admin, [{allow, admin},
|
||||
{deny, all}]}.
|
||||
{access, register, [{deny, all}]}.
|
||||
{access, s2s_shaper, [{fast, all}]}.
|
||||
|
||||
|
||||
{auth_method, internal}.
|
||||
{host, "jabber.dbc.mtview.ca.us"}.
|
||||
{outgoing_s2s_port, 5269}.
|
||||
{shaper, normal, {maxrate, 1000}}.
|
||||
{shaper, fast, {maxrate, 50000}}.
|
||||
{welcome_message, none}.
|
||||
|
||||
|
||||
{listen, [{5222, ejabberd_c2s,
|
||||
[{access, c2s},
|
||||
{shaper, c2s_shaper}]},
|
||||
{5223, ejabberd_c2s,
|
||||
[{access, c2s},
|
||||
{shaper, c2s_shaper},
|
||||
{ssl, [{certfile, "/etc/openssl/certs/ejabberd.pem"}]}]},
|
||||
{5269, ejabberd_s2s_in,
|
||||
[{shaper, s2s_shaper}]}]}.
|
||||
|
||||
|
||||
{modules, [
|
||||
{mod_register, []},
|
||||
{mod_roster, []},
|
||||
{mod_privacy, []},
|
||||
{mod_configure, []},
|
||||
{mod_disco, []},
|
||||
{mod_stats, []},
|
||||
{mod_vcard, []},
|
||||
{mod_offline, []},
|
||||
{mod_echo, [{host, "echo.jabber.dbc.mtview.ca.us"}]},
|
||||
{mod_private, []},
|
||||
% {mod_irc, []},
|
||||
{mod_muc, []},
|
||||
{mod_pubsub, []},
|
||||
{mod_time, []},
|
||||
{mod_last, []},
|
||||
{mod_version, []}
|
||||
]}.
|
||||
|
||||
|
||||
|
||||
% Local Variables:
|
||||
% mode: erlang
|
||||
% End:
|
||||
@@ -1,77 +0,0 @@
|
||||
<!-- aim-transport.xml -->
|
||||
|
||||
<jabber>
|
||||
|
||||
<!--
|
||||
You need to add elogger and rlogger entries when using ejabberd.
|
||||
In this case the transport will do the logging.
|
||||
-->
|
||||
|
||||
<log id='elogger'>
|
||||
<host/>
|
||||
<logtype/>
|
||||
<format>%d: [%t] (%h): %s</format>
|
||||
<file>/var/log/jabber/aim-transport-error.log</file>
|
||||
</log>
|
||||
|
||||
<log id='rlogger'>
|
||||
<host/>
|
||||
<logtype>record</logtype>
|
||||
<format>%d %h %s</format>
|
||||
<file>/var/log/jabber/aim-transport-record.log</file>
|
||||
</log>
|
||||
|
||||
<!--
|
||||
ejabberd do not provide XDB services.
|
||||
xdb_file.so is loaded in to handle all XDB requests.
|
||||
-->
|
||||
|
||||
<xdb id="xdb">
|
||||
<host/>
|
||||
<load>
|
||||
<xdb_file>/usr/local/lib/jabber/libjabberdxdbfile.so</xdb_file> <!-- This file is part of jabberd-1.4.x. -->
|
||||
</load>
|
||||
<xdb_file xmlns="jabber:config:xdb_file">
|
||||
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
|
||||
</xdb_file>
|
||||
</xdb>
|
||||
|
||||
<!--
|
||||
Make sure that all host names here are resolveable via DNS if you
|
||||
want the transport to be available to the public.
|
||||
-->
|
||||
|
||||
<service id='aim.SERVER.COM'>
|
||||
<!-- aim-transport configuration. -->
|
||||
<aimtrans xmlns='jabber:config:aimtrans'>
|
||||
<vCard>
|
||||
<FN>AIM/ICQ Transport</FN>
|
||||
<DESC>This is the AIM/ICQ Transport.</DESC>
|
||||
<MAIL>EMAIL@ADDRESS.COM</MAIL>
|
||||
<URL>http://aim-transport.jabberstudio.org/</URL>
|
||||
</vCard>
|
||||
<charset>cp1252</charset>
|
||||
</aimtrans>
|
||||
<!-- aim-transport module. -->
|
||||
<load>
|
||||
<aim_transport>/usr/local/lib/jabber/aim-transport.so</aim_transport>
|
||||
</load>
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The settings below have to match the settings you made
|
||||
in your ejabberd.cfg configuration file.
|
||||
-->
|
||||
|
||||
<service id="icq-linker">
|
||||
<uplink/>
|
||||
<connect>
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>5233</port>
|
||||
<secret>SECRET</secret>
|
||||
</connect>
|
||||
</service>
|
||||
|
||||
<pidfile>/var/run/jabber/aim-transport.pid</pidfile>
|
||||
|
||||
</jabber>
|
||||
@@ -1,136 +0,0 @@
|
||||
<!-- ile.xml -->
|
||||
|
||||
<config>
|
||||
<jabber>
|
||||
<server>127.0.0.1</server>
|
||||
<port>5238</port>
|
||||
<secret>SECRET</secret>
|
||||
<service>ile.SERVER.COM</service>
|
||||
<connectsleep>7</connectsleep> <!-- seconds to wait if we get disconnected -->
|
||||
<language>en</language>
|
||||
<vCard>
|
||||
<FN>I Love Email</FN>
|
||||
<DESC>With this service you can receive email notifications.
|
||||
|
||||
Security warning: Be careful when using this. Your password will travel in clear from your client to your jabber server if you don't use SSL and it will probably travel in clear from the jabber server to your email server. Use with care. This shouldn't be an issue in your Intranet, but it is if you use an ILE installed in a foreign jabber server.</DESC>
|
||||
<MAIL>EMAIL@ADDRESS.COM</MAIL>
|
||||
<URL>http://ile.jabberstudio.org/</URL>
|
||||
</vCard>
|
||||
</jabber>
|
||||
|
||||
<debug>
|
||||
<file>/var/log/jabber/ile.log</file>
|
||||
<level>1</level> <!-- man Net::Jabber::Debug -->
|
||||
</debug>
|
||||
|
||||
<mail>
|
||||
<checkinterval>10</checkinterval> <!-- in minutes -->
|
||||
<timeout>20</timeout> <!-- timeout for IMAP/POP connection, in seconds -->
|
||||
</mail>
|
||||
|
||||
<files>
|
||||
<users>/var/spool/jabber/ile.SERVER.COM/users.db</users>
|
||||
<passwords>/var/spool/jabber/ile.SERVER.COM/passwords.db</passwords>
|
||||
<hosts>/var/spool/jabber/ile.SERVER.COM/hosts.db</hosts>
|
||||
<types>/var/spool/jabber/ile.SERVER.COM/types.db</types>
|
||||
<notifyxa>/var/spool/jabber/ile.SERVER.COM/notifyxa.db</notifyxa>
|
||||
<notifydnd>/var/spool/jabber/ile.SERVER.COM/notifydnd.db</notifydnd>
|
||||
<urls>/var/spool/jabber/ile.SERVER.COM/urls.db</urls>
|
||||
</files>
|
||||
|
||||
<form>
|
||||
<en>
|
||||
<instructions>Please fill in the fields,according to your email account settings and notification preferences</instructions>
|
||||
<title>ILE: Email notification service</title>
|
||||
<email_options>Email account settings</email_options>
|
||||
<user>Username</user>
|
||||
<pass>Password</pass>
|
||||
<host>Hostname</host>
|
||||
<type>Type</type>
|
||||
<newmail>You have received NUM email messages since last time I checked, which was CHECKINTERVAL minutes ago.</newmail>
|
||||
<errorcheck>There was an error while trying to check mail for ACCOUNT.</errorcheck>
|
||||
<notify_options>Notification Options</notify_options>
|
||||
<notifyxa>Notify even when Xtended Away (XA)</notifyxa>
|
||||
<notifydnd>Notify even when Do Not Disturb (DND)</notifydnd>
|
||||
<webmail_url>Webmail URL</webmail_url>
|
||||
<webmail_login>Login to ACCOUNT</webmail_login>
|
||||
<iledesc>ILE: an email notifier component: http://ile.jabberstudio.org</iledesc>
|
||||
</en>
|
||||
|
||||
<es>
|
||||
<instructions>Por favor, rellene los campos del formulario.</instructions>
|
||||
<title>ILE: Servicio de notificación de correo</title>
|
||||
<email_options>Configuración de la cuenta de correo</email_options>
|
||||
<user>Usuario</user>
|
||||
<pass>Clave</pass>
|
||||
<host>Host</host>
|
||||
<type>Tipo</type>
|
||||
<newmail>Ha recibido NUM email(s) desde la última comprobación que fue hace CHECKINTERVAL minutos</newmail>
|
||||
<errorcheck>Ha habido un error en la comprobación del correo para la cuenta ACCOUNT.</errorcheck>
|
||||
<notify_options>Opciones de notificación</notify_options>
|
||||
<notifyxa>Notificar incluso si muy ausente (XA)</notifyxa>
|
||||
<notifydnd>Notificar incluso si no molestar (DND)</notifydnd>
|
||||
<webmail_url>Webmail URL</webmail_url>
|
||||
<webmail_login>Leer correo de ACCOUNT</webmail_login>
|
||||
<iledesc>ILE: un notificador de nuevo email - http://ile.jabberstudio.org</iledesc>
|
||||
</es>
|
||||
|
||||
<ca>
|
||||
<instructions>Ompli els camps del formulari.</instructions>
|
||||
<title>ILE: Servei de notificació de nou email</title>
|
||||
<email_options>Dades del compte de mail</email_options>
|
||||
<user>Usuari</user>
|
||||
<pass>Clau</pass>
|
||||
<host>Host</host>
|
||||
<type>Tipus</type>
|
||||
<newmail>Ha rebut NUM email(s) des de la última comprobació que va ser fa CHECKINTERVAL minuts.</newmail>
|
||||
<errorcheck>S'ha produit un error en la comprobació del correu per al compte ACCOUNT.</errorcheck>
|
||||
<notify_options>Opcions de notificació</notify_options>
|
||||
<notifyxa>Notificar si molt absent (XA)</notifyxa>
|
||||
<notifydnd>Notificar si no molestar (DND)</notifydnd>
|
||||
<webmail_url>Webmail URL</webmail_url>
|
||||
<webmail_login>Llegir correu de ACCOUNT</webmail_login>
|
||||
<iledesc>ILE: un notificador de nou email - http://ile.jabberstudio.org</iledesc>
|
||||
</ca>
|
||||
|
||||
<ro>
|
||||
<!-- Contributed by Adrian Rappa -->
|
||||
<instructions>Va rog completati urmatoarele campuri</instructions>
|
||||
<title>I Love Email: new email notification service</title>
|
||||
<email_options>Email account settings</email_options>
|
||||
<user>Nume utilizator</user>
|
||||
<pass>Parola</pass>
|
||||
<host>Nume gazda</host>
|
||||
<type>Tip</type>
|
||||
<newmail>Ati primit NUM mesaj(e) de la ultima verificare, care a fost acum CHECKINTERVAL minute.</newmail>
|
||||
<errorcheck>A fost eroare in timp ce incercam sa verific posta pentru ACCOUNT.</errorcheck>
|
||||
<notify_options>Notification Options</notify_options>
|
||||
<notifyxa>Notify even when Xtended Away (XA)</notifyxa>
|
||||
<notifydnd>Notify even when Do Not Disturb (DND)</notifydnd>
|
||||
<webmail_url>Webmail URL</webmail_url>
|
||||
<webmail_login>Login to ACCOUNT</webmail_login>
|
||||
<iledesc>ILE: an email notifier component: http://ile.jabberstudio.org</iledesc>
|
||||
</ro>
|
||||
|
||||
<nl>
|
||||
<!-- Contributed by Sander Devrieze -->
|
||||
<instructions>Vul volgende velden in.</instructions>
|
||||
<title>ILE: Dienst voor e-mailnotificaties</title>
|
||||
<email_options>Instellingen van e-mailaccount</email_options>
|
||||
<user>Gebruikersnaam</user>
|
||||
<pass>Wachtwoord</pass>
|
||||
<host>Inkomende mailserver</host>
|
||||
<type>Type verbinding</type>
|
||||
<newmail>U hebt NUM berichten ontvangen sinds CHECKINTERVAL minuten geleden.</newmail>
|
||||
<errorcheck>Fout tijdens controle op nieuwe e-mails bij ACCOUNT. ILE zal deze account niet meer opnieuw controleren tot u uw registratiegegevens wijzigt of opnieuw aanmeldt.</errorcheck>
|
||||
<notify_options>Notificatie-instellingen</notify_options>
|
||||
<notifyxa>Notificeer ook in de status Niet Beschikbaar (XA)</notifyxa>
|
||||
<notifydnd>Notificeer ook in de status Niet Storen (DND)</notifydnd>
|
||||
<webmail_url>URL van webmail</webmail_url>
|
||||
<webmail_login>Aanmelden op ACCOUNT</webmail_login>
|
||||
<iledesc>ILE: een dienst om e-mailnotificaties te ontvangen: http://ile.jabberstudio.org</iledesc>
|
||||
</nl>
|
||||
|
||||
</form>
|
||||
|
||||
</config>
|
||||
@@ -1,149 +0,0 @@
|
||||
<jggtrans>
|
||||
|
||||
<service jid="gg.SERVER.COM"/>
|
||||
|
||||
<!-- This connects the jabber-gg-transport process to ejabberd. -->
|
||||
<connect id="gglinker">
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>5237</port>
|
||||
<secret>SECRET</secret>
|
||||
</connect>
|
||||
|
||||
<register>
|
||||
<!-- This tag contains the message displayed to users at registration time.
|
||||
You can use <p/> and/or <br/> to break lines. Multiple spaces and newlines
|
||||
are converted to just one, so formatting of config file doesn't really matter. -->
|
||||
<instructions>
|
||||
Fill in your GG number (after "username")
|
||||
and password to register on the transport.
|
||||
<p/>To change your information in the GaduGadu directory you need to fill in the other fields.
|
||||
<p/>To remove registration you need to leave the form blank.
|
||||
</instructions>
|
||||
</register>
|
||||
|
||||
<search>
|
||||
<!-- This tag contains the message displayed to users at search time. -->
|
||||
<instructions>
|
||||
To search people:<br/>
|
||||
First fill in surname or family name, nickname, city, birthyear or range of birthyears (eg. 1950-1960)
|
||||
and gender (you may fill in more fields at once).<br/>
|
||||
or<br/>
|
||||
Fill in phone number<br/>
|
||||
or<br/>
|
||||
Fill in the GG number of the person you are searching.
|
||||
</instructions>
|
||||
</search>
|
||||
|
||||
<gateway>
|
||||
<!-- This is message, that may be displayed to user when adding gg contact. -->
|
||||
<desc>Please fill in the GaduGadu number of the person you want to add.</desc>
|
||||
<!-- And this is the prompt for GG number. -->
|
||||
<prompt>GG Nummer</prompt>
|
||||
</gateway>
|
||||
|
||||
<vCard>
|
||||
<FN>Gadu-Gadu Transport</FN>
|
||||
<DESC>This is the Gadu-Gadu Transport.</DESC>
|
||||
<EMAIL>EMAIL@ADDRESS.COM</EMAIL>
|
||||
<URL>http://www.jabberstudio.org/projects/jabber-gg-transport/</URL>
|
||||
</vCard>
|
||||
|
||||
<!-- Default user locale (language).
|
||||
Empty means system locale setting,
|
||||
no (or commented-out) <default_locale> tag means no translations. -->
|
||||
<!-- <default_locale>pl_PL</default_locale> -->
|
||||
|
||||
<!-- Logger configuration.
|
||||
You may configure one logger of type "syslog" and/or one of type "file".
|
||||
You may also not configure logging at all. -->
|
||||
<log type="syslog" facility="local0"/>
|
||||
<log type="file">/var/log/jabber/jabber-gg-transport.log</log>
|
||||
|
||||
<!-- Uncomment this, if you want proxy to be used for Gadu-Gadu connection. -->
|
||||
<!--
|
||||
<proxy>
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>8080</port>
|
||||
</proxy>
|
||||
-->
|
||||
|
||||
<!-- You can change these values according to your needs. -->
|
||||
<conn_timeout>60</conn_timeout>
|
||||
<ping_interval>10</ping_interval>
|
||||
|
||||
<!-- Gadu-Gadu server doesn't seem to answer pings anymore :-(
|
||||
So let's give it 10 year :-) -->
|
||||
<pong_timeout>315360000</pong_timeout>
|
||||
|
||||
<!-- This time after disconnection from Gadu-Gadu server the transport
|
||||
will try to connect again. -->
|
||||
<reconnect>300</reconnect>
|
||||
|
||||
<!-- How long to wait before restart, after jabber server connection is broken
|
||||
negative value means, that jggtrans should terminate. -->
|
||||
<restart_timeout>60</restart_timeout>
|
||||
|
||||
<!-- Delay between the unavailable presence is received from user and loggin out
|
||||
from Gadu-Gadu - for nice <presence type="invisible"/> support. -->
|
||||
<disconnect_delay>5</disconnect_delay>
|
||||
|
||||
<!-- list of Gadu-Gadu servers to use.
|
||||
<hub/> means "use GG hub to find server"
|
||||
<server/> tag should contain server address and may contain "port"
|
||||
attribute with port number. When TLS is available (supported by libgadu)
|
||||
it will be used unless "tls" attribute is set to "no". Please notice,
|
||||
that not all servers will accept TLS connections.
|
||||
Servers (including hub) are tried in order as they appear in <servers/>
|
||||
element.
|
||||
A reasonable default server list is hardcoded in jggtrans.
|
||||
-->
|
||||
<!--
|
||||
<servers>
|
||||
<hub/>
|
||||
<server port="443">217.17.41.90</server>
|
||||
<server tls="no">217.17.41.85</server>
|
||||
<server tls="no">217.17.41.88</server>
|
||||
</servers>
|
||||
-->
|
||||
|
||||
<!-- Spool directory. This is the place, where user info will be stored. -->
|
||||
<!-- Be careful about permissions - users' Gadu-Gadu passwords are stored there. -->
|
||||
<spool>/var/spool/jabber/gg.SERVER.COM/</spool>
|
||||
|
||||
<!-- Where to store pid file. This tag is optional. -->
|
||||
<pidfile>/var/run/jabber/jabber-gg-transport.pid</pidfile>
|
||||
|
||||
<!-- jid allowed to do some administrative task (eg. discovering online users).
|
||||
May be used multiple times. -->
|
||||
<admin>GG_TRANSPORT_ADMIN@SERVER.COM</admin>
|
||||
|
||||
<!-- ACL gives detailed access control to the transport. -->
|
||||
<acl>
|
||||
<!-- Example entries: -->
|
||||
|
||||
<allow who="admin@SERVER.COM" what="iq/query?xmlns=http://jabber.org/protocol/stats"/>
|
||||
<!-- will allow statistics gathering to admin@SERVER.COM -->
|
||||
|
||||
<deny who="*" what="iq/query?xmlns=http://jabber.org/protocol/stats"/>
|
||||
<!-- will deny statistics gathering for anybody else -->
|
||||
|
||||
<!-- <allow who="*@SERVER.COM"/> -->
|
||||
<!-- will allow anything else to users from "SERVER.COM" -->
|
||||
|
||||
<!-- <deny what="iq/query?xmlns=jabber:x:register"/> -->
|
||||
<!-- will deny registration for all other users -->
|
||||
|
||||
<!-- <allow what="presence"/> -->
|
||||
<!-- allow presence from anybody -->
|
||||
|
||||
<!-- <allow what="iq"/> -->
|
||||
<!-- allow iq from anybody -->
|
||||
|
||||
<!-- <allow what="message"/> -->
|
||||
<!-- allow message from anybody -->
|
||||
|
||||
<!-- <deny/> -->
|
||||
<!-- will deny anything else -->
|
||||
</acl>
|
||||
|
||||
</jggtrans>
|
||||
@@ -1,128 +0,0 @@
|
||||
<!-- jit.xml -->
|
||||
|
||||
<jabber>
|
||||
|
||||
<!--
|
||||
You need to add elogger and rlogger entries here when using ejabberd.
|
||||
In this case the transport will do the logging.
|
||||
-->
|
||||
|
||||
<log id='elogger'>
|
||||
<host/>
|
||||
<logtype/>
|
||||
<file>/var/log/jabber/jit-error</file> <!-- WPJabber logs with date. -->
|
||||
</log>
|
||||
|
||||
<log id='rlogger'>
|
||||
<host/>
|
||||
<logtype>record</logtype>
|
||||
<file>/var/log/jabber/jit-record</file> <!-- WPJabber logs with date. -->
|
||||
</log>
|
||||
|
||||
<!--
|
||||
ejabberd do not provide XDB services.
|
||||
xdb_file-jit.so (the renamed xdb_file.so from WPJabber) is
|
||||
loaded in to handle all XDB requests.
|
||||
Read also the documentation in xdb_file/README from the JIT package.
|
||||
-->
|
||||
|
||||
<xdb id="xdb">
|
||||
<host/>
|
||||
<load>
|
||||
<xdb_file>/usr/local/lib/jabber/xdb_file.so</xdb_file> <!-- The xdb_file.so from WPJabber/JIT. -->
|
||||
</load>
|
||||
<xdb_file xmlns="jabber:config:xdb_file">
|
||||
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
|
||||
</xdb_file>
|
||||
</xdb>
|
||||
|
||||
<!--
|
||||
Make sure that all host names here are resolveable via DNS if you
|
||||
want the transport to be available to the public.
|
||||
-->
|
||||
|
||||
<service id="icq.SERVER.COM">
|
||||
<!--
|
||||
Replace SERVER.COM with the same as above to enable sms.
|
||||
-->
|
||||
<host>sms.icq.SERVER.COM</host>
|
||||
<!-- JIT configuration. -->
|
||||
<icqtrans xmlns="jabber:config:icqtrans">
|
||||
<sms>
|
||||
<host>sms.icq.SERVER.COM</host>
|
||||
<!-- Status of virtual "sms-contacts". -->
|
||||
<show>away</show>
|
||||
<status/>
|
||||
</sms>
|
||||
<instructions>Fill in your UIN and password.</instructions>
|
||||
<search>Search ICQ users.</search>
|
||||
<vCard>
|
||||
<FN>ICQ Transport (JIT)</FN>
|
||||
<DESC>This is the Jabber ICQ Transport.</DESC>
|
||||
<MAIL>EMAIL@ADDRESS.COM</MAIL>
|
||||
<URL>http://jit.jabberstudio.org/</URL>
|
||||
</vCard>
|
||||
<!-- Hashtable for users. -->
|
||||
<prime>3907</prime>
|
||||
<!-- Send messages from ICQ as chat to Jabber clients. -->
|
||||
<chat/>
|
||||
<!-- Enable this for ICQ web presence. -->
|
||||
<web/>
|
||||
<!--
|
||||
If you don't want jabber:x:data forms
|
||||
in reg and search uncomment this tag
|
||||
(Not recomended).
|
||||
-->
|
||||
<no_xdata/>
|
||||
<!--
|
||||
This tag is necessary when using ejabberd.
|
||||
In this way JIT will have its own contact list.
|
||||
-->
|
||||
<own_roster/>
|
||||
<!--
|
||||
When present, this tag will tell JIT not to try to
|
||||
get the user's roster (which will take a bit of time
|
||||
to fail in scenarios described above).
|
||||
-->
|
||||
<no_jabber_roster/>
|
||||
<!-- File with stats. -->
|
||||
<user_count_file>/var/spool/jabber/jit-count</user_count_file>
|
||||
<!--
|
||||
Interval beetween checking sessions: ping, messages, acks.
|
||||
-->
|
||||
<session_check>5</session_check>
|
||||
<!-- Reconnect retries. -->
|
||||
<reconnects>5</reconnects>
|
||||
<!--
|
||||
Time in sec when session can be inactive, 0=disabled.
|
||||
-->
|
||||
<session_timeout>18000</session_timeout>
|
||||
<charset>windows-1252</charset>
|
||||
<server>
|
||||
<host port="5190">login.icq.com</host>
|
||||
</server>
|
||||
</icqtrans>
|
||||
<!-- JIT module. -->
|
||||
<load>
|
||||
<icqtrans>/usr/local/lib/jabber/jit.so</icqtrans>
|
||||
</load>
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The settings below have to match the settings you made
|
||||
in your ejabberd.cfg configuration file.
|
||||
-->
|
||||
|
||||
<service id="icq-linker">
|
||||
<host>SERVER.COM</host>
|
||||
<uplink/>
|
||||
<connect>
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>5234</port>
|
||||
<secret>SECRET</secret>
|
||||
</connect>
|
||||
</service>
|
||||
|
||||
<pidfile>/var/run/jabber/jit.pid</pidfile>
|
||||
|
||||
</jabber>
|
||||
@@ -1,118 +0,0 @@
|
||||
<!-- msn-transport.xml -->
|
||||
|
||||
<jabber>
|
||||
|
||||
<!--
|
||||
You need to add elogger and rlogger entries here when using ejabberd.
|
||||
In this case the transport will do the logging.
|
||||
-->
|
||||
|
||||
<log id='elogger'>
|
||||
<host/>
|
||||
<logtype/>
|
||||
<format>%d: [%t] (%h): %s</format>
|
||||
<file>/var/log/jabber/msn-transport-error.log</file>
|
||||
</log>
|
||||
|
||||
<log id='rlogger'>
|
||||
<host/>
|
||||
<logtype>record</logtype>
|
||||
<format>%d %h %s</format>
|
||||
<file>/var/log/jabber/msn-transport-record.log</file>
|
||||
</log>
|
||||
|
||||
<!--
|
||||
ejabberd do not provide XDB services.
|
||||
xdb_file.so is loaded in to handle all XDB requests.
|
||||
-->
|
||||
|
||||
<xdb id="xdb">
|
||||
<host/>
|
||||
<load>
|
||||
<xdb_file>/usr/local/lib/jabber/libjabberdxdbfile.so</xdb_file>
|
||||
</load>
|
||||
<xdb_file xmlns="jabber:config:xdb_file">
|
||||
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
|
||||
</xdb_file>
|
||||
</xdb>
|
||||
|
||||
<!--
|
||||
Make sure that all host names here are resolveable via DNS if you
|
||||
want the transport to be available to the public.
|
||||
-->
|
||||
|
||||
<service id="msn.SERVER.COM">
|
||||
<!-- msn-transport configuration. -->
|
||||
<msntrans xmlns="jabber:config:msntrans">
|
||||
<instructions>Fill in your MSN account and password (eg: user1@hotmail.com). A nickname is optional.</instructions>
|
||||
<vCard>
|
||||
<FN>MSN Transport</FN>
|
||||
<DESC>This is the MSN Transport.</DESC>
|
||||
<EMAIL>EMAIL@ADDRESS.COM</EMAIL>
|
||||
<URL>http://msn-transport.jabberstudio.org/</URL>
|
||||
</vCard>
|
||||
<!--
|
||||
Conference support allows you to create groupchat rooms on the
|
||||
msn-transport and invite MSN users to join.
|
||||
-->
|
||||
<conference id="conference.msn.SERVER.COM">
|
||||
<!--
|
||||
This will make MSN transport invite you to a special groupchat
|
||||
room when more then one user joins a normal one-on-one session.
|
||||
Joining this room will make MSN transport "switch" the session
|
||||
into groupchat mode. If you ignore it, MSN transport will
|
||||
continue to send the messages as one-on-one chats.
|
||||
-->
|
||||
<invite>More than one user entered this chat session. Enter this room to switch to groupchat modus.</invite>
|
||||
<notice>
|
||||
<join> is available</join>
|
||||
<leave> has leaved the room</leave>
|
||||
</notice>
|
||||
</conference>
|
||||
<!-- Enable Hotmail inbox notification. -->
|
||||
<headlines/>
|
||||
<!--
|
||||
Enable fancy friendly names
|
||||
If the user enters a nickname upon registration, and the user has
|
||||
a status message, their MSN friendly name will be "nickname - status message".
|
||||
|
||||
If the user does not enter a nickname on registration, but they do have
|
||||
a status message, their friendly name will just be their status message.
|
||||
|
||||
If the user did enter a nickname on registration, but they have a blank status message,
|
||||
then their friendly name will just be the registered nickname.
|
||||
|
||||
If the user did not enter a nickname on registration, and they have a blank status message,
|
||||
their nickname will just be the username portion of their JID.
|
||||
|
||||
If the above chosen friendly name is too long, then it will be truncated and "..." placed
|
||||
at the end. MSN only supports friendly names of 128 characters, so this is unavoidable.
|
||||
|
||||
If this is disabled, then the registered nick is always sent as the MSN friendly name,
|
||||
or if that is blank, the username portion of their JID is sent instead.
|
||||
-->
|
||||
<fancy_friendly/>
|
||||
</msntrans>
|
||||
<!-- msn-transport module. -->
|
||||
<load>
|
||||
<msntrans>/usr/local/lib/jabber/msn-transport.so</msntrans>
|
||||
</load>
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The settings below have to match the settings you made
|
||||
in your ejabberd.cfg configuration file.
|
||||
-->
|
||||
|
||||
<service id="msn-linker">
|
||||
<uplink/>
|
||||
<connect>
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>5235</port>
|
||||
<secret>SECRET</secret>
|
||||
</connect>
|
||||
</service>
|
||||
|
||||
<pidfile>/var/run/jabber/msn-transport.pid</pidfile>
|
||||
|
||||
</jabber>
|
||||
@@ -1,86 +0,0 @@
|
||||
<!-- yahoo-transport-2.xml -->
|
||||
|
||||
<jabber>
|
||||
|
||||
<!--
|
||||
You need to add the elogger entry here when using ejabberd.
|
||||
In this case the transport will do the logging.
|
||||
-->
|
||||
|
||||
<log id='elogger'>
|
||||
<host/>
|
||||
<logtype/>
|
||||
<format>%d: [%t] (%h): %s</format>
|
||||
<file>/var/log/jabber/yahoo-transport-2-error.log</file>
|
||||
<stderr/>
|
||||
</log>
|
||||
|
||||
<!--
|
||||
ejabberd do not provide XDB services.
|
||||
xdb_file.so is loaded in to handle all XDB requests.
|
||||
-->
|
||||
|
||||
<xdb id="xdb">
|
||||
<host/>
|
||||
<load>
|
||||
<xdb_file>/usr/local/lib/jabber/libjabberdxdbfile.so</xdb_file>
|
||||
</load>
|
||||
<xdb_file xmlns="jabber:config:xdb_file">
|
||||
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
|
||||
</xdb_file>
|
||||
</xdb>
|
||||
|
||||
<!--
|
||||
Make sure that all host names here are resolveable via DNS if you
|
||||
want the transport to be available to the public.
|
||||
-->
|
||||
|
||||
<service id="yahoo.SERVER.COM">
|
||||
<!-- yahoo-transport-2 configuration. -->
|
||||
<config xmlns="jabber:config:yahoo">
|
||||
<vCard>
|
||||
<NAME>Yahoo! Transport</NAME>
|
||||
<FN>vCard not implemented in current version</FN>
|
||||
<DESC>This is the Yahoo! transport.</DESC>
|
||||
<MAIL>EMAIL@ADDRESS.COM</MAIL>
|
||||
<URL>http://yahoo-transport-2.jabberstudio.org/</URL>
|
||||
</vCard>
|
||||
<instructions>Fill in your YAHOO! Messenger username and password to register on this transport.</instructions>
|
||||
<server>scs.msg.yahoo.com</server>
|
||||
<port>5050</port>
|
||||
<!--
|
||||
The character map. This provides character set translation from UTF-8
|
||||
to the indicated character map. See the man page for 'iconv' for available
|
||||
character maps on your platform. CP1252 is the standard Windows character
|
||||
set.
|
||||
-->
|
||||
<charmap>CP1252</charmap>
|
||||
<!--
|
||||
When this element exists, the transport will send new mail notifications as
|
||||
well as a count of unread messages when the user initially logs in.
|
||||
-->
|
||||
<newmail/>
|
||||
</config>
|
||||
<!-- yahoo-transport-2 module. -->
|
||||
<load>
|
||||
<yahoo_transport>/usr/local/lib/jabber/yahoo-transport-2.so</yahoo_transport>
|
||||
</load>
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The settings below have to match the settings you made
|
||||
in your ejabberd.cfg configuration file.
|
||||
-->
|
||||
|
||||
<service id="yahoo-linker">
|
||||
<uplink/>
|
||||
<connect>
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>5236</port>
|
||||
<secret>SECRET</secret>
|
||||
</connect>
|
||||
</service>
|
||||
|
||||
<pidfile>/var/run/jabber/yahoo-transport-2.pid</pidfile>
|
||||
|
||||
</jabber>
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/bin/sh
|
||||
#########################################################
|
||||
#
|
||||
# aim-transport -- script to start aim-transport.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/jabberd-aim-transport
|
||||
CONF=/etc/jabber/aim-transport.xml
|
||||
NAME=jabberd-aim-transport
|
||||
HOME=/etc/jabber/
|
||||
USER=ejabberd
|
||||
|
||||
#########################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
debug)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in debugging mode."
|
||||
$DAEMON -D -H $HOME -c $CONF &
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON -H $HOME -c $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
;;
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/bin/sh
|
||||
#########################################################
|
||||
#
|
||||
# ile -- script to start ILE.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/ile.pl
|
||||
NAME=ile.pl
|
||||
CONF=/etc/jabber/ile.xml
|
||||
USER=ejabberd
|
||||
|
||||
#########################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
debug)
|
||||
echo "Not implemented yet. Starting in normal mode"
|
||||
$0 start
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
;;
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|status|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/bin/sh
|
||||
#########################################################
|
||||
#
|
||||
# jabber-gg-transport -- script to start jabber-gg-transport.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/jggtrans
|
||||
CONF=/etc/jabber/jabber-gg-transport.xml
|
||||
NAME=jggtrans
|
||||
HOME=/etc/jabber/
|
||||
USER=ejabberd
|
||||
|
||||
#########################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
debug)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in debugging mode."
|
||||
$DAEMON -D -H $HOME -c $CONF &
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
rm /var/run/jabber/jabber-gg-transport.pid
|
||||
;;
|
||||
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/bin/sh
|
||||
#########################################################
|
||||
#
|
||||
# jit -- script to start JIT.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/wpjabber-jit
|
||||
CONF=/etc/jabber/jit.xml
|
||||
NAME=wpjabber-jit
|
||||
HOME=/etc/jabber/
|
||||
USER=ejabberd
|
||||
|
||||
#########################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
debug)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in debugging mode."
|
||||
$DAEMON -D -H $HOME -c $CONF &
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON -H $HOME -c $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
;;
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/bin/sh
|
||||
#########################################################
|
||||
#
|
||||
# msn-transport -- script to start MSN Transport.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/jabberd-msn-transport
|
||||
CONF=/etc/jabber/msn-transport.xml
|
||||
NAME=jabberd-msn-transport
|
||||
HOME=/etc/jabber/
|
||||
USER=ejabberd
|
||||
|
||||
#########################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
strace)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in strace mode."
|
||||
strace -o /opt/ejabberd/var/log/jabber/strace.log $DAEMON -H $HOME -c $CONF &
|
||||
;;
|
||||
debug)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in debugging mode."
|
||||
$DAEMON -D -H $HOME -c $CONF &
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON -H $HOME -c $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
;;
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/bin/sh
|
||||
##############################################################
|
||||
#
|
||||
# yahoo-transport-2 -- script to start Yahoo-transport-2.
|
||||
#
|
||||
#############################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/jabberd-yahoo-transport-2
|
||||
CONF=/etc/jabber/yahoo-transport-2.xml
|
||||
NAME=jabberd-yahoo-transport-2
|
||||
HOME=/etc/jabber/
|
||||
USER=ejabberd
|
||||
|
||||
#############################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
debug)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in debugging mode."
|
||||
$DAEMON -D -H $HOME -c $CONF &
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON -H $HOME -c $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
;;
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
+100
-26
@@ -1,15 +1,11 @@
|
||||
# $Id$
|
||||
|
||||
CC = @CC@
|
||||
CFLAGS = @CFLAGS@
|
||||
CPPFLAGS = @CPPFLAGS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
LIBS = @LIBS@
|
||||
|
||||
EXPAT_CFLAGS = @EXPAT_CFLAGS@
|
||||
ERLANG_CFLAGS= @ERLANG_CFLAGS@
|
||||
|
||||
EXPAT_LIBS = @EXPAT_LIBS@
|
||||
ERLANG_LIBS = @ERLANG_LIBS@
|
||||
|
||||
ASN_FLAGS = -bber_bin +der +compact_bit_string +optimize +noobj
|
||||
@@ -31,14 +27,13 @@ else
|
||||
endif
|
||||
|
||||
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
|
||||
|
||||
DEBUGTOOLS = p1_prof.erl
|
||||
DEBUGTOOLS = p1_prof.erl etop_tr.erl
|
||||
ifdef debugtools
|
||||
SOURCES+=$(DEBUGTOOLS)
|
||||
endif
|
||||
@@ -55,11 +50,6 @@ 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
|
||||
@@ -82,9 +72,9 @@ endif
|
||||
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
|
||||
ERLBEHAVS = cyrsasl.erl gen_mod.erl p1_fsm.erl ejabberd_auth.erl
|
||||
SUBDIRS = stun @mod_pubsub@ @mod_muc@ @mod_proxy65@ @eldap@ @pam@ @web@ @tls@ @odbc@
|
||||
ERLSHLIBS =
|
||||
ERLBEHAVS = cyrsasl.erl gen_mod.erl p1_fsm.erl
|
||||
SOURCES_ALL = $(wildcard *.erl)
|
||||
SOURCES_MISC = $(ERLBEHAVS) $(DEBUGTOOLS)
|
||||
SOURCES += $(filter-out $(SOURCES_MISC),$(SOURCES_ALL))
|
||||
@@ -159,7 +149,7 @@ all-recursive: $(ERLBEHAVBEAMS)
|
||||
|
||||
|
||||
all-recursive install-recursive uninstall-recursive \
|
||||
clean-recursive distclean-recursive \
|
||||
clean-recursive distclean-recursive devdoc-recursive \
|
||||
mostlyclean-recursive maintainer-clean-recursive:
|
||||
@subdirs="$(SUBDIRS)"; for subdir in $$subdirs; do \
|
||||
target=`echo $@|sed 's,-recursive,,'`; \
|
||||
@@ -175,15 +165,13 @@ mostlyclean-recursive maintainer-clean-recursive:
|
||||
$(ERLSHLIBS): %.so: %.c
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(LIBS) \
|
||||
$(subst ../,,$(subst .so,.c,$@)) \
|
||||
$(EXPAT_LIBS) \
|
||||
$(EXPAT_CFLAGS) \
|
||||
$(ERLANG_LIBS) \
|
||||
$(ERLANG_CFLAGS) \
|
||||
-o $@ \
|
||||
$(DYNAMIC_LIB_CFLAGS)
|
||||
|
||||
translations:
|
||||
../contrib/extract_translations/prepare-translation.sh -updateall
|
||||
../tools/extract_translations/prepare-translation.sh -updateall
|
||||
|
||||
install: all
|
||||
#
|
||||
@@ -232,8 +220,6 @@ install: all
|
||||
install -m 644 mod_proxy65/*.hrl $(INCLUDEDIR)/mod_proxy65/
|
||||
install -d $(INCLUDEDIR)/mod_pubsub/
|
||||
install -m 644 mod_pubsub/*.hrl $(INCLUDEDIR)/mod_pubsub/
|
||||
install -d $(INCLUDEDIR)/mod_pubsub_ng/
|
||||
install -m 644 mod_pubsub_ng/*.hrl $(INCLUDEDIR)/mod_pubsub_ng/
|
||||
install -d $(INCLUDEDIR)/web/
|
||||
install -m 644 web/*.hrl $(INCLUDEDIR)/web/
|
||||
#
|
||||
@@ -268,9 +254,11 @@ install: all
|
||||
#
|
||||
# Documentation
|
||||
install -d $(DOCDIR)
|
||||
install -m 644 ../doc/dev.html $(DOCDIR)
|
||||
install -m 644 ../doc/guide.html $(DOCDIR)
|
||||
install -m 644 ../doc/*.png $(DOCDIR)
|
||||
[ -f ../doc/guide.html ] \
|
||||
&& install -m 644 ../doc/dev.html $(DOCDIR) \
|
||||
&& install -m 644 ../doc/guide.html $(DOCDIR) \
|
||||
&& install -m 644 ../doc/*.png $(DOCDIR) \
|
||||
|| echo "No ../doc/guide.html was built"
|
||||
install -m 644 ../doc/*.txt $(DOCDIR)
|
||||
[ -f ../doc/guide.pdf ] \
|
||||
&& install -m 644 ../doc/guide.pdf $(DOCDIR) \
|
||||
@@ -302,7 +290,7 @@ uninstall-all: uninstall-binary
|
||||
rm -rf $(CTLLOCKDIR)
|
||||
rm -rf $(LOGDIR)
|
||||
|
||||
clean: clean-recursive clean-local
|
||||
clean: clean-recursive clean-local clean-devdoc
|
||||
|
||||
clean-local:
|
||||
rm -f *.beam $(ERLSHLIBS) epam ejabberdctl.example
|
||||
@@ -312,7 +300,6 @@ distclean: distclean-recursive clean-local
|
||||
rm -f config.status
|
||||
rm -f config.log
|
||||
rm -f Makefile
|
||||
[ ! -f ../ChangeLog ] || rm -f ../ChangeLog
|
||||
|
||||
TAGS:
|
||||
etags *.erl
|
||||
@@ -321,3 +308,90 @@ Makefile: Makefile.in
|
||||
|
||||
dialyzer: $(BEAMS)
|
||||
@dialyzer -c .
|
||||
|
||||
## Devdoc definitions
|
||||
SRCDIR=.
|
||||
DDTDIR=.
|
||||
DEVDOCDIR=../doc/devdoc
|
||||
DEVDOC_ERLS = $(wildcard ../doc/devdoc/*.erl)
|
||||
DEVDOC_BEAMS = $(DEVDOC_ERLS:.erl=.beam)
|
||||
APPNAME = ejabberd
|
||||
VSN = $(shell sed '/vsn/!d;s/\(.*\)"\(.*\)"\(.*\)/\2/' ./ejabberd.app)
|
||||
.PHONY = all
|
||||
HTMLS = $(SOURCES:%.erl=../doc/devdoc/%.html)
|
||||
ERLHTMLS = $(SOURCES:%.erl=../doc/devdoc/%.erl.html)
|
||||
SVGS = $(SOURCES:%.erl=../doc/devdoc/%.svg)
|
||||
EDOCINDEX = $(DEVDOCDIR)/index.html
|
||||
|
||||
## Devdoc rules
|
||||
devdoc: compile-devdoc devdoc-root devdoc-recursive
|
||||
$(devdoc-customize)
|
||||
$(devdoc-move)
|
||||
devdoc-root: $(EDOCINDEX) $(HTMLS) $(ERLHTMLS) $(SVGS)
|
||||
|
||||
define devdoc-customize
|
||||
find $(DDTDIR) -type f -name '*.erl.html' -exec sed -i 's/<span class="attribute" >module<\/span>(\([A-Za-z0-9_]*\))/<span class="attribute" >module<\/span>(<a href="\1.html">\1<\/a>)/g;' {} \;
|
||||
find $(DDTDIR) -type f -name '*.erl.html' -exec sed -i 's/arity="\([0-9]*\)" >\([A-Za-z0-9_]*\)</><a class="function" id="\2-\1" href="EDOCFILENAME#\2-\1">\2<\/a></g;' {} \;
|
||||
find $(DDTDIR) -type f -name '*.erl.html' -exec sed -i 's/class="export" >\([A-Za-z0-9_]*\)\/\([0-9]*\)</class="export" ><a href="#\1-\2">\1\/\2<\/a></g;' {} \;
|
||||
for fn in *.erl.html; do \
|
||||
sed -i 's/EDOCFILENAME/'$${fn%.erl.html}.html'/g;' $${fn} ; \
|
||||
sed = $${fn} | sed 'N;s/\n/ /;s/^\([0-9_]*\)/<a href="#\1" name="\1" class="l">\1<\/a>/' >$${fn}.tmp ; \
|
||||
mv $${fn}.tmp $${fn} ; \
|
||||
sed -i 's/<a href="#1" name="1" class="l">1<\/a> <html><link rel="stylesheet" type="text\/css"href="escobar.css"><\/link><body><pre><span class="comment" >/<html><link rel="stylesheet" type="text\/css" href="escobar.css"><\/link><body><pre><span class="comment" ><a href="#1" name="1" class="l">1<\/a> /g;' $${fn} ; \
|
||||
done
|
||||
-mv *.erl.html $(DEVDOCDIR)
|
||||
|
||||
find $(DDTDIR) -type f -name '*.html' -exec sed -i 's/<a href="overview-summary.html" target="overviewFrame">/<a href="index.html" target="_parent">/g;' {} \;
|
||||
find $(DDTDIR) -type f -name '*.html' -exec sed -i 's/align=\"right\" border=\"0\" alt=\"erlang logo\"/alt=\"erlang logo\"><\/a><a href=\"http:\/\/www.ejabberd.im\/\"><img src=\"ejabberd-im.png\" alt=\"ejabberd Community\"><\/a><a href=\"http:\/\/www.process-one.net\/en\/ejabberd\/\"><img src=\"ejabberd-p1.png\" alt=\"ejabberd home\"><\/a><a href=\"http:\/\/www.process-one.net\/\"><img src=\"process-one.png\" alt=\"ProcessOne\"/g;' {} \;
|
||||
find $(DDTDIR) -type f -name '*.html' -exec sed -i 's/^<h1>Module \([A-Za-z0-9_]*\)<\/h1>/<h1>Module \1 [<a href="\1.erl.html">erl<\/a> <a href="\1.svg">svg<\/a>]<\/h1>/g;' {} \;
|
||||
find $(DDTDIR) -type f -name '*.html' -exec sed -i 's/class="function"><a name="\([A-Za-z0-9_]*\)-\([0-9_]*\)">/class="function"><a name="\1-\2" href="ESCOFILENAME#\1-\2">/g;' {} \;
|
||||
-for fn in *.html; do sed -i 's/ESCOFILENAME/'$${fn%.html}.erl.html'/g;' $${fn} ; done
|
||||
|
||||
find $(DDTDIR) -type f -name '*.svg' -exec sed -i 's/xlink:href="EXP\([A-Za-z0-9_]*\).html#\([A-Za-z0-9_]*\)\/\([0-9_]*\)"/xlink:href="\1.html#\2-\3"/g;' {} \;
|
||||
find $(DDTDIR) -type f -name '*.svg' -exec sed -i 's/xlink:href="PRI\([A-Za-z0-9_]*\).html#\([A-Za-z0-9_]*\)\/\([0-9_]*\)"/xlink:href="\1.html#\2-\3"/g;' {} \;
|
||||
find $(DDTDIR) -type f -name '*.svg' -exec sed -i 's/xlink:href="APP\([A-Za-z0-9_]*\):\([A-Za-z0-9_]*\)\/\([0-9_]*\)"/xlink:href="\1.html#\2-\3"/g;' {} \;
|
||||
find $(DDTDIR) -type f -name '*.svg' -exec sed -i 's/xlink:href="EXM\([A-Za-z0-9_]*\):\([A-Za-z0-9_]*\)\/\([0-9_]*\)"/xlink:href="http:\/\/www.process-one.net\/docs\/exmpp\/devdoc\/trunk\/\1.html#\2-\3"/g;' {} \;
|
||||
find $(DDTDIR) -type f -name '*.svg' -exec sed -i 's/xlink:href="OTP\([A-Za-z0-9_]*\):\([A-Za-z0-9_]*\)\/\([0-9_]*\)"/xlink:href="http:\/\/www.erlang.org\/doc\/man\/\1.html#\2-\3"/g;' {} \;
|
||||
find $(DDTDIR) -type f -name '*.svg' -exec sed -i 's/xlink:href="OTP\([A-Za-z0-9_]*\)\/\([0-9_]*\)"/xlink:href="http:\/\/www.erlang.org\/doc\/man\/erlang.html#\1-\2"/g;' {} \;
|
||||
endef
|
||||
|
||||
define devdoc-move
|
||||
-rm *.dot
|
||||
-mv *.html $(DEVDOCDIR)
|
||||
-mv *.svg $(DEVDOCDIR)
|
||||
endef
|
||||
|
||||
compile-devdoc: $(DEVDOC_BEAMS)
|
||||
[ ! -f funrelg.beam ] || mv funrelg.beam $(DEVDOCDIR)
|
||||
[ ! -f escobar_hilite.beam ] || mv escobar_hilite.beam $(DEVDOCDIR)
|
||||
[ ! -f escobar_run.beam ] || mv escobar_run.beam $(DEVDOCDIR)
|
||||
|
||||
$(EDOCINDEX):
|
||||
@ERL@ -noshell -run edoc_run application "'$(APPNAME)'" '"$(SRCDIR)"' \
|
||||
'[{dir,"$(DDTDIR)"},{packages,false},{todo,true},{private,true},{def,{vsn,"$(VSN)"}},{stylesheet,"process-one.css"},{overview,"overview.edoc"}]' -s init stop
|
||||
sed -i 's/<title>The ejabberd application<\/title>/<title>ejabberd devdoc<\/title><link rel="shortcut icon" href="favicon.ico" type="image\/x-icon"\/>/g;' $(DDTDIR)/index.html
|
||||
mv edoc-info $(DEVDOCDIR)
|
||||
mv *.png $(DEVDOCDIR)
|
||||
cp *.html $(DEVDOCDIR)
|
||||
@ERL@ -noshell -pa $(DEVDOCDIR) -run escobar_run dir $(SRCDIR) $(SRCDIR) -s init stop
|
||||
@ERL@ -noshell -pa $(DEVDOCDIR) -run funrelg dir $(SRCDIR) $(SRCDIR) -s init stop
|
||||
$(devdoc-customize)
|
||||
$(devdoc-move)
|
||||
|
||||
$(DEVDOCDIR)/%.erl.html: %.erl
|
||||
@ERL@ -noshell -pa $(DEVDOCDIR) -run escobar_run file $< $(SRCDIR) -s init stop
|
||||
|
||||
$(DEVDOCDIR)/%.html: %.erl
|
||||
@ERL@ -noshell -run edoc_run file $< \
|
||||
'[{dir,"$(DDTDIR)"},{packages,false},{todo,true},{private,true},{def,{vsn,"$(VSN)"}},{stylesheet,"process-one.css"},{overview,"overview.edoc"}]' -s init stop
|
||||
|
||||
$(DEVDOCDIR)/%.svg: %.erl
|
||||
@ERL@ -noshell -pa $(DEVDOCDIR) -run funrelg file $< $(SRCDIR) -s init stop
|
||||
|
||||
clean-devdoc:
|
||||
rm -f $(DEVDOCDIR)/edoc-info
|
||||
rm -f $(DEVDOCDIR)/erlang.png
|
||||
rm -f $(DEVDOCDIR)/*.beam
|
||||
rm -f $(DEVDOCDIR)/*.dot
|
||||
rm -f $(DEVDOCDIR)/*.html
|
||||
rm -f $(DEVDOCDIR)/*.svg
|
||||
|
||||
+7
-29
@@ -52,15 +52,10 @@ release : build release_clean
|
||||
mkdir $(SRC_DIR)\eldap
|
||||
copy eldap\eldap.* $(SRC_DIR)\eldap
|
||||
copy eldap\ELDAPv3.asn $(SRC_DIR)\eldap
|
||||
mkdir $(SRC_DIR)\mod_irc
|
||||
copy mod_irc\*.erl $(SRC_DIR)\mod_irc
|
||||
copy mod_irc\*.c $(SRC_DIR)\mod_irc
|
||||
mkdir $(SRC_DIR)\mod_muc
|
||||
copy mod_muc\*.erl $(SRC_DIR)\mod_muc
|
||||
mkdir $(SRC_DIR)\mod_pubsub
|
||||
copy mod_pubsub\*.erl $(SRC_DIR)\mod_pubsub
|
||||
mkdir $(SRC_DIR)\mod_pubsub_ng
|
||||
copy mod_pubsub_ng\*.erl $(SRC_DIR)\mod_pubsub_ng
|
||||
mkdir $(SRC_DIR)\mod_proxy65
|
||||
copy mod_proxy65\*.erl $(SRC_DIR)\mod_proxy65
|
||||
copy mod_proxy65\*.hrl $(SRC_DIR)\mod_proxy65
|
||||
@@ -74,9 +69,6 @@ release : build release_clean
|
||||
mkdir $(SRC_DIR)\tls
|
||||
copy tls\*.erl $(SRC_DIR)\tls
|
||||
copy tls\*.c $(SRC_DIR)\tls
|
||||
mkdir $(SRC_DIR)\ejabberd_zlib
|
||||
copy ejabberd_zlib\*.erl $(SRC_DIR)\ejabberd_zlib
|
||||
copy ejabberd_zlib\*.c $(SRC_DIR)\ejabberd_zlib
|
||||
mkdir $(SRC_DIR)\web
|
||||
copy web\*.erl $(SRC_DIR)\web
|
||||
mkdir $(SRC_DIR)\odbc
|
||||
@@ -87,23 +79,19 @@ release : build release_clean
|
||||
copy ..\doc\*.html $(DOC_DIR)
|
||||
copy ..\doc\*.png $(DOC_DIR)
|
||||
|
||||
SOURCE = expat_erl.c
|
||||
OBJECT = expat_erl.o
|
||||
DLL = expat_erl.dll
|
||||
SOURCE =
|
||||
OBJECT =
|
||||
DLL =
|
||||
|
||||
build : $(DLL) compile-beam all-recursive
|
||||
|
||||
all-recursive :
|
||||
cd eldap
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\mod_irc
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\mod_muc
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\mod_pubsub
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\mod_pubsub_ng
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\mod_proxy65
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\stringprep
|
||||
@@ -112,8 +100,6 @@ all-recursive :
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\tls
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\ejabberd_zlib
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\web
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\odbc
|
||||
@@ -131,8 +117,6 @@ CLEAN : clean-recursive clean-local
|
||||
clean-local :
|
||||
-@erase $(OBJECT)
|
||||
-@erase $(DLL)
|
||||
-@erase expat_erl.exp
|
||||
-@erase expat_erl.lib
|
||||
-@erase *.beam
|
||||
-@erase XmppAddr.asn1db
|
||||
-@erase XmppAddr.erl
|
||||
@@ -141,14 +125,10 @@ clean-local :
|
||||
clean-recursive :
|
||||
cd eldap
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\mod_irc
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\mod_muc
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\mod_pubsub
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\mod_pubsub_ng
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\mod_proxy65
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\stringprep
|
||||
@@ -157,8 +137,6 @@ clean-recursive :
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\tls
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\ejabberd_zlib
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\web
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\odbc
|
||||
@@ -170,13 +148,13 @@ distclean : release_clean clean
|
||||
-@erase Makefile.inc
|
||||
|
||||
CC=cl.exe
|
||||
CC_FLAGS=-nologo -D__WIN32__ -DWIN32 -DWINDOWS -D_WIN32 -DNT $(EXPAT_FLAG) -MD -Ox -I"$(ERLANG_DIR)\usr\include" -I"$(EI_DIR)\include" -I"$(EXPAT_DIR)\source\lib"
|
||||
CC_FLAGS=-nologo -D__WIN32__ -DWIN32 -DWINDOWS -D_WIN32 -DNT -MD -Ox -I"$(ERLANG_DIR)\usr\include" -I"$(EI_DIR)\include"
|
||||
|
||||
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
|
||||
LD_FLAGS=-release -nologo -incremental:no -dll "$(EI_DIR)\lib\ei_md.lib" "$(EI_DIR)\lib\erl_interface_md.lib" MSVCRT.LIB kernel32.lib advapi32.lib gdi32.lib user32.lib comctl32.lib comdlg32.lib shell32.lib
|
||||
|
||||
$(DLL) : $(OBJECT)
|
||||
$(LD) $(LD_FLAGS) -out:$@ $<
|
||||
$(LD) $(LD_FLAGS) -out:$(DLL) $(OBJECT)
|
||||
|
||||
$(OBJECT) : $(SOURCE)
|
||||
$(CC) $(CC_FLAGS) -c -Fo$@ $<
|
||||
$(CC) $(CC_FLAGS) -c -Fo$(OBJECT) $(SOURCE)
|
||||
|
||||
+10
-161
@@ -1,73 +1,3 @@
|
||||
AC_DEFUN([AM_WITH_EXPAT],
|
||||
[ AC_ARG_WITH(expat,
|
||||
[AC_HELP_STRING([--with-expat=PREFIX], [prefix where EXPAT is installed])])
|
||||
|
||||
EXPAT_CFLAGS=
|
||||
EXPAT_LIBS=
|
||||
if test x"$with_expat" != x; then
|
||||
EXPAT_CFLAGS="-I$with_expat/include"
|
||||
EXPAT_LIBS="-L$with_expat/lib"
|
||||
fi
|
||||
|
||||
AC_CHECK_LIB(expat, XML_ParserCreate,
|
||||
[ EXPAT_LIBS="$EXPAT_LIBS -lexpat"
|
||||
expat_found=yes ],
|
||||
[ expat_found=no ],
|
||||
"$EXPAT_LIBS")
|
||||
if test $expat_found = no; then
|
||||
AC_MSG_ERROR([Could not find development files of Expat library])
|
||||
fi
|
||||
expat_save_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS $EXPAT_CFLAGS"
|
||||
expat_save_CPPFLAGS="$CPPFLAGS"
|
||||
CPPFLAGS="$CPPFLAGS $EXPAT_CFLAGS"
|
||||
AC_CHECK_HEADERS(expat.h, , expat_found=no)
|
||||
if test $expat_found = no; then
|
||||
AC_MSG_ERROR([Could not find expat.h])
|
||||
fi
|
||||
CFLAGS="$expat_save_CFLAGS"
|
||||
CPPFLAGS="$expat_save_CPPFLAGS"
|
||||
|
||||
AC_SUBST(EXPAT_CFLAGS)
|
||||
AC_SUBST(EXPAT_LIBS)
|
||||
])
|
||||
|
||||
AC_DEFUN([AM_WITH_ZLIB],
|
||||
[ AC_ARG_WITH(zlib,
|
||||
[AC_HELP_STRING([--with-zlib=PREFIX], [prefix where zlib is installed])])
|
||||
|
||||
if test x"$ejabberd_zlib" != x; then
|
||||
ZLIB_CFLAGS=
|
||||
ZLIB_LIBS=
|
||||
if test x"$with_zlib" != x; then
|
||||
ZLIB_CFLAGS="-I$with_zlib/include"
|
||||
ZLIB_LIBS="-L$with_zlib/lib"
|
||||
fi
|
||||
|
||||
AC_CHECK_LIB(z, gzgets,
|
||||
[ ZLIB_LIBS="$ZLIB_LIBS -lz"
|
||||
zlib_found=yes ],
|
||||
[ zlib_found=no ],
|
||||
"$ZLIB_LIBS")
|
||||
if test $zlib_found = no; then
|
||||
AC_MSG_ERROR([Could not find development files of zlib library. Install them or disable `ejabberd_zlib' with: --disable-ejabberd_zlib])
|
||||
fi
|
||||
zlib_save_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS $ZLIB_CFLAGS"
|
||||
zlib_save_CPPFLAGS="$CFLAGS"
|
||||
CPPFLAGS="$CPPFLAGS $ZLIB_CFLAGS"
|
||||
AC_CHECK_HEADERS(zlib.h, , zlib_found=no)
|
||||
if test $zlib_found = no; then
|
||||
AC_MSG_ERROR([Could not find zlib.h. Install it or disable `ejabberd_zlib' with: --disable-ejabberd_zlib])
|
||||
fi
|
||||
CFLAGS="$zlib_save_CFLAGS"
|
||||
CPPFLAGS="$zlib_save_CPPFLAGS"
|
||||
|
||||
AC_SUBST(ZLIB_CFLAGS)
|
||||
AC_SUBST(ZLIB_LIBS)
|
||||
fi
|
||||
])
|
||||
|
||||
AC_DEFUN([AM_WITH_PAM],
|
||||
[ AC_ARG_WITH(pam,
|
||||
[AC_HELP_STRING([--with-pam=PREFIX], [prefix where PAM is installed])])
|
||||
@@ -125,8 +55,14 @@ AC_DEFUN([AM_WITH_ERLANG],
|
||||
start() ->
|
||||
EIDirS = code:lib_dir("erl_interface") ++ "\n",
|
||||
EILibS = libpath("erl_interface") ++ "\n",
|
||||
EXMPPDir = code:lib_dir("exmpp"),
|
||||
case EXMPPDir of
|
||||
{error, bad_name} -> exit("exmpp not found");
|
||||
_ -> ok
|
||||
end,
|
||||
EXMPPDirS = EXMPPDir ++ "\n",
|
||||
RootDirS = code:root_dir() ++ "\n",
|
||||
file:write_file("conftest.out", list_to_binary(EIDirS ++ EILibS ++ ssldef() ++ RootDirS)),
|
||||
file:write_file("conftest.out", list_to_binary(EIDirS ++ EILibS ++ ssldef() ++ EXMPPDirS ++ RootDirS)),
|
||||
halt().
|
||||
|
||||
ssldef() ->
|
||||
@@ -186,6 +122,8 @@ _EOF
|
||||
ERLANG_EI_LIB=`cat conftest.out | head -n 2 | tail -n 1`
|
||||
# Third line
|
||||
ERLANG_SSLVER=`cat conftest.out | head -n 3 | tail -n 1`
|
||||
# Fourth line
|
||||
ERLANG_EXMPP=`cat conftest.out | head -n 4 | tail -n 1`
|
||||
# End line
|
||||
ERLANG_DIR=`cat conftest.out | tail -n 1`
|
||||
|
||||
@@ -195,6 +133,7 @@ _EOF
|
||||
AC_SUBST(ERLANG_CFLAGS)
|
||||
AC_SUBST(ERLANG_LIBS)
|
||||
AC_SUBST(ERLANG_SSLVER)
|
||||
AC_SUBST(ERLANG_EXMPP)
|
||||
AC_SUBST(ERLC)
|
||||
AC_SUBST(ERL)
|
||||
])
|
||||
@@ -218,96 +157,6 @@ AC_SUBST(make_$1)
|
||||
|
||||
])
|
||||
|
||||
|
||||
dnl From Bruno Haible.
|
||||
|
||||
AC_DEFUN([AM_ICONV],
|
||||
[
|
||||
dnl Some systems have iconv in libc, some have it in libiconv (OSF/1 and
|
||||
dnl those with the standalone portable GNU libiconv installed).
|
||||
AC_ARG_WITH([libiconv-prefix],
|
||||
[AC_HELP_STRING([--with-libiconv-prefix=PREFIX], [prefix where libiconv is installed])], [
|
||||
for dir in `echo "$withval" | tr : ' '`; do
|
||||
if test -d $dir/include; then CPPFLAGS="$CPPFLAGS -I$dir/include"; fi
|
||||
if test -d $dir/include; then CFLAGS="$CFLAGS -I$dir/include"; fi
|
||||
if test -d $dir/lib; then LDFLAGS="$LDFLAGS -L$dir/lib"; fi
|
||||
done
|
||||
])
|
||||
|
||||
AC_CACHE_CHECK(for iconv, am_cv_func_iconv, [
|
||||
am_cv_func_iconv="no, consider installing GNU libiconv"
|
||||
am_cv_lib_iconv=no
|
||||
AC_TRY_LINK([#include <stdlib.h>
|
||||
#include <iconv.h>],
|
||||
[iconv_t cd = iconv_open("","");
|
||||
iconv(cd,NULL,NULL,NULL,NULL);
|
||||
iconv_close(cd);],
|
||||
am_cv_func_iconv=yes)
|
||||
if test "$am_cv_func_iconv" != yes; then
|
||||
am_save_LIBS="$LIBS"
|
||||
LIBS="$LIBS -liconv"
|
||||
AC_TRY_LINK([#include <stdlib.h>
|
||||
#include <iconv.h>],
|
||||
[iconv_t cd = iconv_open("","");
|
||||
iconv(cd,NULL,NULL,NULL,NULL);
|
||||
iconv_close(cd);],
|
||||
am_cv_lib_iconv=yes
|
||||
am_cv_func_iconv=yes)
|
||||
LIBS="$am_save_LIBS"
|
||||
fi
|
||||
dnl trying /usr/local
|
||||
if test "$am_cv_func_iconv" != yes; then
|
||||
am_save_LIBS="$LIBS"
|
||||
am_save_CFLAGS="$CFLAGS"
|
||||
am_save_LDFLAGS="$LDFLAGS"
|
||||
LIBS="$LIBS -liconv"
|
||||
LDFLAGS="$LDFLAGS -L/usr/local/lib"
|
||||
CFLAGS="$CFLAGS -I/usr/local/include"
|
||||
AC_TRY_LINK([#include <stdlib.h>
|
||||
#include <iconv.h>],
|
||||
[iconv_t cd = iconv_open("","");
|
||||
iconv(cd,NULL,NULL,NULL,NULL);
|
||||
iconv_close(cd);],
|
||||
am_cv_lib_iconv=yes
|
||||
am_cv_func_iconv=yes
|
||||
CPPFLAGS="$CPPFLAGS -I/usr/local/include",
|
||||
LDFLAGS="$am_save_LDFLAGS"
|
||||
CFLAGS="$am_save_CFLAGS")
|
||||
LIBS="$am_save_LIBS"
|
||||
fi
|
||||
|
||||
])
|
||||
if test "$am_cv_func_iconv" = yes; then
|
||||
AC_DEFINE(HAVE_ICONV, 1, [Define if you have the iconv() function.])
|
||||
AC_MSG_CHECKING([for iconv declaration])
|
||||
AC_CACHE_VAL(am_cv_proto_iconv, [
|
||||
AC_TRY_COMPILE([
|
||||
#include <stdlib.h>
|
||||
#include <iconv.h>
|
||||
extern
|
||||
#ifdef __cplusplus
|
||||
"C"
|
||||
#endif
|
||||
#if defined(__STDC__) || defined(__cplusplus)
|
||||
size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);
|
||||
#else
|
||||
size_t iconv();
|
||||
#endif
|
||||
], [], am_cv_proto_iconv_arg1="", am_cv_proto_iconv_arg1="const")
|
||||
am_cv_proto_iconv="extern size_t iconv (iconv_t cd, $am_cv_proto_iconv_arg1 char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);"])
|
||||
am_cv_proto_iconv=`echo "[$]am_cv_proto_iconv" | tr -s ' ' | sed -e 's/( /(/'`
|
||||
AC_MSG_RESULT([$]{ac_t:-
|
||||
}[$]am_cv_proto_iconv)
|
||||
AC_DEFINE_UNQUOTED(ICONV_CONST, $am_cv_proto_iconv_arg1,
|
||||
[Define as const if the declaration of iconv() needs const.])
|
||||
fi
|
||||
LIBICONV=
|
||||
if test "$am_cv_lib_iconv" = yes; then
|
||||
LIBICONV="-liconv"
|
||||
fi
|
||||
AC_SUBST(LIBICONV)
|
||||
])
|
||||
|
||||
dnl <openssl>
|
||||
AC_DEFUN([AM_WITH_OPENSSL],
|
||||
[ AC_ARG_WITH(openssl,
|
||||
|
||||
+262
-211
@@ -5,7 +5,7 @@
|
||||
%%% Created : 18 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -25,264 +25,315 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(acl).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0, to_record/3, add/3, add_list/3,
|
||||
match_rule/3, match_acl/3]).
|
||||
-export([start/0,
|
||||
to_record/3,
|
||||
add/3,
|
||||
add_list/3,
|
||||
match_rule/3,
|
||||
for_host/1,
|
||||
% for debugging only
|
||||
match_acl/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
%% @type aclspec() = all | JID_Exact | JID_Regexp | JID_Glob | Shared_Group
|
||||
%% JID_Exact = {user, U} | {user, U, S} | {server, S} | {resource, R}
|
||||
%% U = string()
|
||||
%% S = string()
|
||||
%% R = string()
|
||||
%% JID_Regexp = {user_regexp, UR} | {user_regexp, UR, S} | {server_regexp, SR} | {resource_regexp, RR} | {node_regexp, UR, SR}
|
||||
%% UR = string()
|
||||
%% SR = string()
|
||||
%% RR = string()
|
||||
%% JID_Glob = {user_glob, UG} | {user_glob, UG, S} | {server_glob, SG} | {resource_glob, RG} | {node_glob, UG, SG}
|
||||
%% UG = string()
|
||||
%% SG = string()
|
||||
%% RG = string()
|
||||
%% Shared_Group = {shared_group, G} | {shared_group, G, H}
|
||||
%% G = string()
|
||||
%% H = string().
|
||||
|
||||
%% @type acl() = {acl, ACLName, ACLSpec}
|
||||
%% ACLName = atom()
|
||||
%% ACLSpec = aclspec().
|
||||
%% Record in its Ejabberd-configuration-file variant.
|
||||
|
||||
%% @type storedacl() = {acl, {ACLName, Host}, ACLSpec}
|
||||
%% ACLName = atom()
|
||||
%% Host = global | string()
|
||||
%% ACLSpec = aclspec().
|
||||
%% Record in its Mnesia-table-record variant.
|
||||
|
||||
-record(acl, {aclname, aclspec}).
|
||||
|
||||
-type regexp() :: binary().
|
||||
-type glob() :: binary().
|
||||
-type aclname() :: {atom(), binary() | global}.
|
||||
-type aclspec() :: all | none |
|
||||
{user, binary()} |
|
||||
{user, binary(), binary()} |
|
||||
{server, binary()} |
|
||||
{resource, binary()} |
|
||||
{user_regexp, regexp()} |
|
||||
{shared_group, binary()} |
|
||||
{shared_group, binary(), binary()} |
|
||||
{user_regexp, regexp(), binary()} |
|
||||
{server_regexp, regexp()} |
|
||||
{resource_regexp, regexp()} |
|
||||
{node_regexp, regexp(), regexp()} |
|
||||
{user_glob, glob()} |
|
||||
{user_glob, glob(), binary()} |
|
||||
{server_glob, glob()} |
|
||||
{resource_glob, glob()} |
|
||||
{node_glob, glob(), glob()}.
|
||||
|
||||
-type acl() :: #acl{aclname :: aclname(),
|
||||
aclspec :: aclspec()}.
|
||||
|
||||
-export_type([acl/0]).
|
||||
%% @spec () -> ok
|
||||
|
||||
start() ->
|
||||
mnesia:create_table(acl,
|
||||
[{disc_copies, [node()]}, {type, bag},
|
||||
[{disc_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, acl)}]),
|
||||
mnesia:add_table_copy(acl, node(), ram_copies),
|
||||
update_table(),
|
||||
ok.
|
||||
|
||||
-spec to_record(binary(), atom(), aclspec()) -> acl().
|
||||
%% @spec (Host, ACLName, ACLSpec) -> storedacl()
|
||||
%% Host = global | string()
|
||||
%% ACLName = atom()
|
||||
%% ACLSpec = aclspec()
|
||||
|
||||
to_record(Host, ACLName, ACLSpec) ->
|
||||
#acl{aclname = {ACLName, Host},
|
||||
aclspec = normalize_spec(ACLSpec)}.
|
||||
#acl{aclname = {ACLName, Host}, aclspec = normalize_spec(ACLSpec)}.
|
||||
|
||||
-spec add(binary(), aclname(), aclspec()) -> {atomic, ok} | {aborted, any()}.
|
||||
%% @spec (Host, ACLName, ACLSpec) -> {atomic, ok} | {aborted, Reason}
|
||||
%% Host = global | string()
|
||||
%% ACLName = atom()
|
||||
%% ACLSpec = all | none | aclspec()
|
||||
%% Reason = term()
|
||||
|
||||
add(Host, ACLName, ACLSpec) ->
|
||||
F = fun () ->
|
||||
F = fun() ->
|
||||
mnesia:write(#acl{aclname = {ACLName, Host},
|
||||
aclspec = normalize_spec(ACLSpec)})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
-spec add_list(binary(), [acl()], boolean()) -> false | ok.
|
||||
%% @spec (Host, ACLs, Clear) -> ok | false
|
||||
%% Host = global | string()
|
||||
%% ACLs = [acl()]
|
||||
%% Clear = bool()
|
||||
|
||||
add_list(Host, ACLs, Clear) ->
|
||||
F = fun () ->
|
||||
if Clear ->
|
||||
Ks = mnesia:select(acl,
|
||||
[{{acl, {'$1', Host}, '$2'}, [],
|
||||
['$1']}]),
|
||||
lists:foreach(fun (K) -> mnesia:delete({acl, {K, Host}})
|
||||
end,
|
||||
Ks);
|
||||
true -> ok
|
||||
F = fun() ->
|
||||
if
|
||||
Clear ->
|
||||
Ks = mnesia:select(
|
||||
acl, [{{acl, {'$1', Host}, '$2'}, [], ['$1']}]),
|
||||
lists:foreach(fun(K) ->
|
||||
mnesia:delete({acl, {K, Host}})
|
||||
end, Ks);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
lists:foreach(fun (ACL) ->
|
||||
lists:foreach(fun(ACL) ->
|
||||
case ACL of
|
||||
#acl{aclname = ACLName,
|
||||
aclspec = ACLSpec} ->
|
||||
mnesia:write(#acl{aclname =
|
||||
{ACLName,
|
||||
Host},
|
||||
aclspec =
|
||||
normalize_spec(ACLSpec)})
|
||||
#acl{aclname = ACLName,
|
||||
aclspec = ACLSpec} ->
|
||||
mnesia:write(
|
||||
#acl{aclname = {ACLName, Host},
|
||||
aclspec = normalize_spec(ACLSpec)})
|
||||
end
|
||||
end,
|
||||
ACLs)
|
||||
end, ACLs)
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, _} -> ok;
|
||||
_ -> false
|
||||
{atomic, _} ->
|
||||
ok;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
normalize(A) -> jlib:nodeprep(iolist_to_binary(A)).
|
||||
%% @spec (String) -> Prepd_String
|
||||
%% String = string()
|
||||
%% Prepd_String = string()
|
||||
|
||||
normalize_spec({A, B}) -> {A, normalize(B)};
|
||||
normalize(String) ->
|
||||
exmpp_stringprep:nodeprep(String).
|
||||
|
||||
%% @spec (ACLSpec) -> Normalized_ACLSpec
|
||||
%% ACLSpec = all | none | aclspec()
|
||||
%% Normalized_ACLSpec = aclspec()
|
||||
|
||||
normalize_spec({A, B}) ->
|
||||
{A, normalize(B)};
|
||||
normalize_spec({A, B, C}) ->
|
||||
{A, normalize(B), normalize(C)};
|
||||
normalize_spec(all) -> all;
|
||||
normalize_spec(none) -> none.
|
||||
normalize_spec(all) ->
|
||||
all;
|
||||
normalize_spec(none) ->
|
||||
none.
|
||||
|
||||
-spec match_rule(global | binary(), atom(), jid() | ljid()) -> any().
|
||||
|
||||
|
||||
%% @spec (Host, Rule, JID) -> Access
|
||||
%% Host = global | string()
|
||||
%% Rule = all | none | atom()
|
||||
%% JID = exmpp_jid:jid()
|
||||
%% Access = allow | deny | atom()
|
||||
|
||||
match_rule(global, Rule, JID) ->
|
||||
case Rule of
|
||||
all -> allow;
|
||||
none -> deny;
|
||||
_ ->
|
||||
case ejabberd_config:get_global_option(
|
||||
{access, Rule, global}, fun(V) -> V end)
|
||||
of
|
||||
undefined -> deny;
|
||||
GACLs -> match_acls(GACLs, JID, global)
|
||||
end
|
||||
all -> allow;
|
||||
none -> deny;
|
||||
_ ->
|
||||
case ejabberd_config:get_global_option({access, Rule, global}) of
|
||||
undefined ->
|
||||
deny;
|
||||
GACLs ->
|
||||
match_acls(GACLs, JID, global)
|
||||
end
|
||||
end;
|
||||
|
||||
match_rule(Host, Rule, JID) ->
|
||||
case Rule of
|
||||
all -> allow;
|
||||
none -> deny;
|
||||
_ ->
|
||||
case ejabberd_config:get_global_option(
|
||||
{access, Rule, global}, fun(V) -> V end)
|
||||
of
|
||||
undefined ->
|
||||
case ejabberd_config:get_global_option(
|
||||
{access, Rule, Host}, fun(V) -> V end)
|
||||
of
|
||||
undefined -> deny;
|
||||
ACLs -> match_acls(ACLs, JID, Host)
|
||||
end;
|
||||
GACLs ->
|
||||
case ejabberd_config:get_global_option(
|
||||
{access, Rule, Host}, fun(V) -> V end)
|
||||
of
|
||||
undefined -> match_acls(GACLs, JID, Host);
|
||||
ACLs ->
|
||||
case lists:reverse(GACLs) of
|
||||
[{allow, all} | Rest] ->
|
||||
match_acls(lists:reverse(Rest) ++
|
||||
ACLs ++ [{allow, all}],
|
||||
JID, Host);
|
||||
_ -> match_acls(GACLs ++ ACLs, JID, Host)
|
||||
end
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
match_acls([], _, _Host) -> deny;
|
||||
match_acls([{Access, ACL} | ACLs], JID, Host) ->
|
||||
case match_acl(ACL, JID, Host) of
|
||||
true -> Access;
|
||||
_ -> match_acls(ACLs, JID, Host)
|
||||
end.
|
||||
|
||||
-spec match_acl(atom(), jid() | ljid(), binary()) -> boolean().
|
||||
|
||||
match_acl(ACL, JID, Host) ->
|
||||
case ACL of
|
||||
all -> true;
|
||||
none -> false;
|
||||
_ ->
|
||||
{User, Server, Resource} = jlib:jid_tolower(JID),
|
||||
lists:any(fun (#acl{aclspec = Spec}) ->
|
||||
case Spec of
|
||||
all -> true;
|
||||
{user, U} ->
|
||||
U == User andalso
|
||||
(Host == Server orelse
|
||||
Host == global andalso
|
||||
lists:member(Server, ?MYHOSTS));
|
||||
{user, U, S} -> U == User andalso S == Server;
|
||||
{server, S} -> S == Server;
|
||||
{resource, R} -> R == Resource;
|
||||
{user_regexp, UR} ->
|
||||
(Host == Server orelse
|
||||
Host == global andalso
|
||||
lists:member(Server, ?MYHOSTS))
|
||||
andalso is_regexp_match(User, UR);
|
||||
{shared_group, G} ->
|
||||
Mod = loaded_shared_roster_module(Host),
|
||||
Mod:is_user_in_group({User, Server}, G, Host);
|
||||
{shared_group, 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);
|
||||
{server_regexp, SR} ->
|
||||
is_regexp_match(Server, SR);
|
||||
{resource_regexp, RR} ->
|
||||
is_regexp_match(Resource, RR);
|
||||
{node_regexp, UR, SR} ->
|
||||
is_regexp_match(Server, SR) andalso
|
||||
is_regexp_match(User, UR);
|
||||
{user_glob, UR} ->
|
||||
(Host == Server orelse
|
||||
Host == global andalso
|
||||
lists:member(Server, ?MYHOSTS))
|
||||
andalso is_glob_match(User, UR);
|
||||
{user_glob, UR, S} ->
|
||||
S == Server andalso is_glob_match(User, UR);
|
||||
{server_glob, SR} -> is_glob_match(Server, SR);
|
||||
{resource_glob, RR} ->
|
||||
is_glob_match(Resource, RR);
|
||||
{node_glob, UR, SR} ->
|
||||
is_glob_match(Server, SR) andalso
|
||||
is_glob_match(User, UR);
|
||||
WrongSpec ->
|
||||
?ERROR_MSG("Wrong ACL expression: ~p~nCheck your "
|
||||
"config file and reload it with the override_a"
|
||||
"cls option enabled",
|
||||
[WrongSpec]),
|
||||
false
|
||||
all -> allow;
|
||||
none -> deny;
|
||||
_ ->
|
||||
case ejabberd_config:get_global_option({access, Rule, global}) of
|
||||
undefined ->
|
||||
case ejabberd_config:get_global_option({access, Rule, Host}) of
|
||||
undefined ->
|
||||
deny;
|
||||
ACLs ->
|
||||
match_acls(ACLs, JID, Host)
|
||||
end;
|
||||
GACLs ->
|
||||
case ejabberd_config:get_global_option({access, Rule, Host}) of
|
||||
undefined ->
|
||||
match_acls(GACLs, JID, Host);
|
||||
ACLs ->
|
||||
case lists:reverse(GACLs) of
|
||||
[{allow, all} | Rest] ->
|
||||
match_acls(
|
||||
lists:reverse(Rest) ++ ACLs ++
|
||||
[{allow, all}],
|
||||
JID, Host);
|
||||
_ ->
|
||||
match_acls(GACLs ++ ACLs, JID, Host)
|
||||
end
|
||||
end,
|
||||
ets:lookup(acl, {ACL, global}) ++
|
||||
ets:lookup(acl, {ACL, Host}))
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
is_regexp_match(String, RegExp) ->
|
||||
case ejabberd_regexp:run(String, RegExp) of
|
||||
nomatch -> false;
|
||||
match -> true;
|
||||
{error, ErrDesc} ->
|
||||
?ERROR_MSG("Wrong regexp ~p in ACL: ~p",
|
||||
[RegExp, ErrDesc]),
|
||||
false
|
||||
%% @spec (ACLs, JID, Host) -> Access
|
||||
%% ACLs = [{Access, ACLName}]
|
||||
%% Access = deny | atom()
|
||||
%% ACLName = atom()
|
||||
%% JID = exmpp_jid:jid()
|
||||
%% Host = string()
|
||||
|
||||
match_acls([], _, _Host) ->
|
||||
deny;
|
||||
match_acls([{Access, ACLName} | ACLs], JID, Host) ->
|
||||
case match_acl(ACLName, JID, Host) of
|
||||
true ->
|
||||
Access;
|
||||
_ ->
|
||||
match_acls(ACLs, JID, Host)
|
||||
end.
|
||||
|
||||
%% @spec (ACLName, JID, Host) -> bool()
|
||||
%% ACLName = all | none | atom()
|
||||
%% JID = exmpp_jid:jid()
|
||||
%% Host = string()
|
||||
|
||||
match_acl(ACLName, JID, Host) ->
|
||||
case ACLName of
|
||||
all -> true;
|
||||
none -> false;
|
||||
_ ->
|
||||
User = exmpp_jid:prep_node_as_list(JID),
|
||||
Server = exmpp_jid:prep_domain_as_list(JID),
|
||||
Resource = exmpp_jid:prep_resource_as_list(JID),
|
||||
lists:any(fun(#acl{aclname=Name, aclspec = Spec}) ->
|
||||
case Spec of
|
||||
all ->
|
||||
true;
|
||||
{user, U} ->
|
||||
(U == User)
|
||||
andalso
|
||||
((Host == Server) orelse
|
||||
((Host == global) andalso
|
||||
?IS_MY_HOST(Server)));
|
||||
{user, U, S} ->
|
||||
(U == User) andalso (S == Server);
|
||||
{server, S} ->
|
||||
S == Server;
|
||||
{resource, R} ->
|
||||
R == Resource;
|
||||
{user_regexp, UR} when is_tuple(Name),
|
||||
element(2, Name) =:= global ->
|
||||
?IS_MY_HOST(Server)
|
||||
andalso is_regexp_match(User, UR);
|
||||
{user_regexp, UR} ->
|
||||
((Host == Server) orelse
|
||||
((Host == global) andalso
|
||||
?IS_MY_HOST(Server)))
|
||||
andalso is_regexp_match(User, UR);
|
||||
{shared_group, G} ->
|
||||
mod_shared_roster:is_user_in_group({User, Server}, G, Host);
|
||||
{shared_group, G, H} ->
|
||||
mod_shared_roster:is_user_in_group({User, Server}, G, H);
|
||||
{user_regexp, UR, S} ->
|
||||
(S == Server) andalso
|
||||
is_regexp_match(User, UR);
|
||||
{server_regexp, SR} ->
|
||||
is_regexp_match(Server, SR);
|
||||
{resource_regexp, RR} ->
|
||||
is_regexp_match(Resource, RR);
|
||||
{node_regexp, UR, SR} ->
|
||||
is_regexp_match(Server, SR) andalso
|
||||
is_regexp_match(User, UR);
|
||||
{user_glob, UR} ->
|
||||
((Host == Server) orelse
|
||||
((Host == global) andalso
|
||||
?IS_MY_HOST(Server)))
|
||||
andalso
|
||||
is_glob_match(User, UR);
|
||||
{user_glob, UR, S} ->
|
||||
(S == Server) andalso
|
||||
is_glob_match(User, UR);
|
||||
{server_glob, SR} ->
|
||||
is_glob_match(Server, SR);
|
||||
{resource_glob, RR} ->
|
||||
is_glob_match(Resource, RR);
|
||||
{node_glob, UR, SR} ->
|
||||
is_glob_match(Server, SR) andalso
|
||||
is_glob_match(User, UR);
|
||||
WrongSpec ->
|
||||
?ERROR_MSG(
|
||||
"Wrong ACL expression: ~p~n"
|
||||
"Check your config file and reload it with the override_acls option enabled",
|
||||
[WrongSpec]),
|
||||
false
|
||||
end
|
||||
end,
|
||||
ets:lookup(acl, {ACLName, global}) ++
|
||||
ets:lookup(acl, {ACLName, Host}))
|
||||
end.
|
||||
|
||||
%% @spec (String, RegExp) -> bool()
|
||||
%% String = string() | undefined
|
||||
%% RegExp = string()
|
||||
|
||||
is_regexp_match(undefined, _RegExp) ->
|
||||
false;
|
||||
is_regexp_match(String, RegExp) ->
|
||||
try re:run(String, RegExp, [{capture, none}]) of
|
||||
nomatch ->
|
||||
false;
|
||||
match ->
|
||||
true
|
||||
catch
|
||||
_:ErrDesc ->
|
||||
?ERROR_MSG(
|
||||
"Wrong regexp ~p in ACL:~n~p",
|
||||
[RegExp, ErrDesc]),
|
||||
false
|
||||
end.
|
||||
|
||||
%% @spec (String, Glob) -> bool()
|
||||
%% String = string() | undefined
|
||||
%% Glob = string()
|
||||
|
||||
is_glob_match(String, Glob) ->
|
||||
is_regexp_match(String,
|
||||
ejabberd_regexp:sh_to_awk(Glob)).
|
||||
is_regexp_match(String, xmerl_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.
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, acl),
|
||||
case mnesia:table_info(acl, attributes) of
|
||||
Fields ->
|
||||
ejabberd_config:convert_table_to_binary(
|
||||
acl, Fields, bag,
|
||||
fun(#acl{aclspec = Spec}) when is_tuple(Spec) ->
|
||||
element(2, Spec);
|
||||
(_) ->
|
||||
'$next'
|
||||
end,
|
||||
fun(#acl{aclname = {ACLName, Host},
|
||||
aclspec = Spec} = R) ->
|
||||
NewHost = if Host == global ->
|
||||
Host;
|
||||
true ->
|
||||
iolist_to_binary(Host)
|
||||
end,
|
||||
R#acl{aclname = {ACLName, NewHost},
|
||||
aclspec = normalize_spec(Spec)}
|
||||
end);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating acl table", []),
|
||||
mnesia:transform_table(acl, ignore, Fields)
|
||||
end.
|
||||
for_host(Host) ->
|
||||
mnesia:select(acl,
|
||||
ets:fun2ms(fun (#acl{aclname = {_ACLName, H}})
|
||||
when H =:= Host ->
|
||||
object()
|
||||
end)).
|
||||
|
||||
+90
-110
@@ -5,7 +5,7 @@
|
||||
%%% Created : 31 Oct 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -25,128 +25,108 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(adhoc).
|
||||
|
||||
-author('henoch@dtek.chalmers.se').
|
||||
|
||||
-export([
|
||||
parse_request/1,
|
||||
produce_response/2,
|
||||
produce_response/1
|
||||
]).
|
||||
-export([parse_request/1,
|
||||
produce_response/2,
|
||||
produce_response/1]).
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("adhoc.hrl").
|
||||
|
||||
%%
|
||||
-spec(parse_request/1 ::
|
||||
(
|
||||
IQ :: iq_request())
|
||||
-> adhoc_response()
|
||||
%%
|
||||
| {error, _}
|
||||
).
|
||||
%% Parse an ad-hoc request. Return either an adhoc_request record or
|
||||
%% an {error, ErrorType} tuple.
|
||||
parse_request(#iq{type = Type, ns = NS, payload = SubEl, lang = Lang}) ->
|
||||
try
|
||||
case {Type, NS} of
|
||||
{set, ?NS_ADHOC} ->
|
||||
?DEBUG("entering parse_request...", []),
|
||||
Node = exmpp_xml:get_attribute_as_list(SubEl, <<"node">>, ""),
|
||||
SessionID = exmpp_xml:get_attribute_as_list(SubEl, <<"sessionid">>, ""),
|
||||
Action = exmpp_xml:get_attribute_as_list(SubEl, <<"action">>, ""),
|
||||
XData = find_xdata_el(SubEl),
|
||||
AllEls = exmpp_xml:get_child_elements(SubEl),
|
||||
Others = case XData of
|
||||
false ->
|
||||
AllEls;
|
||||
_ ->
|
||||
lists:delete(XData, AllEls)
|
||||
end,
|
||||
|
||||
parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) ->
|
||||
?DEBUG("entering parse_request...", []),
|
||||
Node = xml:get_tag_attr_s(<<"node">>, SubEl),
|
||||
SessionID = xml:get_tag_attr_s(<<"sessionid">>, SubEl),
|
||||
Action = xml:get_tag_attr_s(<<"action">>, SubEl),
|
||||
XData = find_xdata_el(SubEl),
|
||||
#xmlel{children = AllEls} = SubEl,
|
||||
Others = case XData of
|
||||
false -> AllEls;
|
||||
_ -> lists:delete(XData, AllEls)
|
||||
end,
|
||||
#adhoc_request{
|
||||
lang = Lang,
|
||||
node = Node,
|
||||
sessionid = SessionID,
|
||||
action = Action,
|
||||
xdata = XData,
|
||||
others = Others
|
||||
};
|
||||
parse_request(_) -> {error, ?ERR_BAD_REQUEST}.
|
||||
#adhoc_request{lang = Lang,
|
||||
node = Node,
|
||||
sessionid = SessionID,
|
||||
action = Action,
|
||||
xdata = XData,
|
||||
others = Others};
|
||||
_ ->
|
||||
{error, 'bad-request'}
|
||||
end
|
||||
catch
|
||||
_ ->
|
||||
{error, 'bad-request'}
|
||||
end.
|
||||
|
||||
%% Borrowed from mod_vcard.erl
|
||||
find_xdata_el(#xmlel{children = SubEls}) ->
|
||||
find_xdata_el1(SubEls).
|
||||
|
||||
find_xdata_el1([]) -> false;
|
||||
find_xdata_el1([El | Els]) when is_record(El, xmlel) ->
|
||||
case xml:get_tag_attr_s(<<"xmlns">>, El) of
|
||||
?NS_XDATA -> El;
|
||||
_ -> find_xdata_el1(Els)
|
||||
end;
|
||||
find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
|
||||
find_xdata_el1([]) ->
|
||||
false;
|
||||
find_xdata_el1([#xmlel{ns = ?NS_DATA_FORMS} = El | _Els]) ->
|
||||
El;
|
||||
find_xdata_el1([_ | Els]) ->
|
||||
find_xdata_el1(Els).
|
||||
|
||||
%%
|
||||
-spec(produce_response/2 ::
|
||||
(
|
||||
Adhoc_Request :: adhoc_request(),
|
||||
Adhoc_Response :: adhoc_response())
|
||||
-> Xmlel::xmlel()
|
||||
).
|
||||
%% Produce a <command/> node to use as response from an adhoc_response
|
||||
%% record, filling in values for language, node and session id from
|
||||
%% the request.
|
||||
produce_response(#adhoc_request{lang = Lang,
|
||||
node = Node,
|
||||
sessionid = SessionID},
|
||||
Response) ->
|
||||
produce_response(Response#adhoc_response{lang = Lang,
|
||||
node = Node,
|
||||
sessionid = SessionID}).
|
||||
|
||||
produce_response(#adhoc_request{lang = Lang, node = Node, sessionid = SessionID},
|
||||
Adhoc_Response) ->
|
||||
produce_response(Adhoc_Response#adhoc_response{
|
||||
lang = Lang, node = Node, sessionid = SessionID
|
||||
}).
|
||||
|
||||
%%
|
||||
-spec(produce_response/1 ::
|
||||
(
|
||||
Adhoc_Response::adhoc_response())
|
||||
-> Xmlel::xmlel()
|
||||
).
|
||||
|
||||
produce_response(
|
||||
#adhoc_response{
|
||||
%lang = _Lang,
|
||||
node = Node,
|
||||
sessionid = ProvidedSessionID,
|
||||
status = Status,
|
||||
defaultaction = DefaultAction,
|
||||
actions = Actions,
|
||||
notes = Notes,
|
||||
elements = Elements
|
||||
}) ->
|
||||
SessionID = if is_binary(ProvidedSessionID),
|
||||
ProvidedSessionID /= <<"">> -> ProvidedSessionID;
|
||||
true -> jlib:now_to_utc_string(now())
|
||||
end,
|
||||
%% Produce a <command/> node to use as response from an adhoc_response
|
||||
%% record.
|
||||
produce_response(#adhoc_response{lang = _Lang,
|
||||
node = Node,
|
||||
sessionid = ProvidedSessionID,
|
||||
status = Status,
|
||||
defaultaction = DefaultAction,
|
||||
actions = Actions,
|
||||
notes = Notes,
|
||||
elements = Elements}) ->
|
||||
SessionID = if is_list(ProvidedSessionID), ProvidedSessionID /= "" ->
|
||||
ProvidedSessionID;
|
||||
true ->
|
||||
jlib:now_to_utc_string(now())
|
||||
end,
|
||||
case Actions of
|
||||
[] ->
|
||||
ActionsEls = [];
|
||||
_ ->
|
||||
case DefaultAction of
|
||||
<<"">> -> ActionsElAttrs = [];
|
||||
_ -> ActionsElAttrs = [{<<"execute">>, DefaultAction}]
|
||||
end,
|
||||
ActionsEls = [
|
||||
#xmlel{
|
||||
name = <<"actions">>,
|
||||
attrs = ActionsElAttrs,
|
||||
children = [
|
||||
#xmlel{name = Action, attrs = [], children = []}
|
||||
|| Action <- Actions]
|
||||
}
|
||||
]
|
||||
[] ->
|
||||
ActionsEls = [];
|
||||
_ ->
|
||||
case DefaultAction of
|
||||
"" ->
|
||||
ActionsElAttrs = [];
|
||||
_ ->
|
||||
ActionsElAttrs = [?XMLATTR(<<"execute">>, DefaultAction)]
|
||||
end,
|
||||
ActionsEls = [#xmlel{ns = ?NS_ADHOC, name = 'actions', attrs =
|
||||
ActionsElAttrs, children =
|
||||
[#xmlel{ns = ?NS_ADHOC, name = Action} || Action <- Actions]}]
|
||||
end,
|
||||
NotesEls = lists:map(fun({Type, Text}) ->
|
||||
#xmlel{
|
||||
name = <<"note">>,
|
||||
attrs = [{<<"type">>, Type}],
|
||||
children = [{xmlcdata, Text}]
|
||||
}
|
||||
end, Notes),
|
||||
#xmlel{
|
||||
name = <<"command">>,
|
||||
attrs = [
|
||||
{<<"xmlns">>, ?NS_COMMANDS},
|
||||
{<<"sessionid">>, SessionID},
|
||||
{<<"node">>, Node},
|
||||
{<<"status">>, iolist_to_binary(atom_to_list(Status))}
|
||||
],
|
||||
children = ActionsEls ++ NotesEls ++ Elements
|
||||
}.
|
||||
#xmlel{ns = ?NS_ADHOC, name = 'note', attrs =
|
||||
[?XMLATTR(<<"type">>, Type)],
|
||||
children = [#xmlcdata{cdata = list_to_binary(Text)}]}
|
||||
end, Notes),
|
||||
#xmlel{ns = ?NS_ADHOC, name = 'command', attrs =
|
||||
[?XMLATTR(<<"sessionid">>, SessionID),
|
||||
?XMLATTR(<<"node">>, Node),
|
||||
?XMLATTR(<<"status">>, Status)], children =
|
||||
ActionsEls ++ NotesEls ++ Elements}.
|
||||
|
||||
+15
-24
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -19,27 +19,18 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(adhoc_request,
|
||||
{
|
||||
lang = <<"">> :: binary(),
|
||||
node = <<"">> :: binary(),
|
||||
sessionid = <<"">> :: binary(),
|
||||
action = <<"">> :: binary(),
|
||||
xdata = false :: false | xmlel(),
|
||||
others = [] :: [xmlel()]
|
||||
}).
|
||||
-record(adhoc_request, {lang,
|
||||
node,
|
||||
sessionid,
|
||||
action,
|
||||
xdata,
|
||||
others}).
|
||||
|
||||
-record(adhoc_response,
|
||||
{
|
||||
lang = <<"">> :: binary(),
|
||||
node = <<"">> :: binary(),
|
||||
sessionid = <<"">> :: binary(),
|
||||
status :: atom(),
|
||||
defaultaction = <<"">> :: binary(),
|
||||
actions = [] :: [binary()],
|
||||
notes = [] :: [{binary(), binary()}],
|
||||
elements = [] :: [xmlel()]
|
||||
}).
|
||||
|
||||
-type adhoc_request() :: #adhoc_request{}.
|
||||
-type adhoc_response() :: #adhoc_response{}.
|
||||
-record(adhoc_response, {lang,
|
||||
node,
|
||||
sessionid,
|
||||
status,
|
||||
defaultaction = "",
|
||||
actions = [],
|
||||
notes = [],
|
||||
elements = []}).
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
%% Copyright (C) 2009 Romuald du Song <rdusong _AT_ gmail _DOT_ com>.
|
||||
%% All rights reserved.
|
||||
%%
|
||||
%% Redistribution and use in source and binary forms, with or without
|
||||
%% modification, are permitted provided that the following conditions
|
||||
%% are met:
|
||||
%%
|
||||
%% 1. Redistributions of source code must retain the above copyright
|
||||
%% notice, this list of conditions and the following disclaimer.
|
||||
%% 2. Redistributions in binary form must reproduce the above
|
||||
%% copyright notice, this list of conditions and the following
|
||||
%% disclaimer in the documentation and/or other materials provided
|
||||
%% with the distribution.
|
||||
%%
|
||||
%% THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
||||
%% OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
%% WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
%% ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
%% DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
%% DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
%% GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
%% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
%% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
%% SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
-module(beam_util).
|
||||
|
||||
-export([module_export_list/1, filter_arity/3]).
|
||||
|
||||
|
||||
%% Module = string()
|
||||
%% Function = atom()
|
||||
module_export_list( Module ) ->
|
||||
{_Module, _Binary, Filename} = code:get_object_code(Module),
|
||||
case beam_lib:info( Filename ) of
|
||||
{error, beam_lib, _} ->
|
||||
false;
|
||||
[ _ , _ , _ ] ->
|
||||
case beam_lib:chunks( Filename, [exports]) of
|
||||
{ok, {_, [{exports, Exports}]}} ->
|
||||
Exports;
|
||||
{error, beam_lib, Er} ->
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
%% Module = string()
|
||||
%% Arity = integer()
|
||||
%% Exports = list()
|
||||
filter_arity( Function, Arity, Exports) ->
|
||||
case lists:filter(
|
||||
fun( EFName ) -> {Function, Arity} == EFName end,
|
||||
Exports ) of
|
||||
[{_, _}] -> true;
|
||||
[] -> false
|
||||
end.
|
||||
+4
-9
@@ -6,7 +6,7 @@
|
||||
%%% Created : 29 Aug 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -70,8 +70,8 @@
|
||||
%% API
|
||||
%%====================================================================
|
||||
start_link(Proc, Tab, Opts, Owner) ->
|
||||
?GEN_SERVER:start_link(
|
||||
{local, Proc}, ?MODULE, [Tab, Opts, get_proc_num(), Owner], []).
|
||||
?GEN_SERVER:start_link({local, Proc}, ?MODULE,
|
||||
[Tab, Opts, get_proc_num(), Owner], []).
|
||||
|
||||
new(Tab, Opts) ->
|
||||
Res = lists:flatmap(
|
||||
@@ -380,12 +380,7 @@ do_setopts(#state{procs_num = N} = State, Opts) ->
|
||||
shrink_size = ShrinkSize}.
|
||||
|
||||
get_proc_num() ->
|
||||
case catch erlang:system_info(logical_processors) of
|
||||
Num when is_integer(Num) ->
|
||||
Num;
|
||||
_ ->
|
||||
1
|
||||
end.
|
||||
erlang:system_info(logical_processors).
|
||||
|
||||
get_proc_by_hash(Tab, Term) ->
|
||||
N = erlang:phash2(Term, get_proc_num()) + 1,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
%%% Created : 30 Aug 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
|
||||
Vendored
-6360
File diff suppressed because it is too large
Load Diff
+10
-24
@@ -14,10 +14,6 @@ fi
|
||||
|
||||
#locating erlang
|
||||
AM_WITH_ERLANG
|
||||
#locating iconv
|
||||
AM_ICONV
|
||||
#locating libexpat
|
||||
AM_WITH_EXPAT
|
||||
|
||||
# Checks for typedefs, structures, and compiler characteristics.
|
||||
AC_C_CONST
|
||||
@@ -32,20 +28,14 @@ AC_PREFIX_DEFAULT(/)
|
||||
AC_FUNC_MALLOC
|
||||
AC_HEADER_STDC
|
||||
|
||||
AC_MOD_ENABLE(mod_irc, yes)
|
||||
AC_MOD_ENABLE(mod_muc, yes)
|
||||
AC_MOD_ENABLE(mod_proxy65, yes)
|
||||
AC_MOD_ENABLE(mod_pubsub, yes)
|
||||
AC_MOD_ENABLE(mod_pubsub_ng, yes)
|
||||
AC_MOD_ENABLE(eldap, yes)
|
||||
AC_MOD_ENABLE(odbc, no)
|
||||
AC_MOD_ENABLE(tls, yes)
|
||||
AC_MOD_ENABLE(web, yes)
|
||||
|
||||
AC_MOD_ENABLE(ejabberd_zlib, yes)
|
||||
#locating zlib
|
||||
AM_WITH_ZLIB
|
||||
|
||||
AC_MOD_ENABLE(pam, no)
|
||||
#locating PAM
|
||||
AM_WITH_PAM
|
||||
@@ -104,29 +94,16 @@ 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
|
||||
$make_mod_pubsub
|
||||
$make_mod_pubsub_ng
|
||||
$make_mod_proxy65
|
||||
$make_eldap
|
||||
$make_pam
|
||||
$make_web
|
||||
stringprep/Makefile
|
||||
stun/Makefile
|
||||
$make_tls
|
||||
$make_odbc
|
||||
$make_ejabberd_zlib])
|
||||
$make_odbc])
|
||||
#openssl
|
||||
AM_WITH_OPENSSL
|
||||
# If ssl is kerberized it need krb5.h
|
||||
@@ -190,3 +167,12 @@ LD_SHARED="$LD_SHARED $CBIT"
|
||||
echo "CBIT is set to '$CBIT'"
|
||||
|
||||
AC_OUTPUT
|
||||
|
||||
echo
|
||||
echo "********************** WARNING ! ************************"
|
||||
echo "* ejabberd master is NOT ready for real usage yet, *"
|
||||
echo "* because it is still in heavy development. *"
|
||||
echo "* Don't use ejabberd master, it is still alpha code! *"
|
||||
echo "* Please use ejabberd 2.1.x branch instead. *"
|
||||
echo "*********************************************************"
|
||||
echo
|
||||
|
||||
+2
-16
@@ -5,7 +5,7 @@
|
||||
%%% Created : 27 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -42,17 +42,9 @@ start() ->
|
||||
end,
|
||||
case Static of
|
||||
true ->
|
||||
ExpatLib = "EXPAT_LIB = $(EXPAT_DIR)\\StaticLibs\\libexpatMT.lib\n",
|
||||
ExpatFlag = "EXPAT_FLAG = -DXML_STATIC\n",
|
||||
IconvDir = "ICONV_DIR = c:\\sdk\\GnuWin32\n",
|
||||
IconvLib = "ICONV_LIB = $(ICONV_DIR)\\lib\\libiconv.lib\n",
|
||||
ZlibDir = "ZLIB_DIR = c:\\sdk\\GnuWin32\n",
|
||||
ZlibLib = "ZLIB_LIB = $(ZLIB_DIR)\\lib\\zlib.lib\n";
|
||||
false ->
|
||||
ExpatLib = "EXPAT_LIB = $(EXPAT_DIR)\\Libs\\libexpat.lib\n",
|
||||
ExpatFlag = "",
|
||||
IconvDir = "ICONV_DIR = c:\\sdk\\GnuWin32\n",
|
||||
IconvLib = "ICONV_LIB = $(ICONV_DIR)\\lib\\libiconv.lib\n",
|
||||
ZlibDir = "ZLIB_DIR = c:\\sdk\\GnuWin32\n",
|
||||
ZlibLib = "ZLIB_LIB = $(ZLIB_DIR)\\lib\\zlib.lib\n"
|
||||
end,
|
||||
@@ -62,8 +54,7 @@ start() ->
|
||||
RootDirS = "ERLANG_DIR = " ++ code:root_dir() ++ "\n",
|
||||
%% Load the ejabberd application description so that ?VERSION can read the vsn key
|
||||
application:load(ejabberd),
|
||||
Version = "EJABBERD_VERSION = " ++ binary_to_list(?VERSION) ++ "\n",
|
||||
ExpatDir = "EXPAT_DIR = c:\\sdk\\Expat-2.0.0\n",
|
||||
Version = "EJABBERD_VERSION = " ++ ?VERSION ++ "\n",
|
||||
OpenSSLDir = "OPENSSL_DIR = c:\\sdk\\OpenSSL\n",
|
||||
DBType = "DBTYPE = generic\n", %% 'generic' or 'mssql'
|
||||
|
||||
@@ -79,11 +70,6 @@ start() ->
|
||||
StdLibDir ++
|
||||
OpenSSLDir ++
|
||||
DBType ++
|
||||
ExpatDir ++
|
||||
ExpatLib ++
|
||||
ExpatFlag ++
|
||||
IconvDir ++
|
||||
IconvLib ++
|
||||
ZlibDir ++
|
||||
ZlibLib)),
|
||||
halt().
|
||||
|
||||
+210
-137
@@ -5,7 +5,7 @@
|
||||
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -25,191 +25,264 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(cyrsasl).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0, register_mechanism/3, listmech/1,
|
||||
server_new/7, server_start/3, server_step/2]).
|
||||
-export([start/0,
|
||||
register_mechanism/3,
|
||||
listmech/1,
|
||||
server_new/8,
|
||||
server_start/3,
|
||||
server_step/2]).
|
||||
|
||||
-include("cyrsasl.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
%%
|
||||
-export_type([
|
||||
mechanism/0,
|
||||
mechanisms/0,
|
||||
sasl_mechanism/0
|
||||
]).
|
||||
%% @type saslmechanism() = {sasl_mechanism, Mechanism, Module, Require_Plain}
|
||||
%% Mechanism = string()
|
||||
%% Module = atom()
|
||||
%% Require_Plain = bool().
|
||||
%% Registry entry of a supported SASL mechanism.
|
||||
|
||||
-record(sasl_mechanism,
|
||||
{mechanism = <<"">> :: mechanism() | '$1',
|
||||
module :: atom(),
|
||||
password_type = plain :: password_type() | '$2'}).
|
||||
-record(sasl_mechanism, {mechanism, module, password_type}).
|
||||
|
||||
-type(mechanism() :: binary()).
|
||||
-type(mechanisms() :: [mechanism(),...]).
|
||||
-type(password_type() :: plain | digest | scram).
|
||||
-type(props() :: [{username, binary()} |
|
||||
{authzid, binary()} |
|
||||
{auth_module, atom()}]).
|
||||
%% @type saslstate() = {sasl_state, Service, Myname, Realm, GetPassword, CheckPassword, CheckPasswordDigest, Mech_Mod, Mech_State}
|
||||
%% Service = string()
|
||||
%% Myname = string()
|
||||
%% Realm = string()
|
||||
%% GetPassword = function()
|
||||
%% CheckPassword = function()
|
||||
%% CheckPasswordDigest = any()
|
||||
%% Mech_Mod = atom()
|
||||
%% Mech_State = term().
|
||||
%% State of this process.
|
||||
|
||||
-type(sasl_mechanism() :: #sasl_mechanism{}).
|
||||
-record(sasl_state, {service, myname,
|
||||
mech_mod, mech_state, params}).
|
||||
|
||||
-record(sasl_state,
|
||||
{
|
||||
service,
|
||||
myname,
|
||||
realm,
|
||||
get_password,
|
||||
check_password,
|
||||
check_password_digest,
|
||||
mech_mod,
|
||||
mech_state
|
||||
}).
|
||||
-export([behaviour_info/1]).
|
||||
|
||||
-callback mech_new(binary(), fun(), fun(), fun()) -> any().
|
||||
-callback mech_step(any(), binary()) -> {ok, props()} |
|
||||
{ok, props(), binary()} |
|
||||
{continue, binary(), any()} |
|
||||
{error, binary()} |
|
||||
{error, binary(), binary()}.
|
||||
%% @hidden
|
||||
|
||||
behaviour_info(callbacks) ->
|
||||
[{mech_new, 1}, {mech_step, 2}];
|
||||
behaviour_info(_Other) ->
|
||||
undefined.
|
||||
|
||||
%% @spec () -> ok
|
||||
|
||||
start() ->
|
||||
ets:new(sasl_mechanism,
|
||||
[named_table, public,
|
||||
{keypos, #sasl_mechanism.mechanism}]),
|
||||
ets:new(sasl_mechanism, [named_table,
|
||||
public,
|
||||
{keypos, #sasl_mechanism.mechanism}]),
|
||||
cyrsasl_plain:start([]),
|
||||
cyrsasl_digest:start([]),
|
||||
cyrsasl_scram:start([]),
|
||||
cyrsasl_anonymous:start([]),
|
||||
maybe_try_start_gssapi(),
|
||||
ok.
|
||||
|
||||
%%
|
||||
-spec(register_mechanism/3 ::
|
||||
(
|
||||
Mechanim :: mechanism(),
|
||||
Module :: module(),
|
||||
PasswordType :: password_type())
|
||||
-> any()
|
||||
).
|
||||
maybe_try_start_gssapi() ->
|
||||
case os:getenv("KRB5_KTNAME") of
|
||||
false ->
|
||||
ok;
|
||||
_String ->
|
||||
try_start_gssapi()
|
||||
end.
|
||||
|
||||
try_start_gssapi() ->
|
||||
case code:load_file(esasl) of
|
||||
{module, _Module} ->
|
||||
cyrsasl_gssapi:start([]);
|
||||
{error, What} ->
|
||||
?ERROR_MSG("Support for GSSAPI not started because esasl.beam was not found: ~p", [What])
|
||||
end.
|
||||
|
||||
%% @spec (Mechanism, Module, Require_Plain) -> true
|
||||
%% Mechanism = string()
|
||||
%% Module = atom()
|
||||
%% Require_Plain = bool()
|
||||
|
||||
register_mechanism(Mechanism, Module, PasswordType) ->
|
||||
ets:insert(sasl_mechanism,
|
||||
#sasl_mechanism{mechanism = Mechanism, module = Module,
|
||||
#sasl_mechanism{mechanism = Mechanism,
|
||||
module = Module,
|
||||
password_type = PasswordType}).
|
||||
|
||||
%%% TODO: use callbacks
|
||||
%%-include("ejabberd.hrl").
|
||||
%%-include("jlib.hrl").
|
||||
%%check_authzid(_State, Props) ->
|
||||
%% AuthzId = xml:get_attr_s(authzid, Props),
|
||||
%% case jlib:string_to_jid(AuthzId) of
|
||||
%% error ->
|
||||
%% {error, "invalid-authzid"};
|
||||
%% JID ->
|
||||
%% LUser = jlib:nodeprep(xml:get_attr_s(username, Props)),
|
||||
%% {U, S, R} = jlib:jid_tolower(JID),
|
||||
%% case R of
|
||||
%% "" ->
|
||||
%% {error, "invalid-authzid"};
|
||||
%% _ ->
|
||||
%% case {LUser, ?MYNAME} of
|
||||
%% {U, S} ->
|
||||
%% ok;
|
||||
%% _ ->
|
||||
%% {error, "invalid-authzid"}
|
||||
%% end
|
||||
%% end
|
||||
%% end.
|
||||
% TODO use callbacks
|
||||
%-include("ejabberd.hrl").
|
||||
%-include("jlib.hrl").
|
||||
%check_authzid(_State, Props) ->
|
||||
% AuthzId = xml:get_attr_s(authzid, Props),
|
||||
% case jlib:string_to_jid(AuthzId) of
|
||||
% error ->
|
||||
% {error, "invalid-authzid"};
|
||||
% JID ->
|
||||
% LUser = jlib:nodeprep(xml:get_attr_s(username, Props)),
|
||||
% {U, S, R} = jlib:short_prepd_jid(JID),
|
||||
% case R of
|
||||
% "" ->
|
||||
% {error, "invalid-authzid"};
|
||||
% _ ->
|
||||
% case {LUser, ?MYNAME} of
|
||||
% {U, S} ->
|
||||
% ok;
|
||||
% _ ->
|
||||
% {error, "invalid-authzid"}
|
||||
% end
|
||||
% end
|
||||
% end.
|
||||
|
||||
%% @spec (State, Props) -> ok | {error, 'not-authorized'}
|
||||
%% State = saslstate()
|
||||
%% Props = [{Key, Value}]
|
||||
%% Key = atom()
|
||||
%% Value = string()
|
||||
|
||||
check_credentials(_State, Props) ->
|
||||
User = proplists:get_value(username, Props, <<>>),
|
||||
case jlib:nodeprep(User) of
|
||||
error -> {error, <<"not-authorized">>};
|
||||
<<"">> -> {error, <<"not-authorized">>};
|
||||
_LUser -> ok
|
||||
case proplists:get_value(username, Props) of
|
||||
undefined ->
|
||||
{error, 'not-authorized'};
|
||||
User ->
|
||||
case exmpp_stringprep:is_node(User) of
|
||||
false -> {error, 'not-authorized'};
|
||||
true -> ok
|
||||
end
|
||||
end.
|
||||
|
||||
-spec(listmech/1 ::
|
||||
(
|
||||
Host ::binary())
|
||||
-> Mechanisms::mechanisms()
|
||||
).
|
||||
%% @spec (Host) -> [Mechanism]
|
||||
%% Host = string()
|
||||
%% Mechanism = string()
|
||||
|
||||
listmech(Host) ->
|
||||
Mechs = ets:select(sasl_mechanism,
|
||||
[{#sasl_mechanism{mechanism = '$1',
|
||||
password_type = '$2', _ = '_'},
|
||||
password_type = '$2',
|
||||
_ = '_'},
|
||||
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 -> []
|
||||
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).
|
||||
|
||||
%% @spec (Service, ServerFQDN, UserRealm, SecFlags, GetPassword, CheckPassword, CheckPasswordDigest, Socket) -> saslstate()
|
||||
%% Service = string()
|
||||
%% ServerFQDN = string()
|
||||
%% UserRealm = string()
|
||||
%% SecFlags = [term()]
|
||||
%% GetPassword = function()
|
||||
%% CheckPassword = function()
|
||||
|
||||
server_new(Service, ServerFQDN, UserRealm, _SecFlags,
|
||||
GetPassword, CheckPassword, CheckPasswordDigest) ->
|
||||
#sasl_state{service = Service, myname = ServerFQDN,
|
||||
realm = UserRealm, get_password = GetPassword,
|
||||
check_password = CheckPassword,
|
||||
check_password_digest = CheckPasswordDigest}.
|
||||
GetPassword, CheckPassword, CheckPasswordDigest, Socket) ->
|
||||
Params = #sasl_params{
|
||||
host = ServerFQDN,
|
||||
realm = UserRealm,
|
||||
get_password = GetPassword,
|
||||
check_password = CheckPassword,
|
||||
check_password_digest= CheckPasswordDigest,
|
||||
socket = Socket
|
||||
},
|
||||
|
||||
#sasl_state{service = Service,
|
||||
myname = ServerFQDN,
|
||||
params = Params}.
|
||||
|
||||
|
||||
%% @spec (State, Mech, ClientIn) -> Ok | Continue | Error
|
||||
%% State = saslstate()
|
||||
%% Mech = string()
|
||||
%% ClientIn = string()
|
||||
%% Ok = {ok, Props}
|
||||
%% Props = [Prop]
|
||||
%% Prop = [{Key, Value}]
|
||||
%% Key = atom()
|
||||
%% Value = string()
|
||||
%% Continue = {continue, ServerOut, New_State}
|
||||
%% ServerOut = string()
|
||||
%% New_State = saslstate()
|
||||
%% Error = {error, Reason} | {error, Reason, Username}
|
||||
%% Reason = term()
|
||||
%% Username = string()
|
||||
|
||||
server_start(State, Mech, ClientIn) ->
|
||||
case lists:member(Mech,
|
||||
listmech(State#sasl_state.myname))
|
||||
of
|
||||
true ->
|
||||
case ets:lookup(sasl_mechanism, Mech) of
|
||||
[#sasl_mechanism{module = Module}] ->
|
||||
{ok, MechState} =
|
||||
Module:mech_new(State#sasl_state.myname,
|
||||
State#sasl_state.get_password,
|
||||
State#sasl_state.check_password,
|
||||
State#sasl_state.check_password_digest),
|
||||
server_step(State#sasl_state{mech_mod = Module,
|
||||
mech_state = MechState},
|
||||
ClientIn);
|
||||
_ -> {error, <<"no-mechanism">>}
|
||||
end;
|
||||
false -> {error, <<"no-mechanism">>}
|
||||
case lists:member(Mech, listmech(State#sasl_state.myname)) of
|
||||
true ->
|
||||
case ets:lookup(sasl_mechanism, Mech) of
|
||||
[#sasl_mechanism{module = Module}] ->
|
||||
{ok, MechState} =
|
||||
Module:mech_new(State#sasl_state.params),
|
||||
server_step(State#sasl_state{mech_mod = Module,
|
||||
mech_state = MechState},
|
||||
ClientIn);
|
||||
_ ->
|
||||
{error, 'invalid-mechanism'}
|
||||
end;
|
||||
false ->
|
||||
{error, 'invalid-mechanism'}
|
||||
end.
|
||||
|
||||
%% @spec (State, ClientIn) -> Ok | Continue | Error
|
||||
%% State = saslstate()
|
||||
%% ClientIn = string()
|
||||
%% Ok = {ok, Props} | {ok, Props, ServerOut}
|
||||
%% Props = [Prop]
|
||||
%% Prop = [{Key, Value}]
|
||||
%% Key = atom()
|
||||
%% Value = string()
|
||||
%% Continue = {continue, ServerOut, New_State}
|
||||
%% ServerOut = string()
|
||||
%% New_State = saslstate()
|
||||
%% Error = {error, Reason} | {error, Reason, Text, Username}
|
||||
%% Reason = term()
|
||||
%% Text = string()
|
||||
%% Username = string()
|
||||
|
||||
server_step(State, ClientIn) ->
|
||||
Module = State#sasl_state.mech_mod,
|
||||
MechState = State#sasl_state.mech_state,
|
||||
case Module:mech_step(MechState, ClientIn) of
|
||||
{ok, Props} ->
|
||||
case check_credentials(State, Props) of
|
||||
ok -> {ok, Props};
|
||||
{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}};
|
||||
{error, Error, Username} ->
|
||||
{error, Error, Username};
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
{ok, Props} ->
|
||||
case check_credentials(State, Props) of
|
||||
ok ->
|
||||
{ok, Props};
|
||||
{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}};
|
||||
{error, Error, Text, Username} ->
|
||||
{error, Error, Text, Username};
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
%% @spec (Host, Mechs) -> [Filtered_Mechs]
|
||||
%% Host = string()
|
||||
%% Mechs = [Mech]
|
||||
%% Mech = string()
|
||||
%% Filtered_Mechs = [Mech]
|
||||
%%
|
||||
-spec(filter_anonymous/2 ::
|
||||
(
|
||||
Host :: binary(),
|
||||
Mechs :: mechanisms())
|
||||
-> mechanisms()
|
||||
).
|
||||
%% @doc Remove the anonymous mechanism from the list if not enabled for
|
||||
%% the given host.
|
||||
|
||||
filter_anonymous(Host, Mechs) ->
|
||||
case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(Host) of
|
||||
true -> Mechs;
|
||||
false -> Mechs -- [<<"ANONYMOUS">>]
|
||||
true -> Mechs;
|
||||
false -> Mechs -- ["ANONYMOUS"]
|
||||
end.
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
%% @type saslparams() = {sasl_params, Host, Realm, GetPassword, CheckPassword, CheckPasswordDigest}
|
||||
%% Host = string()
|
||||
%% Realm = string()
|
||||
%% GetPassword = function()
|
||||
%% CheckPassword = function()
|
||||
%% CheckPasswordDigest = any().
|
||||
%% Parameters for SASL.
|
||||
|
||||
-record(sasl_params, {
|
||||
host,
|
||||
realm,
|
||||
get_password,
|
||||
check_password,
|
||||
check_password_digest,
|
||||
socket}).
|
||||
+36
-10
@@ -6,7 +6,7 @@
|
||||
%%% Created : 23 Aug 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -27,24 +27,50 @@
|
||||
|
||||
-module(cyrsasl_anonymous).
|
||||
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2]).
|
||||
-export([start/1, stop/0, mech_new/1, mech_step/2]).
|
||||
|
||||
-include("cyrsasl.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state, {server = <<"">> :: binary()}).
|
||||
%% @type mechstate() = {state, Server}
|
||||
%% Server = string().
|
||||
|
||||
-record(state, {server}).
|
||||
|
||||
%% @spec (Opts) -> true
|
||||
%% Opts = term()
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism(<<"ANONYMOUS">>, ?MODULE, plain),
|
||||
cyrsasl:register_mechanism("ANONYMOUS", ?MODULE, plain),
|
||||
ok.
|
||||
|
||||
stop() -> ok.
|
||||
%% @spec () -> ok
|
||||
|
||||
mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
mech_new(#sasl_params{host=Host}) ->
|
||||
{ok, #state{server = Host}}.
|
||||
|
||||
mech_step(#state{server = Server}, _ClientIn) ->
|
||||
User = randoms:get_string(),
|
||||
%% @spec (State, ClientIn) -> Ok | Error
|
||||
%% State = mechstate()
|
||||
%% ClientIn = string()
|
||||
%% Ok = {ok, Props}
|
||||
%% Props = [Prop]
|
||||
%% Prop = {username, Username} | {auth_module, AuthModule}
|
||||
%% Username = string()
|
||||
%% AuthModule = ejabberd_auth_anonymous
|
||||
%% Error = {error, 'not-authorized'}
|
||||
|
||||
mech_step(State, _ClientIn) ->
|
||||
%% We generate a random username:
|
||||
User = lists:concat([randoms:get_string() | tuple_to_list(now())]),
|
||||
Server = State#state.server,
|
||||
|
||||
%% Checks that the username is available
|
||||
case ejabberd_auth:is_user_exists(User, Server) of
|
||||
true -> {error, <<"not-authorized">>};
|
||||
false -> {ok, [{username, User}, {auth_module, ejabberd_auth_anonymous}]}
|
||||
true -> {error, 'not-authorized'};
|
||||
false -> {ok, [{username, User},
|
||||
{auth_module, ejabberd_auth_anonymous}]}
|
||||
end.
|
||||
|
||||
+280
-170
@@ -5,7 +5,7 @@
|
||||
%%% Created : 11 Mar 2003 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -25,228 +25,338 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(cyrsasl_digest).
|
||||
|
||||
-author('alexey@sevcom.net').
|
||||
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1]).
|
||||
-export([start/1,
|
||||
stop/0,
|
||||
mech_new/1,
|
||||
mech_step/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("cyrsasl.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-type get_password_fun() :: fun((binary()) -> {false, any()} |
|
||||
{binary(), atom()}).
|
||||
%% @type mechstate() = {state, Step, Nonce, Username, AuthzId, GetPassword, CheckPassword, AuthModule, Host}
|
||||
%% Step = 1 | 3 | 5
|
||||
%% Nonce = string()
|
||||
%% Username = string()
|
||||
%% AuthzId = string()
|
||||
%% GetPassword = function()
|
||||
%% AuthModule = atom()
|
||||
%% Host = string().
|
||||
|
||||
-type check_password_fun() :: fun((binary(), binary(), binary(),
|
||||
fun((binary()) -> binary())) ->
|
||||
{boolean(), any()} |
|
||||
false).
|
||||
|
||||
-record(state, {step = 1 :: 1 | 3 | 5,
|
||||
nonce = <<"">> :: binary(),
|
||||
username = <<"">> :: binary(),
|
||||
authzid = <<"">> :: binary(),
|
||||
get_password = fun(_) -> {false, <<>>} end :: get_password_fun(),
|
||||
check_password = fun(_, _, _, _) -> false end :: check_password_fun(),
|
||||
auth_module :: atom(),
|
||||
host = <<"">> :: binary(),
|
||||
hostfqdn :: binary() | [binary()]}).
|
||||
-record(state, {step, nonce, username, authzid, get_password, check_password, auth_module,
|
||||
host}).
|
||||
|
||||
%% @spec (Opts) -> true
|
||||
%% Opts = term()
|
||||
|
||||
start(_Opts) ->
|
||||
Fqdn = get_local_fqdn(),
|
||||
?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~s",
|
||||
[Fqdn]),
|
||||
cyrsasl:register_mechanism(<<"DIGEST-MD5">>, ?MODULE,
|
||||
digest).
|
||||
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, digest).
|
||||
|
||||
stop() -> ok.
|
||||
%% @spec () -> ok
|
||||
|
||||
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}}.
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
mech_new(#sasl_params{host=Host, get_password=GetPassword,
|
||||
check_password_digest=CheckPasswordDigest}) ->
|
||||
{ok, #state{step = 1,
|
||||
nonce = randoms:get_string(),
|
||||
host = Host,
|
||||
get_password = GetPassword,
|
||||
check_password = CheckPasswordDigest}}.
|
||||
|
||||
%% @spec (State, ClientIn) -> Ok | Continue | Error
|
||||
%% State = mechstate()
|
||||
%% ClientIn = string()
|
||||
%% Ok = {ok, Props}
|
||||
%% Props = [Prop]
|
||||
%% Prop = {username, Username} | {authzid, AuthzId} | {auth_module, AuthModule}
|
||||
%% Username = string()
|
||||
%% AuthzId = string()
|
||||
%% AuthModule = atom()
|
||||
%% Continue = {continue, ServerOut, New_State}
|
||||
%% ServerOut = string()
|
||||
%% New_State = mechstate()
|
||||
%% Error = {error, Reason} | {error, Reason, Text, Username}
|
||||
%% Reason = term()
|
||||
|
||||
mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
|
||||
{continue,
|
||||
<<"nonce=\"", Nonce/binary,
|
||||
"\",qop=\"auth\",charset=utf-8,algorithm=md5-sess">>,
|
||||
"nonce=\"" ++ Nonce ++
|
||||
"\",qop=\"auth\",charset=utf-8,algorithm=md5-sess",
|
||||
State#state{step = 3}};
|
||||
mech_step(#state{step = 3, nonce = Nonce} = State,
|
||||
ClientIn) ->
|
||||
mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
bad -> {error, <<"bad-protocol">>};
|
||||
KeyVals ->
|
||||
DigestURI = proplists:get_value(<<"digest-uri">>, KeyVals, <<>>),
|
||||
%DigestURI = xml:get_attr_s(<<"digest-uri">>, KeyVals),
|
||||
UserName = proplists:get_value(<<"username">>, KeyVals, <<>>),
|
||||
%UserName = xml:get_attr_s(<<"username">>, KeyVals),
|
||||
case is_digesturi_valid(DigestURI, State#state.host,
|
||||
State#state.hostfqdn)
|
||||
of
|
||||
false ->
|
||||
?DEBUG("User login not authorized because digest-uri "
|
||||
"seems invalid: ~p (checking for Host "
|
||||
"~p, FQDN ~p)",
|
||||
[DigestURI, State#state.host, State#state.hostfqdn]),
|
||||
{error, <<"not-authorized">>, UserName};
|
||||
true ->
|
||||
AuthzId = proplists:get_value(<<"authzid">>, KeyVals, <<>>),
|
||||
%AuthzId = xml:get_attr_s(<<"authzid">>, KeyVals),
|
||||
case (State#state.get_password)(UserName) of
|
||||
{false, _} -> {error, <<"not-authorized">>, UserName};
|
||||
{Passwd, AuthModule} ->
|
||||
case (State#state.check_password)(UserName, <<"">>,
|
||||
proplists:get_value(<<"response">>, KeyVals, <<>>),
|
||||
%xml:get_attr_s(<<"response">>, KeyVals),
|
||||
fun (PW) ->
|
||||
response(KeyVals,
|
||||
UserName,
|
||||
PW,
|
||||
Nonce,
|
||||
AuthzId,
|
||||
<<"AUTHENTICATE">>)
|
||||
end)
|
||||
of
|
||||
{true, _} ->
|
||||
RspAuth = response(KeyVals, UserName, Passwd, Nonce,
|
||||
AuthzId, <<"">>),
|
||||
{continue, <<"rspauth=", RspAuth/binary>>,
|
||||
State#state{step = 5, auth_module = AuthModule,
|
||||
username = UserName,
|
||||
authzid = AuthzId}};
|
||||
false -> {error, <<"not-authorized">>, UserName};
|
||||
{false, _} -> {error, <<"not-authorized">>, UserName}
|
||||
end
|
||||
end
|
||||
end
|
||||
bad ->
|
||||
{error, 'malformed-request'};
|
||||
KeyVals ->
|
||||
DigestURI = proplists:get_value("digest-uri", KeyVals, ""),
|
||||
UserName = proplists:get_value("username", KeyVals, ""),
|
||||
case is_digesturi_valid(DigestURI, State#state.host) of
|
||||
false ->
|
||||
?DEBUG("User login not authorized because digest-uri "
|
||||
"seems invalid: ~p", [DigestURI]),
|
||||
{error, 'not-authorized', "", UserName};
|
||||
true ->
|
||||
AuthzId = proplists:get_value("authzid", KeyVals, ""),
|
||||
case (State#state.get_password)(UserName) of
|
||||
{false, _} ->
|
||||
{error, 'not-authorized', "", UserName};
|
||||
{Passwd, AuthModule} ->
|
||||
case (State#state.check_password)(UserName, "",
|
||||
proplists:get_value("response", KeyVals, ""),
|
||||
fun(PW) -> response(KeyVals, UserName, PW, Nonce, AuthzId,
|
||||
"AUTHENTICATE") end) of
|
||||
{true, _} ->
|
||||
RspAuth = response(KeyVals,
|
||||
UserName, Passwd,
|
||||
Nonce, AuthzId, ""),
|
||||
{continue,
|
||||
"rspauth=" ++ RspAuth,
|
||||
State#state{step = 5,
|
||||
auth_module = AuthModule,
|
||||
username = UserName,
|
||||
authzid = AuthzId}};
|
||||
false ->
|
||||
{error, 'not-authorized', "", UserName};
|
||||
{false, _} ->
|
||||
{error, 'not-authorized', "", UserName}
|
||||
end
|
||||
end
|
||||
end
|
||||
end;
|
||||
mech_step(#state{step = 5, auth_module = AuthModule,
|
||||
username = UserName, authzid = AuthzId},
|
||||
<<"">>) ->
|
||||
{ok,
|
||||
[{username, UserName}, {authzid, AuthzId},
|
||||
{auth_module, AuthModule}]};
|
||||
mech_step(#state{step = 5,
|
||||
auth_module = AuthModule,
|
||||
username = UserName,
|
||||
authzid = AuthzId}, "") ->
|
||||
{ok, [{username, UserName}, {authzid, AuthzId},
|
||||
{auth_module, AuthModule}]};
|
||||
mech_step(A, B) ->
|
||||
?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]),
|
||||
{error, <<"bad-protocol">>}.
|
||||
?DEBUG("SASL DIGEST: A ~p B ~p", [A,B]),
|
||||
{error, 'malformed-request'}.
|
||||
|
||||
parse(S) -> parse1(binary_to_list(S), "", []).
|
||||
%% @spec (S) -> [{Key, Value}] | bad
|
||||
%% S = string()
|
||||
%% Key = string()
|
||||
%% Value = string()
|
||||
|
||||
parse(S) ->
|
||||
parse1(S, "", []).
|
||||
|
||||
%% @hidden
|
||||
|
||||
parse1([$= | Cs], S, Ts) ->
|
||||
parse2(Cs, lists:reverse(S), "", Ts);
|
||||
parse1([$, | Cs], [], Ts) -> parse1(Cs, [], Ts);
|
||||
parse1([$\s | Cs], [], Ts) -> parse1(Cs, [], Ts);
|
||||
parse1([C | Cs], S, Ts) -> parse1(Cs, [C | S], Ts);
|
||||
parse1([], [], T) -> lists:reverse(T);
|
||||
parse1([], _S, _T) -> bad.
|
||||
parse1([$, | Cs], [], Ts) ->
|
||||
parse1(Cs, [], Ts);
|
||||
parse1([$\s | Cs], [], Ts) ->
|
||||
parse1(Cs, [], Ts);
|
||||
parse1([C | Cs], S, Ts) ->
|
||||
parse1(Cs, [C | S], Ts);
|
||||
parse1([], [], T) ->
|
||||
lists:reverse(T);
|
||||
parse1([], _S, _T) ->
|
||||
bad.
|
||||
|
||||
parse2([$" | Cs], Key, Val, Ts) ->
|
||||
%% @hidden
|
||||
|
||||
parse2([$\" | Cs], Key, Val, Ts) ->
|
||||
parse3(Cs, Key, Val, Ts);
|
||||
parse2([C | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, [C | Val], Ts);
|
||||
parse2([], _, _, _) -> bad.
|
||||
parse2([], _, _, _) ->
|
||||
bad.
|
||||
|
||||
parse3([$" | Cs], Key, Val, Ts) ->
|
||||
%% @hidden
|
||||
|
||||
parse3([$\" | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, Val, Ts);
|
||||
parse3([$\\, C | Cs], Key, Val, Ts) ->
|
||||
parse3(Cs, Key, [C | Val], Ts);
|
||||
parse3([C | Cs], Key, Val, Ts) ->
|
||||
parse3(Cs, Key, [C | Val], Ts);
|
||||
parse3([], _, _, _) -> bad.
|
||||
parse3([], _, _, _) ->
|
||||
bad.
|
||||
|
||||
%% @hidden
|
||||
|
||||
parse4([$, | Cs], Key, Val, Ts) ->
|
||||
parse1(Cs, "", [{list_to_binary(Key), list_to_binary(lists:reverse(Val))} | Ts]);
|
||||
parse1(Cs, "", [{Key, lists:reverse(Val)} | Ts]);
|
||||
parse4([$\s | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, Val, Ts);
|
||||
parse4([C | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, [C | Val], Ts);
|
||||
parse4([], Key, Val, Ts) ->
|
||||
parse1([], "", [{list_to_binary(Key), list_to_binary(lists:reverse(Val))} | Ts]).
|
||||
parse1([], "", [{Key, lists:reverse(Val)} | Ts]).
|
||||
|
||||
is_digesturi_valid(DigestURICase, JabberDomain,
|
||||
JabberFQDN) ->
|
||||
DigestURI = stringprep:tolower(DigestURICase),
|
||||
case catch str:tokens(DigestURI, <<"/">>) of
|
||||
[<<"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;
|
||||
|
||||
%% @spec (DigestURICase, JabberHost) -> bool()
|
||||
%% DigestURICase = string()
|
||||
%% JabberHost = string()
|
||||
%%
|
||||
%% @doc Check if the digest-uri is valid.
|
||||
%% RFC-2831 allows to provide the IP address in Host,
|
||||
%% however ejabberd doesn't allow that.
|
||||
%% If the service (for example jabber.example.org)
|
||||
%% is provided by several hosts (being one of them server3.example.org),
|
||||
%% then digest-uri can be like xmpp/server3.example.org/jabber.example.org
|
||||
%% In that case, ejabberd only checks the service name, not the host.
|
||||
|
||||
is_digesturi_valid(DigestURICase, JabberHost) ->
|
||||
DigestURI = exmpp_stringprep:to_lower(DigestURICase),
|
||||
case catch string:tokens(DigestURI, "/") of
|
||||
["xmpp", Host] when Host == JabberHost ->
|
||||
true;
|
||||
["xmpp", _Host, ServName] when ServName == JabberHost ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
is_host_fqdn(Host, Fqdn) when Host == Fqdn ->
|
||||
true;
|
||||
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);
|
||||
is_host_fqdn(_Host, _Fqdn) ->
|
||||
false.
|
||||
|
||||
get_local_fqdn() ->
|
||||
case catch get_local_fqdn2() of
|
||||
Str when is_binary(Str) -> Str;
|
||||
_ ->
|
||||
<<"unknown-fqdn, please configure fqdn "
|
||||
"option in ejabberd.cfg!">>
|
||||
end.
|
||||
|
||||
get_local_fqdn2() ->
|
||||
case ejabberd_config:get_local_option(
|
||||
fqdn, fun iolist_to_binary/1) of
|
||||
ConfiguredFqdn when is_binary(ConfiguredFqdn) ->
|
||||
ConfiguredFqdn;
|
||||
undefined ->
|
||||
{ok, Hostname} = inet:gethostname(),
|
||||
{ok, {hostent, Fqdn, _, _, _, _}} =
|
||||
inet:gethostbyname(Hostname),
|
||||
list_to_binary(Fqdn)
|
||||
end.
|
||||
|
||||
%% @hidden
|
||||
|
||||
digit_to_xchar(D) when (D >= 0) and (D < 10) ->
|
||||
D + 48;
|
||||
digit_to_xchar(D) ->
|
||||
D + 87.
|
||||
|
||||
%% @hidden
|
||||
|
||||
hex(S) ->
|
||||
sha:to_hexlist(S).
|
||||
hex(S, []).
|
||||
|
||||
proplists_get_bin_value(Key, Pairs, Default) ->
|
||||
case proplists:get_value(Key, Pairs, Default) of
|
||||
L when is_list(L) ->
|
||||
list_to_binary(L);
|
||||
L2 ->
|
||||
L2
|
||||
end.
|
||||
%% @hidden
|
||||
|
||||
response(KeyVals, User, Passwd, Nonce, AuthzId,
|
||||
A2Prefix) ->
|
||||
Realm = proplists_get_bin_value(<<"realm">>, KeyVals, <<>>),
|
||||
CNonce = proplists_get_bin_value(<<"cnonce">>, KeyVals, <<>>),
|
||||
DigestURI = proplists_get_bin_value(<<"digest-uri">>, KeyVals, <<>>),
|
||||
NC = proplists_get_bin_value(<<"nc">>, KeyVals, <<>>),
|
||||
QOP = proplists_get_bin_value(<<"qop">>, KeyVals, <<>>),
|
||||
MD5Hash = crypto:md5(<<User/binary, ":", Realm/binary, ":",
|
||||
Passwd/binary>>),
|
||||
hex([], Res) ->
|
||||
lists:reverse(Res);
|
||||
hex([N | Ns], Res) ->
|
||||
hex(Ns, [digit_to_xchar(N rem 16),
|
||||
digit_to_xchar(N div 16) | Res]).
|
||||
|
||||
|
||||
%% @spec (KeyVals, User, Passwd, Nonce, AuthzId, A2Prefix) -> string()
|
||||
%% KeyVals = [{Key, Value}]
|
||||
%% Key = string()
|
||||
%% Value = string()
|
||||
%% User = string()
|
||||
%% Passwd = string()
|
||||
%% Nonce = string()
|
||||
%% AuthzId = nil() | string()
|
||||
%% A2Prefix = string()
|
||||
|
||||
response(KeyVals, User, Passwd, Nonce, AuthzId, A2Prefix) ->
|
||||
Realm = proplists:get_value("realm", KeyVals, ""),
|
||||
CNonce = proplists:get_value("cnonce", KeyVals, ""),
|
||||
DigestURI = proplists:get_value("digest-uri", KeyVals, ""),
|
||||
NC = proplists:get_value("nc", KeyVals, ""),
|
||||
QOP = proplists:get_value("qop", KeyVals, ""),
|
||||
%% handle non-fully latin-1 strings as specified
|
||||
%% on RFC 2831 Section 2.1.2.1 (EJAB-476)
|
||||
SUser = sanitize(User),
|
||||
SPasswd = sanitize(Passwd),
|
||||
SRealm = sanitize(Realm),
|
||||
A1 = case AuthzId of
|
||||
<<"">> ->
|
||||
<<MD5Hash/binary, ":", Nonce/binary, ":", CNonce/binary>>;
|
||||
_ ->
|
||||
<<MD5Hash/binary, ":", Nonce/binary, ":", CNonce/binary, ":",
|
||||
AuthzId/binary>>
|
||||
"" ->
|
||||
binary_to_list(
|
||||
crypto:md5(SUser ++ ":" ++ SRealm ++ ":" ++ SPasswd)) ++
|
||||
":" ++ Nonce ++ ":" ++ CNonce;
|
||||
_ ->
|
||||
binary_to_list(
|
||||
crypto:md5(SUser ++ ":" ++ SRealm ++ ":" ++ SPasswd)) ++
|
||||
":" ++ Nonce ++ ":" ++ CNonce ++ ":" ++ AuthzId
|
||||
end,
|
||||
A2 = case QOP of
|
||||
<<"auth">> ->
|
||||
<<A2Prefix/binary, ":", DigestURI/binary>>;
|
||||
_ ->
|
||||
<<A2Prefix/binary, ":", DigestURI/binary,
|
||||
":00000000000000000000000000000000">>
|
||||
"auth" ->
|
||||
A2Prefix ++ ":" ++ DigestURI;
|
||||
_ ->
|
||||
A2Prefix ++ ":" ++ DigestURI ++
|
||||
":00000000000000000000000000000000"
|
||||
end,
|
||||
T = <<(hex((crypto:md5(A1))))/binary, ":", Nonce/binary,
|
||||
":", NC/binary, ":", CNonce/binary, ":", QOP/binary,
|
||||
":", (hex((crypto:md5(A2))))/binary>>,
|
||||
hex((crypto:md5(T))).
|
||||
T = hex(binary_to_list(crypto:md5(A1))) ++ ":" ++ Nonce ++ ":" ++
|
||||
NC ++ ":" ++ CNonce ++ ":" ++ QOP ++ ":" ++
|
||||
hex(binary_to_list(crypto:md5(A2))),
|
||||
hex(binary_to_list(crypto:md5(T))).
|
||||
|
||||
|
||||
sanitize(V) ->
|
||||
L = from_utf8(V),
|
||||
case lists:all(fun is_latin1/1, L) of
|
||||
true -> L;
|
||||
false -> V
|
||||
end.
|
||||
|
||||
%%%% copied from xmerl_ucs:from_utf8/1 and xmerl_ucs:is_latin1/1 , to not
|
||||
%%%% require xmerl as a dependency only for this.
|
||||
|
||||
from_utf8(Bin) when is_binary(Bin) -> from_utf8(binary_to_list(Bin));
|
||||
from_utf8(List) ->
|
||||
case expand_utf8(List) of
|
||||
{Result,0} -> Result;
|
||||
{_Res,_NumBadChar} ->
|
||||
exit({ucs,{bad_utf8_character_code}})
|
||||
end.
|
||||
|
||||
|
||||
|
||||
%% expand_utf8([Byte]) -> {[UnicodeChar],NumberOfBadBytes}
|
||||
%% Expand UTF8 byte sequences to ISO 10646/Unicode
|
||||
%% charactes. Any illegal bytes are removed and the number of
|
||||
%% bad bytes are returned.
|
||||
%%
|
||||
%% Reference:
|
||||
%% RFC 3629: "UTF-8, a transformation format of ISO 10646".
|
||||
|
||||
expand_utf8(Str) ->
|
||||
expand_utf8_1(Str, [], 0).
|
||||
|
||||
expand_utf8_1([C|Cs], Acc, Bad) when C < 16#80 ->
|
||||
%% Plain Ascii character.
|
||||
expand_utf8_1(Cs, [C|Acc], Bad);
|
||||
expand_utf8_1([C1,C2|Cs], Acc, Bad) when C1 band 16#E0 =:= 16#C0,
|
||||
C2 band 16#C0 =:= 16#80 ->
|
||||
case ((C1 band 16#1F) bsl 6) bor (C2 band 16#3F) of
|
||||
C when 16#80 =< C ->
|
||||
expand_utf8_1(Cs, [C|Acc], Bad);
|
||||
_ ->
|
||||
%% Bad range.
|
||||
expand_utf8_1(Cs, Acc, Bad+1)
|
||||
end;
|
||||
expand_utf8_1([C1,C2,C3|Cs], Acc, Bad) when C1 band 16#F0 =:= 16#E0,
|
||||
C2 band 16#C0 =:= 16#80,
|
||||
C3 band 16#C0 =:= 16#80 ->
|
||||
case ((((C1 band 16#0F) bsl 6) bor (C2 band 16#3F)) bsl 6) bor
|
||||
(C3 band 16#3F) of
|
||||
C when 16#800 =< C ->
|
||||
expand_utf8_1(Cs, [C|Acc], Bad);
|
||||
_ ->
|
||||
%% Bad range.
|
||||
expand_utf8_1(Cs, Acc, Bad+1)
|
||||
end;
|
||||
expand_utf8_1([C1,C2,C3,C4|Cs], Acc, Bad) when C1 band 16#F8 =:= 16#F0,
|
||||
C2 band 16#C0 =:= 16#80,
|
||||
C3 band 16#C0 =:= 16#80,
|
||||
C4 band 16#C0 =:= 16#80 ->
|
||||
case ((((((C1 band 16#0F) bsl 6) bor (C2 band 16#3F)) bsl 6) bor
|
||||
(C3 band 16#3F)) bsl 6) bor (C4 band 16#3F) of
|
||||
C when 16#10000 =< C ->
|
||||
expand_utf8_1(Cs, [C|Acc], Bad);
|
||||
_ ->
|
||||
%% Bad range.
|
||||
expand_utf8_1(Cs, Acc, Bad+1)
|
||||
end;
|
||||
expand_utf8_1([_|Cs], Acc, Bad) ->
|
||||
%% Ignore bad character.
|
||||
expand_utf8_1(Cs, Acc, Bad+1);
|
||||
expand_utf8_1([], Acc, Bad) -> {lists:reverse(Acc),Bad}.
|
||||
|
||||
|
||||
%%% Test for legitimate Latin-1 code
|
||||
is_latin1(Ch) when is_integer(Ch), Ch >= 0, Ch =< 255 -> true;
|
||||
is_latin1(_) -> false.
|
||||
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl_gssapi.erl
|
||||
%%% Author : Mikael Magnusson <mikma@users.sourceforge.net>
|
||||
%%% Purpose : GSSAPI SASL mechanism
|
||||
%%% Created : 1 June 2007 by Mikael Magnusson <mikma@users.sourceforge.net>
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% Copyright (C) 2007-2009 Mikael Magnusson <mikma@users.sourceforge.net>
|
||||
%%%
|
||||
%%% Permission is hereby granted, free of charge, to any person
|
||||
%%% obtaining a copy of this software and associated documentation
|
||||
%%% files (the "Software"), to deal in the Software without
|
||||
%%% restriction, including without limitation the rights to use, copy,
|
||||
%%% modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
%%% of the Software, and to permit persons to whom the Software is
|
||||
%%% furnished to do so, subject to the following conditions:
|
||||
%%%
|
||||
%%% The above copyright notice and this permission notice shall be
|
||||
%%% included in all copies or substantial portions of the Software.
|
||||
%%%
|
||||
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
%%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
%%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
%%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
%%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
%%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
%%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%%% SOFTWARE.
|
||||
%%%
|
||||
|
||||
%%%
|
||||
%%% configuration options:
|
||||
%%% {sasl_realm, "<Kerberos realm>"}.
|
||||
%%%
|
||||
%%% environment variables:
|
||||
%%% KRB5_KTNAME
|
||||
%%%
|
||||
|
||||
-module(cyrsasl_gssapi).
|
||||
-author('mikma@users.sourceforge.net').
|
||||
|
||||
-export([start/1,
|
||||
stop/0,
|
||||
mech_new/1,
|
||||
mech_step/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("cyrsasl.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
-define(SERVICE, "xmpp").
|
||||
|
||||
-record(state, {sasl,
|
||||
needsmore=true,
|
||||
step=0,
|
||||
host,
|
||||
realm,
|
||||
authid,
|
||||
authzid,
|
||||
authrealm,
|
||||
error}).
|
||||
|
||||
start(_Opts) ->
|
||||
ChildSpec =
|
||||
{?SERVER,
|
||||
{esasl, start_link, [{local, ?SERVER}]},
|
||||
transient,
|
||||
1000,
|
||||
worker,
|
||||
[esasl]},
|
||||
|
||||
case supervisor:start_child(ejabberd_sup, ChildSpec) of
|
||||
{ok, _Pid} ->
|
||||
cyrsasl:register_mechanism("GSSAPI", ?MODULE, false);
|
||||
{error, Error} = E ->
|
||||
?ERROR_MSG("esasl failed: ~p", [Error]),
|
||||
E
|
||||
end.
|
||||
|
||||
|
||||
stop() ->
|
||||
catch esasl:stop(?SERVER),
|
||||
supervisor:terminate_child(ejabberd_sup, ?SERVER),
|
||||
supervisor:delete_child(ejabberd_sup, ?SERVER).
|
||||
|
||||
mech_new(#sasl_params{host=Host, realm=Realm, socket=Socket}) ->
|
||||
case ejabberd_socket:gethostname(Socket) of
|
||||
{ok, FQDN} ->
|
||||
?DEBUG("mech_new ~p ~p ~p~n", [Host, Realm, FQDN]),
|
||||
case esasl:server_start(?SERVER, "GSSAPI", ?SERVICE, FQDN) of
|
||||
{ok, Sasl} ->
|
||||
{ok, #state{sasl=Sasl,host=Host,realm=Realm}};
|
||||
{error, {gsasl_error, Error}} ->
|
||||
{ok, Str} = esasl:str_error(?SERVER, Error),
|
||||
?DEBUG("esasl error: ~p", [Str]),
|
||||
{ok, #state{needsmore=error,error="internal-server-error"}};
|
||||
{error, Error} ->
|
||||
?DEBUG("esasl error: ~p", [Error]),
|
||||
{ok, #state{needsmore=error,error="internal-server-error"}}
|
||||
end;
|
||||
{error, Error} ->
|
||||
?DEBUG("gethostname error: ~p", [Error]),
|
||||
{ok, #state{needsmore=error,error="internal-server-error"}}
|
||||
end.
|
||||
|
||||
mech_step(State, ClientIn) when is_list(ClientIn) ->
|
||||
catch do_step(State, ClientIn).
|
||||
|
||||
do_step(#state{needsmore=error,error=Error}=_State, _) ->
|
||||
{error, Error};
|
||||
do_step(#state{needsmore=false}=State, _) ->
|
||||
check_user(State);
|
||||
do_step(#state{needsmore=true,sasl=Sasl,step=Step}=State, ClientIn) ->
|
||||
?DEBUG("mech_step~n", []),
|
||||
case esasl:step(Sasl, list_to_binary(ClientIn)) of
|
||||
{ok, RspAuth} ->
|
||||
?DEBUG("ok~n", []),
|
||||
{ok, Display_name} = esasl:property_get(Sasl, gssapi_display_name),
|
||||
{ok, Authzid} = esasl:property_get(Sasl, authzid),
|
||||
{Authid, [$@ | Auth_realm]} =
|
||||
lists:splitwith(fun(E)->E =/= $@ end, Display_name),
|
||||
State1 = State#state{authid=Authid,
|
||||
authzid=Authzid,
|
||||
authrealm=Auth_realm},
|
||||
handle_step_ok(State1, binary_to_list(RspAuth));
|
||||
{needsmore, RspAuth} ->
|
||||
?DEBUG("needsmore~n", []),
|
||||
if (Step > 0) and (ClientIn =:= []) and (RspAuth =:= <<>>) ->
|
||||
{error, 'not-authorized'};
|
||||
true ->
|
||||
{continue, binary_to_list(RspAuth),
|
||||
State#state{step=Step+1}}
|
||||
end;
|
||||
{error, _} ->
|
||||
{error, 'not-authorized'}
|
||||
end.
|
||||
|
||||
handle_step_ok(State, []) ->
|
||||
check_user(State);
|
||||
handle_step_ok(#state{step=Step}=State, RspAuth) ->
|
||||
?DEBUG("continue~n", []),
|
||||
{continue, RspAuth, State#state{needsmore=false,step=Step+1}}.
|
||||
|
||||
check_user(#state{authid=Authid,authzid=Authzid,
|
||||
authrealm=Auth_realm,host=Host,realm=Realm}) ->
|
||||
if Realm =/= Auth_realm ->
|
||||
?DEBUG("bad realm ~p (expected ~p)~n",[Auth_realm, Realm]),
|
||||
throw({error, 'not-authorized'});
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
|
||||
case ejabberd_auth:is_user_exists(Authid, Host) of
|
||||
false ->
|
||||
?DEBUG("bad user ~p~n",[Authid]),
|
||||
throw({error, 'not-authorized'});
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
|
||||
?DEBUG("GSSAPI authenticated ~p ~p~n", [Authid, Authzid]),
|
||||
{ok, [{username, Authid}, {authzid, Authzid}]}.
|
||||
+69
-32
@@ -5,7 +5,7 @@
|
||||
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -25,66 +25,103 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(cyrsasl_plain).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1]).
|
||||
-export([start/1, stop/0, mech_new/1, mech_step/2, parse/1]).
|
||||
|
||||
-include("cyrsasl.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
%% @type mechstate() = {state, CheckPassword}
|
||||
%% CheckPassword = function().
|
||||
|
||||
-record(state, {check_password}).
|
||||
|
||||
%% @spec (Opts) -> true
|
||||
%% Opts = term()
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism(<<"PLAIN">>, ?MODULE, plain),
|
||||
cyrsasl:register_mechanism("PLAIN", ?MODULE, plain),
|
||||
ok.
|
||||
|
||||
stop() -> ok.
|
||||
%% @spec () -> ok
|
||||
|
||||
mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) ->
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
mech_new(#sasl_params{check_password = CheckPassword}) ->
|
||||
{ok, #state{check_password = CheckPassword}}.
|
||||
|
||||
%% @spec (State, ClientIn) -> Ok | Error
|
||||
%% State = mechstate()
|
||||
%% ClientIn = string()
|
||||
%% Ok = {ok, Props}
|
||||
%% Props = [Prop]
|
||||
%% Prop = {username, Username} | {authzid, AuthzId} | {auth_module, AuthModule}
|
||||
%% Username = string()
|
||||
%% AuthzId = string()
|
||||
%% AuthModule = atom()
|
||||
%% Error = {error, Reason} | {error, Reason, Text, Username}
|
||||
|
||||
mech_step(State, ClientIn) ->
|
||||
case prepare(ClientIn) of
|
||||
[AuthzId, User, Password] ->
|
||||
case (State#state.check_password)(User, Password) of
|
||||
{true, AuthModule} ->
|
||||
{ok,
|
||||
[{username, User}, {authzid, AuthzId},
|
||||
{auth_module, AuthModule}]};
|
||||
_ -> {error, <<"not-authorized">>, User}
|
||||
end;
|
||||
_ -> {error, <<"bad-protocol">>}
|
||||
[AuthzId, User, Password] ->
|
||||
case (State#state.check_password)(User, Password) of
|
||||
{true, AuthModule} ->
|
||||
{ok, [{username, User}, {authzid, AuthzId},
|
||||
{auth_module, AuthModule}]};
|
||||
{false, ReasonAuthFail} when is_list(ReasonAuthFail) ->
|
||||
{error, 'not-authorized', ReasonAuthFail, User};
|
||||
_ ->
|
||||
{error, 'not-authorized', "", User}
|
||||
end;
|
||||
_ ->
|
||||
{error, 'malformed-request'}
|
||||
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
|
||||
[[], 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(binary_to_list(S), "", []).
|
||||
|
||||
%% @hidden
|
||||
|
||||
parse(S) ->
|
||||
parse1(S, "", []).
|
||||
|
||||
%% @hidden
|
||||
|
||||
parse1([0 | Cs], S, T) ->
|
||||
parse1(Cs, "", [list_to_binary(lists:reverse(S)) | T]);
|
||||
parse1([C | Cs], S, T) -> parse1(Cs, [C | S], T);
|
||||
parse1(Cs, "", [lists:reverse(S) | T]);
|
||||
parse1([C | Cs], S, T) ->
|
||||
parse1(Cs, [C | S], T);
|
||||
%parse1([], [], T) ->
|
||||
% lists:reverse(T);
|
||||
parse1([], S, T) ->
|
||||
lists:reverse([list_to_binary(lists:reverse(S)) | T]).
|
||||
lists:reverse([lists:reverse(S) | T]).
|
||||
|
||||
parse_domain(S) -> parse_domain1(binary_to_list(S), "", []).
|
||||
|
||||
parse_domain(S) ->
|
||||
parse_domain1(S, "", []).
|
||||
|
||||
parse_domain1([$@ | Cs], S, T) ->
|
||||
parse_domain1(Cs, "", [list_to_binary(lists:reverse(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([list_to_binary(lists:reverse(S)) | T]).
|
||||
lists:reverse([lists:reverse(S) | T]).
|
||||
|
||||
+142
-160
@@ -5,7 +5,7 @@
|
||||
%%% Created : 7 Aug 2011 by Stephen Röttger <stephen.roettger@googlemail.com>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -25,185 +25,167 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(cyrsasl_scram).
|
||||
|
||||
-author('stephen.roettger@googlemail.com').
|
||||
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2]).
|
||||
-export([start/1,
|
||||
stop/0,
|
||||
mech_new/1,
|
||||
mech_step/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("cyrsasl.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state,
|
||||
{step = 2 :: 2 | 4,
|
||||
stored_key = <<"">> :: binary(),
|
||||
server_key = <<"">> :: binary(),
|
||||
username = <<"">> :: binary(),
|
||||
get_password :: fun(),
|
||||
check_password :: fun(),
|
||||
auth_message = <<"">> :: binary(),
|
||||
client_nonce = <<"">> :: binary(),
|
||||
server_nonce = <<"">> :: binary()}).
|
||||
-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).
|
||||
cyrsasl:register_mechanism("SCRAM-SHA-1", ?MODULE, scram).
|
||||
|
||||
stop() -> ok.
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
mech_new(_Host, GetPassword, _CheckPassword,
|
||||
_CheckPasswordDigest) ->
|
||||
mech_new(#sasl_params{get_password=GetPassword}) ->
|
||||
{ok, #state{step = 2, get_password = GetPassword}}.
|
||||
|
||||
mech_step(#state{step = 2} = State, ClientIn) ->
|
||||
case str: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 =
|
||||
str:substr(ClientIn,
|
||||
str:str(ClientIn, <<"n=">>)),
|
||||
ServerNonce =
|
||||
jlib:encode_base64(crypto:rand_bytes(?NONCE_LENGTH)),
|
||||
ServerFirstMessage =
|
||||
iolist_to_binary(
|
||||
["r=",
|
||||
ClientNonce,
|
||||
ServerNonce,
|
||||
",", "s=",
|
||||
jlib:encode_base64(Salt),
|
||||
",", "i=",
|
||||
integer_to_list(IterationCount)]),
|
||||
{continue, ServerFirstMessage,
|
||||
State#state{step = 4, stored_key = StoredKey,
|
||||
server_key = ServerKey,
|
||||
auth_message =
|
||||
<<ClientFirstMessageBare/binary,
|
||||
",", ServerFirstMessage/binary>>,
|
||||
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 str: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)/binary,
|
||||
(State#state.server_nonce)/binary>>,
|
||||
case parse_attribute(NonceAttribute) of
|
||||
{$r, CompareNonce} when CompareNonce == Nonce ->
|
||||
case parse_attribute(ClientProofAttribute) of
|
||||
{$p, ClientProofB64} ->
|
||||
ClientProof = jlib:decode_base64(ClientProofB64),
|
||||
AuthMessage =
|
||||
iolist_to_binary(
|
||||
[State#state.auth_message,
|
||||
",",
|
||||
str:substr(ClientIn, 1,
|
||||
str: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=",
|
||||
(jlib:encode_base64(ServerSignature))/binary>>};
|
||||
true -> {error, <<"bad-auth">>}
|
||||
end;
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
end;
|
||||
{$r, _} -> {error, <<"bad-nonce">>};
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
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, 'malformed-request', "Error in username encoding", EscapedUserName};
|
||||
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, 'malformed-request'}
|
||||
end
|
||||
end
|
||||
end;
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
end;
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
end.
|
||||
_Else ->
|
||||
{error, 'malformed-request'}
|
||||
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, 'not-authorized', "", State#state.username}
|
||||
end;
|
||||
_Else ->
|
||||
{error, 'malformed-request', "Bad protocol", State#state.username}
|
||||
end;
|
||||
{$r, _} ->
|
||||
{error, 'malformed-request', "Bad nonce", State#state.username};
|
||||
_Else ->
|
||||
{error, 'malformed-request', "Bad protocol", State#state.username}
|
||||
end;
|
||||
_Else ->
|
||||
{error, 'malformed-request', "Bad protocol", State#state.username}
|
||||
end;
|
||||
_Else ->
|
||||
{error, 'malformed-request', "Bad protocol", State#state.username}
|
||||
end.
|
||||
|
||||
parse_attribute(Attribute) ->
|
||||
AttributeLen = byte_size(Attribute),
|
||||
if AttributeLen >= 3 ->
|
||||
AttributeS = binary_to_list(Attribute),
|
||||
SecondChar = lists:nth(2, AttributeS),
|
||||
case is_alpha(lists:nth(1, AttributeS)) of
|
||||
true ->
|
||||
if SecondChar == $= ->
|
||||
String = str:substr(Attribute, 3),
|
||||
{lists:nth(1, AttributeS), 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.
|
||||
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, 'malformed-request', "Second char not equal sign", ""}
|
||||
end;
|
||||
_Else ->
|
||||
{error, 'malformed-request', "First char not a letter", ""}
|
||||
end;
|
||||
true ->
|
||||
{error, 'malformed-request', "Attribute too short", ""}
|
||||
end.
|
||||
|
||||
unescape_username(<<"">>) -> <<"">>;
|
||||
unescape_username("") ->
|
||||
"";
|
||||
unescape_username(EscapedUsername) ->
|
||||
Pos = str:str(EscapedUsername, <<"=">>),
|
||||
if Pos == 0 -> EscapedUsername;
|
||||
true ->
|
||||
Start = str:substr(EscapedUsername, 1, Pos - 1),
|
||||
End = str:substr(EscapedUsername, Pos),
|
||||
EndLen = byte_size(End),
|
||||
if EndLen < 3 -> error;
|
||||
true ->
|
||||
case str:substr(End, 1, 3) of
|
||||
<<"=2C">> ->
|
||||
<<Start/binary, ",",
|
||||
(unescape_username(str:substr(End, 4)))/binary>>;
|
||||
<<"=3D">> ->
|
||||
<<Start/binary, "=",
|
||||
(unescape_username(str:substr(End, 4)))/binary>>;
|
||||
_Else -> error
|
||||
end
|
||||
end
|
||||
end.
|
||||
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.
|
||||
|
||||
is_alpha(Char) when Char >= $a, Char =< $z -> true;
|
||||
is_alpha(Char) when Char >= $A, Char =< $Z -> true;
|
||||
is_alpha(_) -> false.
|
||||
|
||||
+2
-11
@@ -1,8 +1,6 @@
|
||||
%% $Id$
|
||||
|
||||
{application, ejabberd,
|
||||
[{description, "ejabberd"},
|
||||
{vsn, "3.0.0"},
|
||||
{vsn, "3.0.0-alpha-4"},
|
||||
{modules, [acl,
|
||||
adhoc,
|
||||
configure,
|
||||
@@ -16,10 +14,9 @@
|
||||
ejabberd_auth_anonymous,
|
||||
ejabberd_auth,
|
||||
ejabberd_auth_external,
|
||||
ejabberd_auth_internal,
|
||||
ejabberd_auth_ldap,
|
||||
ejabberd_auth_odbc,
|
||||
ejabberd_auth_pam,
|
||||
ejabberd_auth_storage,
|
||||
ejabberd,
|
||||
ejabberd_c2s,
|
||||
ejabberd_c2s_config,
|
||||
@@ -51,7 +48,6 @@
|
||||
ejabberd_update,
|
||||
ejabberd_web_admin,
|
||||
ejabberd_web,
|
||||
ejabberd_zlib,
|
||||
ejd2odbc,
|
||||
eldap,
|
||||
eldap_filter,
|
||||
@@ -63,7 +59,6 @@
|
||||
gen_mod,
|
||||
gen_pubsub_node,
|
||||
gen_pubsub_nodetree,
|
||||
iconv,
|
||||
idna,
|
||||
jd2ejd,
|
||||
jlib,
|
||||
@@ -76,8 +71,6 @@
|
||||
mod_echo,
|
||||
mod_http_bind,
|
||||
mod_http_fileserver,
|
||||
mod_irc,
|
||||
mod_irc_connection,
|
||||
mod_last,
|
||||
mod_muc,
|
||||
mod_muc_log,
|
||||
@@ -120,7 +113,6 @@
|
||||
tls,
|
||||
translate,
|
||||
xml,
|
||||
xml_stream,
|
||||
'XmppAddr'
|
||||
]},
|
||||
{registered, [ejabberd,
|
||||
@@ -140,7 +132,6 @@
|
||||
ejabberd_mod_roster,
|
||||
ejabberd_mod_echo,
|
||||
ejabberd_mod_pubsub,
|
||||
ejabberd_mod_irc,
|
||||
ejabberd_mod_muc,
|
||||
ejabberd_offline,
|
||||
random_generator
|
||||
|
||||
+101
-65
@@ -80,14 +80,24 @@
|
||||
%%{watchdog_admins, ["bob@example.com"]}.
|
||||
|
||||
|
||||
%%%. =====================
|
||||
%%%' CLUSTER CONFIGURATION
|
||||
|
||||
%% clusterid: the integer id of the cluster of nodes this node will
|
||||
%% belong to.
|
||||
{clusterid, 1}.
|
||||
|
||||
|
||||
%%%. ================
|
||||
%%%' SERVED HOSTNAMES
|
||||
|
||||
%%
|
||||
%% hosts: Domains served by ejabberd.
|
||||
%% You can define one or several, for example:
|
||||
%% {hosts, ["example.net", "example.com", "example.org"]}.
|
||||
%% {hosts, ["localhost", "example.net", "example.com", "example.org"]}.
|
||||
%%
|
||||
%% Additional hosts will be discovered from the 'hosts' database table
|
||||
%% configured for the first host.
|
||||
{hosts, ["localhost"]}.
|
||||
|
||||
%%
|
||||
@@ -154,13 +164,6 @@
|
||||
%%
|
||||
%%{{3478, udp}, ejabberd_stun, []},
|
||||
|
||||
%%
|
||||
%% To handle XML-RPC requests that provide admin credentials:
|
||||
%%
|
||||
%%{4560, ejabberd_xmlrpc, [
|
||||
%% {access_commands, [{admin, all, []}]}
|
||||
%% ]},
|
||||
|
||||
{5280, ejabberd_http, [
|
||||
%%{request_handlers,
|
||||
%% [
|
||||
@@ -220,18 +223,19 @@
|
||||
|
||||
%%
|
||||
%% auth_method: Method used to authenticate the users.
|
||||
%% The default method is the internal.
|
||||
%% The default method is the internal Mnesia database storage.
|
||||
%% If you want to use a different method,
|
||||
%% comment this line and enable the correct ones.
|
||||
%% comment those lines and enable the desired lines later.
|
||||
%%
|
||||
{auth_method, internal}.
|
||||
{auth_method, storage}.
|
||||
{auth_storage, mnesia}.
|
||||
|
||||
%%
|
||||
%% Store the plain passwords or hashed for SCRAM:
|
||||
%%{auth_password_format, plain}.
|
||||
%%{auth_password_format, scram}.
|
||||
%% Authentication using ODBC
|
||||
%% Remember to setup a database in the next section.
|
||||
%%
|
||||
%% Define the FQDN if ejabberd doesn't detect it:
|
||||
%%{fqdn, "server3.example.com"}.
|
||||
%%{auth_method, storage}.
|
||||
%%{auth_storage, odbc}.
|
||||
|
||||
%%
|
||||
%% Authentication using external script
|
||||
@@ -240,12 +244,6 @@
|
||||
%%{auth_method, external}.
|
||||
%%{extauth_program, "/path/to/authentication/script"}.
|
||||
|
||||
%%
|
||||
%% Authentication using ODBC
|
||||
%% Remember to setup a database in the next section.
|
||||
%%
|
||||
%%{auth_method, odbc}.
|
||||
|
||||
%%
|
||||
%% Authentication using PAM
|
||||
%%
|
||||
@@ -297,6 +295,15 @@
|
||||
%%
|
||||
%%{host_config, "public.example.org", [{auth_method, [internal, anonymous]}]}.
|
||||
|
||||
%%
|
||||
%% auth_password_format: Format of storing users passwords
|
||||
%% The default format is plain text.
|
||||
%% If you change to hashed scram, you can never go back to plain.
|
||||
%% This option is only supported by the 'storage' auth_method.
|
||||
%%
|
||||
{auth_password_format, plain}.
|
||||
%%{auth_password_format, scram}.
|
||||
|
||||
|
||||
%%%. ==============
|
||||
%%%' DATABASE SETUP
|
||||
@@ -307,22 +314,32 @@
|
||||
%% you want to use other database backends.
|
||||
%% Please consult the ejabberd Guide for details on database creation.
|
||||
|
||||
%% Template host ODBC configuration.
|
||||
%% The host specified here will be used by default for all discovered vhosts.
|
||||
%% The actual ODBC server configuration must be provided in a host_config directive.
|
||||
{odbc_server, {host, "localhost"}}.
|
||||
|
||||
{host_config, "localhost",
|
||||
[
|
||||
%%
|
||||
%% MySQL server:
|
||||
%%
|
||||
%%{odbc_server, {mysql, "server", "database", "username", "password"}}.
|
||||
%%{odbc_server, {mysql, "server", "database", "username", "password"}}
|
||||
%%
|
||||
%% If you want to specify the port:
|
||||
%%{odbc_server, {mysql, "server", 1234, "database", "username", "password"}}.
|
||||
%%{odbc_server, {mysql, "server", 1234, "database", "username", "password"}}
|
||||
|
||||
%%
|
||||
%% PostgreSQL server:
|
||||
%%
|
||||
%%{odbc_server, {pgsql, "server", "database", "username", "password"}}.
|
||||
%%{odbc_server, {pgsql, "server", "database", "username", "password"}}
|
||||
%%
|
||||
%% If you want to specify the port:
|
||||
%%{odbc_server, {pgsql, "server", 1234, "database", "username", "password"}}.
|
||||
%%{odbc_server, {pgsql, "server", 1234, "database", "username", "password"}}
|
||||
%%
|
||||
]
|
||||
}.
|
||||
|
||||
%% If you use PostgreSQL, have a large database, and need a
|
||||
%% faster but inexact replacement for "select count(*) from users"
|
||||
%%
|
||||
@@ -464,6 +481,19 @@
|
||||
%%}.
|
||||
|
||||
|
||||
%%%. =======
|
||||
%%%' CAPTCHA
|
||||
|
||||
%%
|
||||
%% Full path to a script that generates the image.
|
||||
%%
|
||||
%%{captcha_cmd, "/lib/ejabberd/priv/bin/captcha.sh"}.
|
||||
|
||||
%% Host for the URL and port where ejabberd listens for CAPTCHA requests.
|
||||
%%
|
||||
%%{captcha_host, "example.org:5280"}.
|
||||
|
||||
|
||||
%%%. ================
|
||||
%%%' DEFAULT LANGUAGE
|
||||
|
||||
@@ -480,67 +510,51 @@
|
||||
%%}.
|
||||
|
||||
|
||||
%%%. =======
|
||||
%%%' CAPTCHA
|
||||
%%%. ================
|
||||
%%%' INSTANCE MODULES
|
||||
|
||||
%%
|
||||
%% Full path to a script that generates the image.
|
||||
%% Modules to start for every virtual host.
|
||||
%%
|
||||
%%{captcha_cmd, "/lib/ejabberd/priv/bin/captcha.sh"}.
|
||||
|
||||
%%
|
||||
%% Host for the URL and port where ejabberd listens for CAPTCHA requests.
|
||||
%%
|
||||
%%{captcha_host, "example.org:5280"}.
|
||||
|
||||
%%
|
||||
%% Limit CAPTCHA calls per minute for JID/IP to avoid DoS.
|
||||
%%
|
||||
%%{captcha_limit, 5}.
|
||||
|
||||
%%%. =======
|
||||
%%%' MODULES
|
||||
|
||||
%%
|
||||
%% Modules enabled in all ejabberd virtual hosts.
|
||||
%% Note: One copy of each module will be started for each host.
|
||||
%%
|
||||
{modules,
|
||||
[
|
||||
{mod_adhoc, []},
|
||||
{mod_announce, [{access, announce}]}, % recommends mod_adhoc
|
||||
{mod_blocking,[]}, % requires mod_privacy
|
||||
{mod_caps, []},
|
||||
{mod_caps, []}, % 1 proc/host
|
||||
{mod_configure,[]}, % requires mod_adhoc
|
||||
{mod_disco, []},
|
||||
%%{mod_echo, [{host, "echo.localhost"}]},
|
||||
{mod_irc, []},
|
||||
{mod_http_bind, []},
|
||||
%%{mod_http_fileserver, [
|
||||
%% {docroot, "/var/www"},
|
||||
%% {accesslog, "/var/log/ejabberd/access.log"}
|
||||
%% ]},
|
||||
{mod_last, []},
|
||||
{mod_muc, [
|
||||
%%{host, "conference.@HOST@"},
|
||||
{access, muc},
|
||||
{access_create, muc_create},
|
||||
{access_persistent, muc_create},
|
||||
{access_admin, muc_admin}
|
||||
]},
|
||||
{mod_muc, [ % 1 proc/host
|
||||
{host, "conference.@HOST@"},
|
||||
{access, muc},
|
||||
{access_create, muc_create},
|
||||
{access_persistent, muc_create},
|
||||
{access_admin, muc_admin}
|
||||
]},
|
||||
%%{mod_muc_log,[]},
|
||||
%%{mod_multicast,[]},
|
||||
{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}, % 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_pubsub, [ % 1 proc/host
|
||||
%% {access_createnode, pubsub_createnode},
|
||||
%% {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", "pep"]} % pep requires mod_caps
|
||||
%% ]},
|
||||
{mod_register, [
|
||||
%%
|
||||
%% Protect In-Band account registrations with CAPTCHA.
|
||||
@@ -590,10 +604,34 @@
|
||||
{mod_shared_roster,[]},
|
||||
{mod_stats, []},
|
||||
{mod_time, []},
|
||||
{mod_vcard, []},
|
||||
{mod_vcard, []}, % 1 proc/host
|
||||
{mod_version, []}
|
||||
]}.
|
||||
|
||||
|
||||
%%%.
|
||||
%%%' STATIC MODULES
|
||||
|
||||
%%
|
||||
%% Modules to start for the global virtual host ("localhost").
|
||||
%%
|
||||
%% The functionality of these modules will be enabled in all ejabberd
|
||||
%% virtual hosts without consuming more resources.
|
||||
%%
|
||||
{static_modules,
|
||||
[
|
||||
%% {mod_adhoc, []},
|
||||
%% {mod_announce, []},
|
||||
%% {mod_disco, []},
|
||||
%% {mod_echo, [{prefix, "echo"}]},
|
||||
%% {mod_last, []},
|
||||
%% {mod_offline, []}
|
||||
]}.
|
||||
|
||||
|
||||
%%%.
|
||||
%%%' HOST SPECIFIC MODULES
|
||||
|
||||
%%
|
||||
%% Enable modules with custom options in a specific virtual host
|
||||
%%
|
||||
@@ -609,8 +647,6 @@
|
||||
%%%.
|
||||
%%%'
|
||||
|
||||
%%% $Id$
|
||||
|
||||
%%% Local Variables:
|
||||
%%% mode: erlang
|
||||
%%% End:
|
||||
|
||||
+36
-3
@@ -5,7 +5,7 @@
|
||||
%%% Created : 16 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -28,8 +28,14 @@
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0, stop/0,
|
||||
get_pid_file/0,
|
||||
get_so_path/0, get_bin_path/0]).
|
||||
get_so_path/0,
|
||||
get_bin_path/0,
|
||||
get_pid_file/0,
|
||||
is_my_host/1,
|
||||
normalize_host/1
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
start() ->
|
||||
%%ejabberd_cover:start(),
|
||||
@@ -65,6 +71,33 @@ get_bin_path() ->
|
||||
Path
|
||||
end.
|
||||
|
||||
is_my_host([$* | _]) ->
|
||||
false;
|
||||
is_my_host(Host) ->
|
||||
case ejabberd_config:get_local_option({Host, host}) of
|
||||
undefined ->
|
||||
WCHost = re:replace(Host, "^[^.]*\.", "*.", [{return, list}]),
|
||||
case ejabberd_config:get_local_option({WCHost, host}) of
|
||||
undefined ->
|
||||
false;
|
||||
_ ->
|
||||
true
|
||||
end;
|
||||
_ ->
|
||||
true
|
||||
end.
|
||||
|
||||
normalize_host(global) ->
|
||||
global;
|
||||
normalize_host(Host) ->
|
||||
WCHost = re:replace(Host, "^[^.]*\.", "*.", [{return, list}]),
|
||||
case ejabberd_config:get_local_option({WCHost, host}) of
|
||||
undefined ->
|
||||
Host;
|
||||
_ ->
|
||||
WCHost
|
||||
end.
|
||||
|
||||
%% @spec () -> false | string()
|
||||
get_pid_file() ->
|
||||
case os:getenv("EJABBERD_PID_PATH") of
|
||||
|
||||
+26
-38
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -19,67 +19,55 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-define(VERSION, ejabberd_config:get_version()).
|
||||
%% This macro returns a string of the ejabberd version running, e.g. "2.3.4"
|
||||
%% If the ejabberd application description isn't loaded, returns atom: undefined
|
||||
-define(VERSION, element(2, application:get_key(ejabberd,vsn))).
|
||||
|
||||
-define(MYHOSTS, ejabberd_config:get_myhosts()).
|
||||
-define(IS_MY_HOST(Host), ejabberd:is_my_host(Host)).
|
||||
-define(MYHOSTS, ejabberd_config:get_global_option(hosts)). %% Deprecated
|
||||
-define(MYNAME, hd(ejabberd_config:get_global_option(hosts))).
|
||||
-define(MYLANG, ejabberd_config:get_global_option(language)).
|
||||
|
||||
-define(MYNAME, hd(ejabberd_config:get_myhosts())).
|
||||
|
||||
-define(MYLANG, ejabberd_config:get_mylang()).
|
||||
|
||||
-define(MSGS_DIR, <<"msgs">>).
|
||||
|
||||
-define(CONFIG_PATH, <<"ejabberd.cfg">>).
|
||||
|
||||
-define(LOG_PATH, <<"ejabberd.log">>).
|
||||
-define(MSGS_DIR, "msgs").
|
||||
-define(CONFIG_PATH, "ejabberd.cfg").
|
||||
-define(LOG_PATH, "ejabberd.log").
|
||||
|
||||
-ifdef(ENABLE_FLASH_HACK).
|
||||
|
||||
-define(FLASH_HACK, true).
|
||||
|
||||
-else.
|
||||
|
||||
-define(FLASH_HACK, false).
|
||||
|
||||
-endif.
|
||||
|
||||
-define(EJABBERD_URI,
|
||||
<<"http://www.process-one.net/en/ejabberd/">>).
|
||||
|
||||
-define(EJABBERD_URI, "http://www.process-one.net/en/ejabberd/").
|
||||
|
||||
-define(S2STIMEOUT, 600000).
|
||||
|
||||
-define(PRIVACY_SUPPORT, true).
|
||||
|
||||
%%-define(DBGFSM, true).
|
||||
|
||||
-record(scram,
|
||||
{storedkey = <<"">>,
|
||||
serverkey = <<"">>,
|
||||
salt = <<"">>,
|
||||
iterationcount = 0 :: integer()}).
|
||||
|
||||
-type scram() :: #scram{}.
|
||||
|
||||
-record(scram, {storedkey, serverkey, salt, iterationcount}).
|
||||
-define(SCRAM_DEFAULT_ITERATION_COUNT, 4096).
|
||||
|
||||
%% ---------------------------------
|
||||
%% Logging mechanism
|
||||
|
||||
-define(PRINT(Format, Args), io:format(Format, Args)).
|
||||
%% Print in standard output
|
||||
-define(PRINT(Format, Args),
|
||||
io:format(Format, Args)).
|
||||
|
||||
-define(DEBUG(Format, Args),
|
||||
ejabberd_logger:debug_msg(?MODULE, ?LINE, Format,
|
||||
Args)).
|
||||
ejabberd_logger:debug_msg(?MODULE,?LINE,Format, Args)).
|
||||
|
||||
-define(INFO_MSG(Format, Args),
|
||||
ejabberd_logger:info_msg(?MODULE, ?LINE, Format, Args)).
|
||||
|
||||
ejabberd_logger:info_msg(?MODULE,?LINE,Format, Args)).
|
||||
|
||||
-define(WARNING_MSG(Format, Args),
|
||||
ejabberd_logger:warning_msg(?MODULE, ?LINE, Format,
|
||||
Args)).
|
||||
|
||||
ejabberd_logger:warning_msg(?MODULE,?LINE,Format, Args)).
|
||||
|
||||
-define(ERROR_MSG(Format, Args),
|
||||
ejabberd_logger:error_msg(?MODULE, ?LINE, Format,
|
||||
Args)).
|
||||
ejabberd_logger:error_msg(?MODULE,?LINE,Format, Args)).
|
||||
|
||||
-define(CRITICAL_MSG(Format, Args),
|
||||
ejabberd_logger:critical_msg(?MODULE, ?LINE, Format,
|
||||
Args)).
|
||||
ejabberd_logger:critical_msg(?MODULE,?LINE,Format, Args)).
|
||||
|
||||
+42
-153
@@ -5,7 +5,7 @@
|
||||
%%% Created : 7 May 2006 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -30,14 +30,15 @@
|
||||
-export([start/0, stop/0,
|
||||
%% Server
|
||||
status/0, reopen_log/0,
|
||||
stop_migrate/1, migrate/1,
|
||||
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,
|
||||
%% For debugging gen_storage
|
||||
get_last_info/0,
|
||||
get_last_info/2,
|
||||
%% Migration jabberd1.4
|
||||
import_file/1, import_dir/1,
|
||||
%% Purge DB
|
||||
@@ -49,9 +50,7 @@
|
||||
install_fallback_mnesia/1,
|
||||
dump_to_textfile/1, dump_to_textfile/2,
|
||||
mnesia_change_nodename/4,
|
||||
restore/1, % Still used by some modules
|
||||
moderate_room_history/2,
|
||||
persist_recent_messages/0
|
||||
restore/1 % Still used by some modules
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -89,24 +88,9 @@ commands() ->
|
||||
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 = migrate, tags = [server],
|
||||
desc = "Try to migrate C2S/BOSH/MUC sessions to other nodes",
|
||||
module = ?MODULE, function = migrate,
|
||||
args = [{delay, integer}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = stop_migrate, tags = [server],
|
||||
desc = "Try to migrate C2S/BOSH/MUC sessions to other"
|
||||
"nodes and then stop",
|
||||
module = ?MODULE, function = stop_migrate,
|
||||
args = [{delay, integer}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = get_loglevel, tags = [logs, server],
|
||||
desc = "Get the current loglevel",
|
||||
module = ejabberd_loglevel, function = get,
|
||||
@@ -130,23 +114,33 @@ commands() ->
|
||||
#ejabberd_commands{name = register, tags = [accounts],
|
||||
desc = "Register a user",
|
||||
module = ?MODULE, function = register,
|
||||
args = [{user, binary}, {host, binary}, {password, binary}],
|
||||
args = [{user, string}, {host, string}, {password, string}],
|
||||
result = {res, restuple}},
|
||||
#ejabberd_commands{name = unregister, tags = [accounts],
|
||||
desc = "Unregister a user",
|
||||
module = ?MODULE, function = unregister,
|
||||
args = [{user, binary}, {host, binary}],
|
||||
args = [{user, string}, {host, string}],
|
||||
result = {res, restuple}},
|
||||
#ejabberd_commands{name = registered_users, tags = [accounts],
|
||||
desc = "List all registered users in HOST",
|
||||
module = ?MODULE, function = registered_users,
|
||||
args = [{host, binary}],
|
||||
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,
|
||||
|
||||
#ejabberd_commands{name = gli, tags = [accounts],
|
||||
desc = "Get information about last access of an account",
|
||||
module = ?MODULE, function = get_last_info,
|
||||
args = [],
|
||||
result = {vhosts, {list, {vhost, string}}}},
|
||||
result = {lastinfo, {tuple, [{timestamp, integer},
|
||||
{status, string}
|
||||
]}}},
|
||||
#ejabberd_commands{name = get_last_info, tags = [accounts],
|
||||
desc = "Get information about last access of an account",
|
||||
module = ?MODULE, function = get_last_info,
|
||||
args = [{user, string}, {host, string}],
|
||||
result = {lastinfo, {tuple, [{timestamp, integer},
|
||||
{status, string}
|
||||
]}}},
|
||||
|
||||
#ejabberd_commands{name = import_file, tags = [mnesia],
|
||||
desc = "Import user data from jabberd14 spool file",
|
||||
@@ -171,11 +165,6 @@ commands() ->
|
||||
module = ejabberd_piefxis, function = export_host,
|
||||
args = [{dir, string}, {host, string}], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = export_odbc, tags = [mnesia, odbc],
|
||||
desc = "Export virtual host information from Mnesia tables to a SQL file.",
|
||||
module = ejd2odbc, function = export,
|
||||
args = [{host, string}, {file, string}], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = delete_expired_messages, tags = [purge],
|
||||
desc = "Delete expired offline messages from database",
|
||||
module = ?MODULE, function = delete_expired_messages,
|
||||
@@ -184,11 +173,6 @@ commands() ->
|
||||
desc = "Delete offline messages older than DAYS",
|
||||
module = ?MODULE, function = delete_old_messages,
|
||||
args = [{days, integer}], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = rename_default_nodeplugin, tags = [mnesia],
|
||||
desc = "Update PubSub table from old ejabberd trunk SVN to 2.1.0",
|
||||
module = mod_pubsub, function = rename_default_nodeplugin,
|
||||
args = [], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = set_master, tags = [mnesia],
|
||||
desc = "Set master node of the clustered Mnesia tables",
|
||||
@@ -225,40 +209,9 @@ commands() ->
|
||||
#ejabberd_commands{name = install_fallback, tags = [mnesia],
|
||||
desc = "Install the database from a fallback file",
|
||||
module = ?MODULE, function = install_fallback_mnesia,
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = moderate_room_history, tags = [server],
|
||||
desc = "Clean messages from the short-term MUC storage",
|
||||
module = ?MODULE, function = moderate_room_history,
|
||||
args = [{room, string}, {nick, string}],
|
||||
result = {res, restuple}},
|
||||
#ejabberd_commands{name = persist_recent_messages, tags = [server],
|
||||
desc = "Force recent muc messages to be savd on DB",
|
||||
module = ?MODULE, function = persist_recent_messages,
|
||||
args = [],
|
||||
result = {res, restuple}}
|
||||
args = [{file, string}], result = {res, restuple}}
|
||||
].
|
||||
|
||||
%%%
|
||||
%%% MUC moderation
|
||||
%%%
|
||||
%%% Same room can be replicated into different nodes,
|
||||
%%% call all of them.
|
||||
moderate_room_history(Room, Nick) ->
|
||||
{Res, BadNodes} = rpc:multicall(mod_muc, moderate_room_history, [Room, Nick], 5000),
|
||||
B = case BadNodes of
|
||||
[] ->
|
||||
"";
|
||||
_ ->
|
||||
io_lib:format("Bad nodes: ~p", [BadNodes])
|
||||
end,
|
||||
{ok, io_lib:format("Deleted: ~p ~s", [Res, B])}.
|
||||
|
||||
persist_recent_messages() ->
|
||||
Saved = [ {Host, mod_muc:persist_recent_messages(Host)} || Host <- ?MYHOSTS],
|
||||
R = lists:map(fun({Host, {RoomsPersisted, Messages}}) ->
|
||||
io_lib:format("Host '~s' , ~p messages persisted in ~p rooms\n", [Host, Messages, RoomsPersisted])
|
||||
end, Saved),
|
||||
{ok,io_lib:format("~s", [R])}.
|
||||
|
||||
%%%
|
||||
%%% Server management
|
||||
@@ -308,8 +261,7 @@ get_sasl_error_logger_type () ->
|
||||
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",
|
||||
Steps = [{"Stopping ejabberd port listeners",
|
||||
ejabberd_listener, stop_listeners, []},
|
||||
{"Sending announcement to connected users",
|
||||
mod_announce, send_announcement_to_all,
|
||||
@@ -331,7 +283,7 @@ stop_kindly(DelaySeconds, AnnouncementText) ->
|
||||
- TimestampStart,
|
||||
io:format("[~p/~p ~ps] ~s... ",
|
||||
[NumberThis, NumberLast, SecondsDiff, Desc]),
|
||||
Result = (catch apply(Mod, Func, Args)),
|
||||
Result = apply(Mod, Func, Args),
|
||||
io:format("~p~n", [Result]),
|
||||
NumberThis+1
|
||||
end,
|
||||
@@ -340,77 +292,16 @@ stop_kindly(DelaySeconds, AnnouncementText) ->
|
||||
ok.
|
||||
|
||||
send_service_message_all_mucs(Subject, AnnouncementText) ->
|
||||
Message = list_to_binary(
|
||||
io_lib:format("~s~n~s", [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)
|
||||
ServerHost, mod_muc, "conference.@HOST@"),
|
||||
MUCHostB = list_to_binary(MUCHost),
|
||||
mod_muc:broadcast_service_message(MUCHostB, Message)
|
||||
end,
|
||||
?MYHOSTS).
|
||||
|
||||
%%%
|
||||
%%% Migrate w/o stopping
|
||||
%%%
|
||||
migrate(DelaySeconds) ->
|
||||
WaitingDesc = io_lib:format("Starting migration, this will take ~p seconds",
|
||||
[DelaySeconds]),
|
||||
Steps = [
|
||||
{"Stopping ejabberd port listeners",
|
||||
ejabberd_listener, stop_listeners, []},
|
||||
{WaitingDesc, ejabberd_cluster, shutdown_migrate,
|
||||
[DelaySeconds * 1000]}
|
||||
],
|
||||
NumberLast = length(Steps),
|
||||
TimestampStart = calendar:datetime_to_gregorian_seconds({date(), time()}),
|
||||
lists:foldl(
|
||||
fun({Desc, Mod, Func, Args}, NumberThis) ->
|
||||
SecondsDiff =
|
||||
calendar:datetime_to_gregorian_seconds({date(), time()})
|
||||
- TimestampStart,
|
||||
io:format("[~p/~p ~ps] ~s... ",
|
||||
[NumberThis, NumberLast, SecondsDiff, Desc]),
|
||||
Result = apply(Mod, Func, Args),
|
||||
io:format("~p~n", [Result]),
|
||||
NumberThis+1
|
||||
end,
|
||||
1,
|
||||
Steps),
|
||||
ok.
|
||||
|
||||
%%%
|
||||
%%% Migrate and stop
|
||||
%%%
|
||||
stop_migrate(DelaySeconds) ->
|
||||
WaitingDesc = io_lib:format("Starting migration, this will take ~p seconds",
|
||||
[DelaySeconds]),
|
||||
Steps = [
|
||||
{"Stopping ejabberd port listeners",
|
||||
ejabberd_listener, stop_listeners, []},
|
||||
{WaitingDesc, ejabberd_cluster, shutdown_migrate,
|
||||
[DelaySeconds * 1000]},
|
||||
{"Stopping ejabberd", application, stop, [ejabberd]},
|
||||
{"Stopping Mnesia", mnesia, stop, []},
|
||||
{"Stopping Erlang node", init, stop, []}
|
||||
],
|
||||
NumberLast = length(Steps),
|
||||
TimestampStart = calendar:datetime_to_gregorian_seconds({date(), time()}),
|
||||
lists:foldl(
|
||||
fun({Desc, Mod, Func, Args}, NumberThis) ->
|
||||
SecondsDiff =
|
||||
calendar:datetime_to_gregorian_seconds({date(), time()})
|
||||
- TimestampStart,
|
||||
io:format("[~p/~p ~ps] ~s... ",
|
||||
[NumberThis, NumberLast, SecondsDiff, Desc]),
|
||||
Result = apply(Mod, Func, Args),
|
||||
io:format("~p~n", [Result]),
|
||||
NumberThis+1
|
||||
end,
|
||||
1,
|
||||
Steps),
|
||||
ok.
|
||||
|
||||
%%%
|
||||
%%% ejabberd_update
|
||||
%%%
|
||||
@@ -421,17 +312,14 @@ update_list() ->
|
||||
[atom_to_list(Beam) || Beam <- UpdatedBeams].
|
||||
|
||||
update("all") ->
|
||||
[update_module(ModStr) || ModStr <- update_list()],
|
||||
{ok, []};
|
||||
[update_module(ModStr) || ModStr <- update_list()];
|
||||
update(ModStr) ->
|
||||
update_module(ModStr).
|
||||
|
||||
update_module(ModuleNameBin) when is_binary(ModuleNameBin) ->
|
||||
update_module(binary_to_list(ModuleNameBin));
|
||||
update_module(ModuleNameString) ->
|
||||
ModuleName = list_to_atom(ModuleNameString),
|
||||
case ejabberd_update:update([ModuleName]) of
|
||||
{ok, _Res} -> {ok, []};
|
||||
{ok, Res} -> {ok, io_lib:format("Updated: ~p", [Res])};
|
||||
{error, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
@@ -462,8 +350,12 @@ registered_users(Host) ->
|
||||
SUsers = lists:sort(Users),
|
||||
lists:map(fun({U, _S}) -> U end, SUsers).
|
||||
|
||||
registered_vhosts() ->
|
||||
?MYHOSTS.
|
||||
get_last_info() -> get_last_info("badlop", "localhost").
|
||||
get_last_info(User, Server) ->
|
||||
case mod_last:get_last_info(User, Server) of
|
||||
{ok, TimeStamp, Status} -> {TimeStamp, Status};
|
||||
not_found -> {"never", ""}
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% Migration management
|
||||
@@ -495,16 +387,13 @@ import_dir(Path) ->
|
||||
%%%
|
||||
|
||||
delete_expired_messages() ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
{atomic, ok} = mod_offline:remove_expired_messages(Host)
|
||||
end, ?MYHOSTS).
|
||||
mod_offline:remove_expired_messages(),
|
||||
ok.
|
||||
|
||||
delete_old_messages(Days) ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
{atomic, _} = mod_offline:remove_old_messages(Days, Host)
|
||||
end, ?MYHOSTS).
|
||||
mod_offline:remove_old_messages(Days),
|
||||
ok.
|
||||
|
||||
|
||||
%%%
|
||||
%%% Mnesia management
|
||||
|
||||
+50
-53
@@ -5,7 +5,7 @@
|
||||
%%% Created : 31 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -42,11 +42,10 @@ start(normal, _Args) ->
|
||||
ejabberd_loglevel:set(4),
|
||||
write_pid_file(),
|
||||
application:start(sasl),
|
||||
application:start(exmpp),
|
||||
randoms:start(),
|
||||
db_init(),
|
||||
sha:start(),
|
||||
stringprep_sup:start_link(),
|
||||
xml:start(),
|
||||
start(),
|
||||
translate:start(),
|
||||
acl:start(),
|
||||
@@ -57,18 +56,19 @@ 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_riak_sup:start(),
|
||||
ejabberd_auth:start(),
|
||||
cyrsasl:start(),
|
||||
% Profiling
|
||||
%ejabberd_debug:eprof_start(),
|
||||
%ejabberd_debug:fprof_start(),
|
||||
maybe_add_nameservers(),
|
||||
{ok, Pid} = ejabberd_cluster:start(),
|
||||
print_start_debug_info(),
|
||||
start_modules(),
|
||||
ejabberd_cluster:announce(Pid),
|
||||
ejabberd_cluster:announce(),
|
||||
ejabberd_node_groups:start(),
|
||||
ejabberd_listener:start_listeners(),
|
||||
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
|
||||
@@ -80,7 +80,6 @@ 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_cluster:shutdown(),
|
||||
stop_modules(),
|
||||
ejabberd_admin:stop(),
|
||||
broadcast_c2s_shutdown(),
|
||||
@@ -109,19 +108,7 @@ init() ->
|
||||
LogPath = get_log_path(),
|
||||
error_logger:add_report_handler(ejabberd_logger_h, LogPath),
|
||||
erl_ddll:load_driver(ejabberd:get_so_path(), tls_drv),
|
||||
case erl_ddll:load_driver(ejabberd:get_so_path(), expat_erl) of
|
||||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, "expat_erl"}, [binary]),
|
||||
loop(Port).
|
||||
|
||||
|
||||
loop(Port) ->
|
||||
receive
|
||||
_ ->
|
||||
loop(Port)
|
||||
end.
|
||||
ok.
|
||||
|
||||
db_init() ->
|
||||
case mnesia:system_info(extra_db_nodes) of
|
||||
@@ -135,50 +122,52 @@ db_init() ->
|
||||
|
||||
%% Start all the modules in all the hosts
|
||||
start_modules() ->
|
||||
case ejabberd_config:get_local_option({static_modules, global}) of
|
||||
undefined ->
|
||||
ok;
|
||||
StaticModules ->
|
||||
lists:foreach(
|
||||
fun({Module, Args}) ->
|
||||
gen_mod:start_module(global, Module, Args)
|
||||
end, StaticModules)
|
||||
end,
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
Modules = ejabberd_config:get_local_option(
|
||||
{modules, Host},
|
||||
fun(Mods) ->
|
||||
lists:map(
|
||||
fun({M, A}) when is_atom(M), is_list(A) ->
|
||||
{M, A}
|
||||
end, Mods)
|
||||
end, []),
|
||||
lists:foreach(
|
||||
fun({Module, Args}) ->
|
||||
gen_mod:start_module(Host, Module, Args)
|
||||
end, Modules)
|
||||
case ejabberd_config:get_local_option({modules, Host}) of
|
||||
undefined ->
|
||||
ok;
|
||||
Modules ->
|
||||
lists:foreach(
|
||||
fun({Module, Args}) ->
|
||||
gen_mod:start_module(Host, Module, Args)
|
||||
end, Modules)
|
||||
end
|
||||
end, ?MYHOSTS).
|
||||
|
||||
%% Stop all the modules in all the hosts
|
||||
stop_modules() ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
Modules = ejabberd_config:get_local_option(
|
||||
{modules, Host},
|
||||
fun(Mods) ->
|
||||
lists:map(
|
||||
fun({M, A}) when is_atom(M), is_list(A) ->
|
||||
{M, A}
|
||||
end, Mods)
|
||||
end, []),
|
||||
lists:foreach(
|
||||
fun({Module, _Args}) ->
|
||||
gen_mod:stop_module_keep_config(Host, Module)
|
||||
end, Modules)
|
||||
case ejabberd_config:get_local_option({modules, Host}) of
|
||||
undefined ->
|
||||
ok;
|
||||
Modules ->
|
||||
lists:foreach(
|
||||
fun({Module, _Args}) ->
|
||||
gen_mod:stop_module_keep_config(Host, Module)
|
||||
end, Modules)
|
||||
end
|
||||
end, ?MYHOSTS).
|
||||
|
||||
connect_nodes() ->
|
||||
Nodes = ejabberd_config:get_local_option(
|
||||
cluster_nodes,
|
||||
fun(Ns) ->
|
||||
true = lists:all(fun is_atom/1, Ns),
|
||||
Ns
|
||||
end, []),
|
||||
lists:foreach(fun(Node) ->
|
||||
net_kernel:connect_node(Node)
|
||||
end, Nodes).
|
||||
case ejabberd_config:get_local_option(cluster_nodes) of
|
||||
undefined ->
|
||||
ok;
|
||||
Nodes when is_list(Nodes) ->
|
||||
lists:foreach(fun(Node) ->
|
||||
net_kernel:connect_node(Node)
|
||||
end, Nodes)
|
||||
end.
|
||||
|
||||
%% @spec () -> string()
|
||||
%% @doc Returns the full path to the ejabberd log file.
|
||||
@@ -249,3 +238,11 @@ delete_pid_file() ->
|
||||
PidFilename ->
|
||||
file:delete(PidFilename)
|
||||
end.
|
||||
|
||||
print_start_debug_info() ->
|
||||
?DEBUG("Inet DB RC:~n~p", [inet_db:get_rc()]),
|
||||
?DEBUG("Mnesia database:~n~p", [mnesia:system_info(all)]),
|
||||
?DEBUG("Mnesia tables:~n~p",
|
||||
[ [{Table, mnesia:table_info(Table, all)} ||
|
||||
Table <- lists:sort(mnesia:system_info(tables))] ]),
|
||||
ok.
|
||||
|
||||
+430
-330
@@ -5,7 +5,7 @@
|
||||
%%% Created : 23 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -27,405 +27,505 @@
|
||||
%% TODO: Use the functions in ejabberd auth to add and remove users.
|
||||
|
||||
-module(ejabberd_auth).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
%% External exports
|
||||
-export([start/0, set_password/3, check_password/3,
|
||||
check_password/5, check_password_with_authmodule/3,
|
||||
check_password_with_authmodule/5, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2, export/1,
|
||||
-export([start/0,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
check_password_with_authmodule/3,
|
||||
check_password_with_authmodule/5,
|
||||
try_register/3,
|
||||
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, get_password_with_authmodule/2,
|
||||
is_user_exists/2, is_user_exists_in_other_modules/3,
|
||||
remove_user/2, remove_user/3, plain_password_required/1,
|
||||
store_type/1, entropy/1]).
|
||||
get_vh_registered_users_number/2,
|
||||
get_password/2,
|
||||
get_password_s/2,
|
||||
get_password_with_authmodule/2,
|
||||
is_user_exists/2,
|
||||
is_user_exists_in_other_modules/3,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
plain_password_required/1,
|
||||
store_type/1,
|
||||
entropy/1
|
||||
]).
|
||||
|
||||
-export([start/1
|
||||
,start_modules/2
|
||||
,stop_modules/2
|
||||
,start_methods/2
|
||||
,stop_methods/2
|
||||
]).
|
||||
|
||||
-export([auth_modules/1]).
|
||||
|
||||
%% For benchmarking
|
||||
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
|
||||
password = <<"">> :: binary() | scram() | '_'}).
|
||||
|
||||
-export([create_users/5]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-type opts() :: [{prefix, binary()} | {from, integer()} |
|
||||
{to, integer()} | {limit, integer()} |
|
||||
{offset, integer()}].
|
||||
%% @type authmodule() = ejabberd_auth_anonymous | ejabberd_auth_external |
|
||||
%% ejabberd_auth_ldap | ejabberd_auth_pam |
|
||||
%% ejabberd_auth_storage | atom().
|
||||
|
||||
-callback start(binary()) -> any().
|
||||
-callback plain_password_required() -> boolean().
|
||||
-callback store_type() -> plain | external | scram.
|
||||
-callback set_password(binary(), binary(), binary()) -> ok | {error, atom()}.
|
||||
-callback remove_user(binary(), binary()) -> any().
|
||||
-callback remove_user(binary(), binary(), binary()) -> any().
|
||||
-callback is_user_exists(binary(), binary()) -> boolean() | {error, atom()}.
|
||||
-callback check_password(binary(), binary(), binary()) -> boolean().
|
||||
-callback check_password(binary(), binary(), binary(), binary(),
|
||||
fun((binary()) -> binary())) -> boolean().
|
||||
-callback try_register(binary(), binary(), binary()) -> {atomic, atom()} |
|
||||
{error, atom()}.
|
||||
-callback dirty_get_registered_users() -> [{binary(), binary()}].
|
||||
-callback get_vh_registered_users(binary()) -> [{binary(), binary()}].
|
||||
-callback get_vh_registered_users(binary(), opts()) -> [{binary(), binary()}].
|
||||
-callback get_vh_registered_users_number(binary()) -> number().
|
||||
-callback get_vh_registered_users_number(binary(), opts()) -> number().
|
||||
-callback get_password(binary(), binary()) -> false | binary().
|
||||
-callback get_password_s(binary(), binary()) -> binary().
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%% @spec () -> term()
|
||||
|
||||
start() ->
|
||||
lists:foreach(fun (Host) ->
|
||||
lists:foreach(fun (M) -> M:start(Host) end,
|
||||
auth_modules(Host))
|
||||
end,
|
||||
?MYHOSTS).
|
||||
?DEBUG("About to start auth modules. Hosts: ~p", [?MYHOSTS]),
|
||||
lists:foreach(fun start/1, ?MYHOSTS).
|
||||
|
||||
plain_password_required(Server) ->
|
||||
lists:any(fun (M) -> M:plain_password_required() end,
|
||||
auth_modules(Server)).
|
||||
start(Host) ->
|
||||
start_modules(Host, auth_modules(Host)).
|
||||
|
||||
start_modules(Host, Modules) when is_list(Modules) ->
|
||||
lists:foreach(fun (M) -> start_modules(Host, M) end, Modules);
|
||||
start_modules(Host, Module) when is_atom(Module) ->
|
||||
Module:start(Host).
|
||||
|
||||
stop_modules(Host, Modules) when is_list(Modules) ->
|
||||
lists:foreach(fun (M) -> stop_modules(Host, M) end, Modules);
|
||||
stop_modules(Host, Module) when is_atom(Module) ->
|
||||
Module:stop(Host).
|
||||
|
||||
start_methods(Host, Methods) when is_list(Methods) ->
|
||||
lists:foreach(fun (M) -> start_methods(Host, M) end, Methods);
|
||||
start_methods(Host, Method) when is_atom(Method) ->
|
||||
start_modules(Host, module_name(Method)).
|
||||
|
||||
stop_methods(Host, Methods) when is_list(Methods) ->
|
||||
lists:foreach(fun (M) -> stop_methods(Host, M) end, Methods);
|
||||
stop_methods(Host, Method) when is_atom(Method) ->
|
||||
stop_modules(Host, module_name(Method)).
|
||||
|
||||
|
||||
%% @spec (Server) -> bool()
|
||||
%% Server = string()
|
||||
|
||||
%% This is only executed by ejabberd_c2s for non-SASL auth client
|
||||
plain_password_required(Server) when is_list(Server) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
M:plain_password_required()
|
||||
end, auth_modules(Server)).
|
||||
|
||||
%% @spec (Server) -> bool()
|
||||
%% Server = string()
|
||||
|
||||
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)).
|
||||
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)).
|
||||
|
||||
-spec check_password(binary(), binary(), binary()) -> boolean().
|
||||
%% @spec (User, Server, Password) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% @doc Check if the user and password can login in server.
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
case check_password_with_authmodule(User, Server,
|
||||
Password)
|
||||
of
|
||||
{true, _AuthModule} -> true;
|
||||
false -> false
|
||||
check_password(User, Server, Password)
|
||||
when is_list(User), is_list(Server), is_list(Password) ->
|
||||
case check_password_with_authmodule(User, Server, Password) of
|
||||
{true, _AuthModule} -> true;
|
||||
{false, _Reason} -> false
|
||||
end.
|
||||
|
||||
-spec check_password(binary(), binary(), binary(), binary(),
|
||||
fun((binary()) -> binary())) -> boolean().
|
||||
%% @spec (User, Server, Password, Digest, DigestGen) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% Digest = string()
|
||||
%% DigestGen = function()
|
||||
%% @doc Check if the user and password can login in server.
|
||||
|
||||
check_password(User, Server, Password, Digest,
|
||||
DigestGen) ->
|
||||
case check_password_with_authmodule(User, Server,
|
||||
Password, Digest, DigestGen)
|
||||
of
|
||||
{true, _AuthModule} -> true;
|
||||
false -> false
|
||||
check_password(User, Server, Password, Digest, DigestGen)
|
||||
when is_list(User), is_list(Server), is_list(Password),
|
||||
is_list(Digest), is_function(DigestGen) ->
|
||||
case check_password_with_authmodule(User, Server, Password,
|
||||
Digest, DigestGen) of
|
||||
{true, _AuthModule} -> true;
|
||||
{false, _Reason} -> false
|
||||
end.
|
||||
|
||||
-spec check_password_with_authmodule(binary(), binary(), binary()) -> false |
|
||||
{true, atom()}.
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% {true, AuthModule} | {false, Reason::string()}
|
||||
%% where
|
||||
%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external
|
||||
%% | ejabberd_auth_internal | ejabberd_auth_ldap
|
||||
%% | ejabberd_auth_odbc | ejabberd_auth_pam
|
||||
%% @doc Check if the user and password can login in server.
|
||||
%% The user can login if at least an authentication method accepts the user
|
||||
%% and the password.
|
||||
%% The first authentication method that accepts the credentials is returned.
|
||||
check_password_with_authmodule(User, Server, Password)
|
||||
when is_list(User), is_list(Server), is_list(Password) ->
|
||||
check_password_loop(auth_modules(Server), [User, Server, Password], "").
|
||||
|
||||
check_password_with_authmodule(User, Server,
|
||||
Password) ->
|
||||
check_password_loop(auth_modules(Server),
|
||||
[User, Server, Password]).
|
||||
%% @spec (User, Server, Password, Digest, DigestGen) -> {true, AuthModule} | false
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string() | undefined
|
||||
%% Digest = string() | undefined
|
||||
%% DigestGen = function()
|
||||
%% AuthModule = authmodule()
|
||||
%% @doc Check the password is valid and also return the authentication module that accepts it.
|
||||
%% The password is 'undefined' if the client
|
||||
%% authenticates using the digest method as defined in
|
||||
%% XEP-0078: Non-SASL Authentication
|
||||
|
||||
-spec check_password_with_authmodule(binary(), binary(), binary(), binary(),
|
||||
fun((binary()) -> binary())) -> false |
|
||||
{true, atom()}.
|
||||
check_password_with_authmodule(User, Server, Password, Digest, DigestGen)
|
||||
when is_list(User), is_list(Server), (is_list(Password) orelse Password == 'undefined'),
|
||||
is_function(DigestGen), (is_list(Digest) orelse Digest == 'undefined')->
|
||||
check_password_loop(auth_modules(Server), [User, Server, Password,
|
||||
Digest, DigestGen], "").
|
||||
|
||||
check_password_with_authmodule(User, Server, Password,
|
||||
Digest, DigestGen) ->
|
||||
check_password_loop(auth_modules(Server),
|
||||
[User, Server, Password, Digest, DigestGen]).
|
||||
|
||||
check_password_loop([], _Args) -> false;
|
||||
check_password_loop([AuthModule | AuthModules], Args) ->
|
||||
check_password_loop([], _Args, LastReason) ->
|
||||
{false, LastReason};
|
||||
check_password_loop([AuthModule | AuthModules], Args, PreviousReason) ->
|
||||
case apply(AuthModule, check_password, Args) of
|
||||
true -> {true, AuthModule};
|
||||
false -> check_password_loop(AuthModules, Args)
|
||||
true ->
|
||||
{true, AuthModule};
|
||||
{false, Reason} when Reason /= "" ->
|
||||
check_password_loop(AuthModules, Args, Reason);
|
||||
{false, ""} ->
|
||||
check_password_loop(AuthModules, Args, PreviousReason);
|
||||
false ->
|
||||
check_password_loop(AuthModules, Args, PreviousReason)
|
||||
end.
|
||||
|
||||
-spec set_password(binary(), binary(), binary()) -> ok |
|
||||
{error, atom()}.
|
||||
|
||||
set_password(_User, _Server, <<"">>) ->
|
||||
%% @spec (User, Server, Password) -> ok | {error, ErrorType}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% ErrorType = empty_password | not_allowed | invalid_jid
|
||||
set_password(_User, _Server, "") ->
|
||||
%% We do not allow empty password
|
||||
{error, empty_password};
|
||||
set_password(User, Server, Password) ->
|
||||
lists:foldl(fun (M, {error, _}) ->
|
||||
M:set_password(User, Server, Password);
|
||||
(_M, Res) -> Res
|
||||
end,
|
||||
{error, not_allowed}, auth_modules(Server)).
|
||||
set_password(User, Server, Password)
|
||||
when is_list(User), is_list(Server), is_list(Password) ->
|
||||
lists:foldl(
|
||||
fun(M, {error, _}) ->
|
||||
M:set_password(User, Server, Password);
|
||||
(_M, Res) ->
|
||||
Res
|
||||
end, {error, not_allowed}, auth_modules(Server)).
|
||||
|
||||
-spec try_register(binary(), binary(), binary()) -> {atomic, atom()} |
|
||||
{error, atom()}.
|
||||
|
||||
try_register(_User, _Server, <<"">>) ->
|
||||
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string() | nil()
|
||||
try_register(_User, _Server, "") ->
|
||||
%% We do not allow empty password
|
||||
{error, not_allowed};
|
||||
try_register(User, Server, Password) ->
|
||||
try_register(User, Server, Password)
|
||||
when is_list(User), is_list(Server), is_list(Password) ->
|
||||
case is_user_exists(User, Server) of
|
||||
true -> {atomic, exists};
|
||||
false ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
case lists:member(LServer, ?MYHOSTS) of
|
||||
true ->
|
||||
MaxUsers = ejabberd_config:get_local_option({max_users, LServer},
|
||||
fun(X) when is_integer(X) -> X end, no_limit),
|
||||
RegAllowed = case MaxUsers of
|
||||
no_limit ->
|
||||
true;
|
||||
Num ->
|
||||
get_vh_registered_users_number(LServer) < Num
|
||||
end,
|
||||
case RegAllowed of
|
||||
true ->
|
||||
Res = lists:foldl(fun (_M, {atomic, ok} = Res) -> Res;
|
||||
(M, _) ->
|
||||
M:try_register(User, Server, Password)
|
||||
end,
|
||||
{error, not_allowed}, auth_modules(Server)),
|
||||
case Res of
|
||||
{atomic, ok} ->
|
||||
ejabberd_hooks:run(register_user, Server,
|
||||
[User, Server]),
|
||||
{atomic, ok};
|
||||
_ -> Res
|
||||
end;
|
||||
_ ->
|
||||
{error, too_many_users}
|
||||
end;
|
||||
false -> {error, not_allowed}
|
||||
end
|
||||
true ->
|
||||
{atomic, exists};
|
||||
false ->
|
||||
case ?IS_MY_HOST(exmpp_stringprep:nameprep(Server)) of
|
||||
true ->
|
||||
Res = lists:foldl(
|
||||
fun (_M, {atomic, ok} = Res) ->
|
||||
Res;
|
||||
(M, _) ->
|
||||
M:try_register(User, Server, Password)
|
||||
end, {error, not_allowed}, auth_modules(Server)),
|
||||
trigger_register_hooks(Res, User, Server);
|
||||
false ->
|
||||
{error, not_allowed}
|
||||
end
|
||||
end.
|
||||
|
||||
-spec dirty_get_registered_users() -> [{binary(), binary()}].
|
||||
trigger_register_hooks({atomic, ok} = Res, User, Server) ->
|
||||
ejabberd_hooks:run(register_user, list_to_binary(Server),
|
||||
[User, Server]),
|
||||
Res;
|
||||
trigger_register_hooks(Res, _User, _Server) ->
|
||||
Res.
|
||||
|
||||
%% @spec () -> [{LUser, LServer}]
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
%% @doc Registered users list do not include anonymous users logged.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
lists:flatmap(fun (M) -> M:dirty_get_registered_users()
|
||||
end,
|
||||
auth_modules()).
|
||||
lists:flatmap(
|
||||
fun(M) ->
|
||||
M:dirty_get_registered_users()
|
||||
end, auth_modules()).
|
||||
|
||||
-spec get_vh_registered_users(binary()) -> [{binary(), binary()}].
|
||||
%% @spec (Server) -> [{LUser, LServer}]
|
||||
%% Server = string()
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
%% @doc Registered users list do not include anonymous users logged.
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
lists:flatmap(fun (M) ->
|
||||
M:get_vh_registered_users(Server)
|
||||
end,
|
||||
auth_modules(Server)).
|
||||
get_vh_registered_users(Server) when is_list(Server) ->
|
||||
lists:flatmap(
|
||||
fun(M) ->
|
||||
M:get_vh_registered_users(Server)
|
||||
end, auth_modules(Server)).
|
||||
|
||||
-spec get_vh_registered_users(binary(), opts()) -> [{binary(), binary()}].
|
||||
%% @spec (Server, Opts) -> [{LUser, LServer}]
|
||||
%% Server = string()
|
||||
%% Opts = [{Opt, Val}]
|
||||
%% Opt = atom()
|
||||
%% Val = term()
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
|
||||
get_vh_registered_users(Server, Opts) ->
|
||||
lists:flatmap(fun (M) ->
|
||||
case erlang:function_exported(M,
|
||||
get_vh_registered_users,
|
||||
2)
|
||||
of
|
||||
true -> M:get_vh_registered_users(Server, Opts);
|
||||
false -> M:get_vh_registered_users(Server)
|
||||
end
|
||||
end,
|
||||
auth_modules(Server)).
|
||||
get_vh_registered_users(Server, Opts) when is_list(Server) ->
|
||||
lists:flatmap(
|
||||
fun(M) ->
|
||||
case erlang:function_exported(
|
||||
M, get_vh_registered_users, 2) of
|
||||
true ->
|
||||
M:get_vh_registered_users(Server, Opts);
|
||||
false ->
|
||||
M:get_vh_registered_users(Server)
|
||||
end
|
||||
end, auth_modules(Server)).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
lists:sum(lists:map(fun (M) ->
|
||||
case erlang:function_exported(M,
|
||||
get_vh_registered_users_number,
|
||||
1)
|
||||
of
|
||||
true ->
|
||||
M:get_vh_registered_users_number(Server);
|
||||
false ->
|
||||
length(M:get_vh_registered_users(Server))
|
||||
end
|
||||
end,
|
||||
auth_modules(Server))).
|
||||
%% @spec (Server) -> Users_Number
|
||||
%% Server = string()
|
||||
%% Users_Number = integer()
|
||||
|
||||
-spec get_vh_registered_users_number(binary(), opts()) -> number().
|
||||
get_vh_registered_users_number(Server) when is_list(Server) ->
|
||||
lists:sum(
|
||||
lists:map(
|
||||
fun(M) ->
|
||||
case erlang:function_exported(
|
||||
M, get_vh_registered_users_number, 1) of
|
||||
true ->
|
||||
M:get_vh_registered_users_number(Server);
|
||||
false ->
|
||||
length(M:get_vh_registered_users(Server))
|
||||
end
|
||||
end, auth_modules(Server))).
|
||||
|
||||
get_vh_registered_users_number(Server, Opts) ->
|
||||
lists:sum(lists:map(fun (M) ->
|
||||
case erlang:function_exported(M,
|
||||
get_vh_registered_users_number,
|
||||
2)
|
||||
of
|
||||
true ->
|
||||
M:get_vh_registered_users_number(Server,
|
||||
Opts);
|
||||
false ->
|
||||
length(M:get_vh_registered_users(Server))
|
||||
end
|
||||
end,
|
||||
auth_modules(Server))).
|
||||
%% @spec (Server, Opts) -> Users_Number
|
||||
%% Server = string()
|
||||
%% Opts = [{Opt, Val}]
|
||||
%% Opt = atom()
|
||||
%% Val = term()
|
||||
%% Users_Number = integer()
|
||||
|
||||
-spec get_password(binary(), binary()) -> false | binary().
|
||||
get_vh_registered_users_number(Server, Opts) when is_list(Server) ->
|
||||
lists:sum(
|
||||
lists:map(
|
||||
fun(M) ->
|
||||
case erlang:function_exported(
|
||||
M, get_vh_registered_users_number, 2) of
|
||||
true ->
|
||||
M:get_vh_registered_users_number(Server, Opts);
|
||||
false ->
|
||||
length(M:get_vh_registered_users(Server))
|
||||
end
|
||||
end, auth_modules(Server))).
|
||||
|
||||
get_password(User, Server) ->
|
||||
lists:foldl(fun (M, false) ->
|
||||
M:get_password(User, Server);
|
||||
(_M, Password) -> Password
|
||||
end,
|
||||
false, auth_modules(Server)).
|
||||
%% @spec (User, Server) -> Password | false
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% @doc Get the password of the user.
|
||||
|
||||
-spec get_password_s(binary(), binary()) -> binary().
|
||||
get_password(User, Server) when is_list(User), is_list(Server) ->
|
||||
lists:foldl(
|
||||
fun(M, false) ->
|
||||
M:get_password(User, Server);
|
||||
(_M, Password) ->
|
||||
Password
|
||||
end, false, auth_modules(Server)).
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
%% @spec (User, Server) -> Password | nil()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% @doc Get the password of the user.
|
||||
|
||||
get_password_s(User, Server) when is_list(User), is_list(Server) ->
|
||||
case get_password(User, Server) of
|
||||
false -> <<"">>;
|
||||
Password -> Password
|
||||
false ->
|
||||
"";
|
||||
Password when is_list(Password) ->
|
||||
Password;
|
||||
_ ->
|
||||
""
|
||||
end.
|
||||
|
||||
-spec get_password_with_authmodule(binary(), binary()) -> {false | binary(), atom()}.
|
||||
%% @spec (User, Server) -> {Password, AuthModule} | {false, none}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% AuthModule = authmodule()
|
||||
%% @doc Get the password of the user and the auth module.
|
||||
|
||||
get_password_with_authmodule(User, Server) ->
|
||||
lists:foldl(fun (M, {false, _}) ->
|
||||
{M:get_password(User, Server), M};
|
||||
(_M, {Password, AuthModule}) -> {Password, AuthModule}
|
||||
end,
|
||||
{false, none}, auth_modules(Server)).
|
||||
get_password_with_authmodule(User, Server)
|
||||
when is_list(User), is_list(Server) ->
|
||||
lists:foldl(
|
||||
fun(M, {false, _}) ->
|
||||
{M:get_password(User, Server), M};
|
||||
(_M, {Password, AuthModule}) ->
|
||||
{Password, AuthModule}
|
||||
end, {false, none}, auth_modules(Server)).
|
||||
|
||||
-spec is_user_exists(binary(), binary()) -> boolean().
|
||||
%% @spec (User, Server) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% @doc Returns true if the user exists in the DB or if an anonymous
|
||||
%% user is logged under the given name.
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
lists:any(fun (M) ->
|
||||
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~nError message: ~p",
|
||||
[M, User, Server, Error]),
|
||||
false;
|
||||
Else -> Else
|
||||
end
|
||||
end,
|
||||
auth_modules(Server)).
|
||||
is_user_exists(User, Server) when is_list(User), is_list(Server) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
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)).
|
||||
|
||||
-spec is_user_exists_in_other_modules(atom(), binary(), binary()) -> boolean() | maybe.
|
||||
%% @spec (Module, User, Server) -> true | false | maybe
|
||||
%% Module = authmodule()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% @doc Check if the user exists in all authentications module except
|
||||
%% the module passed as parameter.
|
||||
|
||||
is_user_exists_in_other_modules(Module, User, Server) ->
|
||||
is_user_exists_in_other_modules_loop(auth_modules(Server)
|
||||
-- [Module],
|
||||
User, Server).
|
||||
|
||||
is_user_exists_in_other_modules_loop([], _User,
|
||||
_Server) ->
|
||||
is_user_exists_in_other_modules(Module, User, Server)
|
||||
when is_list(User), is_list(Server) ->
|
||||
is_user_exists_in_other_modules_loop(
|
||||
auth_modules(Server)--[Module],
|
||||
User, Server).
|
||||
is_user_exists_in_other_modules_loop([], _User, _Server) ->
|
||||
false;
|
||||
is_user_exists_in_other_modules_loop([AuthModule
|
||||
| AuthModules],
|
||||
User, Server) ->
|
||||
is_user_exists_in_other_modules_loop([AuthModule|AuthModules], User, Server) ->
|
||||
case AuthModule:is_user_exists(User, Server) of
|
||||
true -> true;
|
||||
false ->
|
||||
is_user_exists_in_other_modules_loop(AuthModules, User,
|
||||
Server);
|
||||
{error, Error} ->
|
||||
?DEBUG("The authentication module ~p returned "
|
||||
"an error~nwhen checking user ~p in server "
|
||||
"~p~nError message: ~p",
|
||||
[AuthModule, User, Server, Error]),
|
||||
maybe
|
||||
true ->
|
||||
true;
|
||||
false ->
|
||||
is_user_exists_in_other_modules_loop(AuthModules, User, Server);
|
||||
{error, Error} ->
|
||||
?DEBUG("The authentication module ~p returned an error~nwhen "
|
||||
"checking user ~p in server ~p~nError message: ~p",
|
||||
[AuthModule, User, Server, Error]),
|
||||
maybe
|
||||
end.
|
||||
|
||||
-spec remove_user(binary(), binary()) -> ok.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
lists:foreach(fun (M) -> M:remove_user(User, Server)
|
||||
end,
|
||||
auth_modules(Server)),
|
||||
ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
|
||||
[User, Server]),
|
||||
ok.
|
||||
%% @spec (User, Server) -> ok | error | {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% @doc Remove user.
|
||||
%% TODO: Fix me: It always return ok even if there was some problem removing the user.
|
||||
%% dialyzer warning
|
||||
%% ejabberd_auth.erl:388: The variable _ can never match since previous clauses completely covered the type 'ok'
|
||||
|
||||
-spec remove_user(binary(), binary(), binary()) -> any().
|
||||
remove_user(User, Server) when is_list(User), is_list(Server) ->
|
||||
R = lists:foreach(
|
||||
fun(M) ->
|
||||
M:remove_user(User, Server)
|
||||
end, auth_modules(Server)),
|
||||
ejabberd_hooks:run(remove_user, list_to_binary(exmpp_stringprep:nameprep(Server)),
|
||||
[list_to_binary(User), list_to_binary(Server)]),
|
||||
R.
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
R = lists:foldl(fun (_M, ok = Res) -> Res;
|
||||
(M, _) -> M:remove_user(User, Server, Password)
|
||||
end,
|
||||
error, auth_modules(Server)),
|
||||
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% @doc Try to remove user if the provided password is correct.
|
||||
%% The removal is attempted in each auth method provided:
|
||||
%% when one returns 'ok' the loop stops;
|
||||
%% if no method returns 'ok' then it returns the error message indicated by the last method attempted.
|
||||
|
||||
remove_user(User, Server, Password)
|
||||
when is_list(User), is_list(Server), is_list(Password) ->
|
||||
R = lists:foldl(
|
||||
fun(_M, ok = Res) ->
|
||||
Res;
|
||||
(M, _) ->
|
||||
M:remove_user(User, Server, Password)
|
||||
end, error, auth_modules(Server)),
|
||||
case R of
|
||||
ok ->
|
||||
ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
|
||||
[User, Server]);
|
||||
_ -> none
|
||||
ok -> ejabberd_hooks:run(remove_user, list_to_binary(exmpp_stringprep:nameprep(Server)),
|
||||
[list_to_binary(User), list_to_binary(Server)]);
|
||||
_ -> none
|
||||
end,
|
||||
R.
|
||||
|
||||
%% @spec (IOList) -> non_negative_float()
|
||||
%% @doc Calculate informational entropy.
|
||||
entropy(B) ->
|
||||
case binary_to_list(B) 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 >= 33, C =< 126 ->
|
||||
[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)
|
||||
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
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%% @spec () -> [authmodule()]
|
||||
%% @doc Return the lists of all the auth modules actually used in the
|
||||
%% configuration.
|
||||
|
||||
auth_modules() ->
|
||||
lists:usort(lists:flatmap(fun (Server) ->
|
||||
auth_modules(Server)
|
||||
end,
|
||||
?MYHOSTS)).
|
||||
lists:usort(
|
||||
lists:flatmap(
|
||||
fun(Server) ->
|
||||
auth_modules(Server)
|
||||
end, ?MYHOSTS)).
|
||||
|
||||
-spec auth_modules(binary()) -> [atom()].
|
||||
%% @spec (Server) -> [authmodule()]
|
||||
%% Server = string()
|
||||
%% @doc Return the list of authenticated modules for a given host.
|
||||
|
||||
auth_modules(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
Methods = ejabberd_config:get_local_option(
|
||||
{auth_method, LServer},
|
||||
fun(V) when is_list(V) ->
|
||||
true = lists:all(fun is_atom/1, V),
|
||||
V;
|
||||
(V) when is_atom(V) ->
|
||||
[V]
|
||||
end, []),
|
||||
[jlib:binary_to_atom(<<"ejabberd_auth_",
|
||||
(jlib:atom_to_binary(M))/binary>>)
|
||||
|| M <- Methods].
|
||||
auth_modules(Server) when is_list(Server) ->
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
Method = ejabberd_config:get_local_option({auth_method, ejabberd:normalize_host(LServer)}),
|
||||
Methods = if
|
||||
Method == undefined -> [];
|
||||
is_list(Method) -> Method;
|
||||
is_atom(Method) -> [Method]
|
||||
end,
|
||||
[module_name(M) || M <- Methods].
|
||||
|
||||
export(Server) ->
|
||||
ejabberd_auth_internal:export(Server).
|
||||
|
||||
-spec create_users(binary(), binary(), binary(),
|
||||
pos_integer(), gen_mod:db_type()) -> any().
|
||||
|
||||
create_users(UserPattern, PassPattern, Server, Total, DBType) ->
|
||||
lists:foreach(
|
||||
fun(I) ->
|
||||
LUser = jlib:nodeprep(
|
||||
iolist_to_binary([UserPattern, integer_to_list(I)])),
|
||||
Pass = iolist_to_binary([PassPattern, integer_to_list(I)]),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case DBType of
|
||||
mnesia ->
|
||||
mnesia:dirty_write(#passwd{us = US,
|
||||
password = Pass});
|
||||
riak ->
|
||||
ejabberd_riak:put(
|
||||
#passwd{us = US,
|
||||
password = Pass},
|
||||
[{'2i', [{<<"host">>, LServer}]}]);
|
||||
odbc ->
|
||||
erlang:error(odbc_not_supported)
|
||||
end
|
||||
end, lists:seq(1, Total)).
|
||||
module_name(Method) when is_atom(Method) ->
|
||||
list_to_atom("ejabberd_auth_" ++ atom_to_list(Method)).
|
||||
|
||||
+275
-163
@@ -5,7 +5,7 @@
|
||||
%%% Created : 17 Feb 2006 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -25,237 +25,349 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_auth_anonymous).
|
||||
|
||||
-author('mickael.remond@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
-export([start/1, allow_anonymous/1,
|
||||
-export([start/1,
|
||||
stop/1,
|
||||
allow_anonymous/1,
|
||||
is_sasl_anonymous_enabled/1,
|
||||
is_login_anonymous_enabled/1, anonymous_user_exist/2,
|
||||
allow_multiple_connections/1, register_connection/3,
|
||||
unregister_migrated_connection/3,
|
||||
unregister_connection/3]).
|
||||
is_login_anonymous_enabled/1,
|
||||
anonymous_user_exist/2,
|
||||
allow_multiple_connections/1,
|
||||
register_connection/3,
|
||||
unregister_connection/3
|
||||
]).
|
||||
|
||||
|
||||
%% Function used by ejabberd_auth:
|
||||
-export([login/2, set_password/3, check_password/3,
|
||||
check_password/5, 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_s/2,
|
||||
get_password/2, get_password/3, is_user_exists/2,
|
||||
remove_user/2, remove_user/3, store_type/0,
|
||||
-export([login/2,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
try_register/3,
|
||||
dirty_get_registered_users/0,
|
||||
get_vh_registered_users/1,
|
||||
get_password/2,
|
||||
get_password/3,
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
plain_password_required/0]).
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-record(anonymous, {us, sid}).
|
||||
|
||||
-include("jlib.hrl").
|
||||
%% @spec (Host) -> ok
|
||||
%% Host = string()
|
||||
%% @doc Create the anonymous table if at least one virtual host has
|
||||
%% anonymous features enabled.
|
||||
%% Register to login / logout events.
|
||||
|
||||
-record(anonymous, {us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
sid = {now(), self()} :: ejabberd_sm:sid()}).
|
||||
|
||||
start(Host) ->
|
||||
start(Host) when is_list(Host) ->
|
||||
HostB = list_to_binary(Host),
|
||||
%% TODO: Check cluster mode
|
||||
update_tables(),
|
||||
mnesia:create_table(anonymous,
|
||||
[{ram_copies, [node()]}, {type, bag},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, anonymous)}]),
|
||||
mnesia:create_table(anonymous, [{ram_copies, [node()]},
|
||||
{type, bag}, {local_content, true},
|
||||
{attributes, record_info(fields, anonymous)}]),
|
||||
mnesia:add_table_copy(anonymous, node(), ram_copies),
|
||||
ejabberd_hooks:add(sm_register_connection_hook, Host,
|
||||
%% The hooks are needed to add / remove users from the anonymous tables
|
||||
ejabberd_hooks:add(sm_register_connection_hook, HostB,
|
||||
?MODULE, register_connection, 100),
|
||||
ejabberd_hooks:add(sm_remove_migrated_connection_hook,
|
||||
Host, ?MODULE, unregister_migrated_connection, 100),
|
||||
ejabberd_hooks:add(sm_remove_connection_hook, Host,
|
||||
ejabberd_hooks:add(sm_remove_connection_hook, HostB,
|
||||
?MODULE, unregister_connection, 100),
|
||||
ok.
|
||||
|
||||
allow_anonymous(Host) ->
|
||||
stop(Host) when is_list(Host) ->
|
||||
HostB = list_to_binary(Host),
|
||||
ejabberd_hooks:delete(sm_register_connection_hook, HostB,
|
||||
?MODULE, register_connection, 100),
|
||||
ejabberd_hooks:delete(sm_remove_connection_hook, HostB,
|
||||
?MODULE, unregister_connection, 100),
|
||||
ok.
|
||||
|
||||
%% @spec (Host) -> bool()
|
||||
%% Host = string()
|
||||
%% @doc Return true if anonymous is allowed for host or false otherwise.
|
||||
|
||||
allow_anonymous(Host) when is_list(Host) ->
|
||||
lists:member(?MODULE, ejabberd_auth:auth_modules(Host)).
|
||||
|
||||
is_sasl_anonymous_enabled(Host) ->
|
||||
%% @spec (Host) -> bool()
|
||||
%% Host = string()
|
||||
%% @doc Return true if anonymous mode is enabled and if anonymous
|
||||
%% protocol is SASL anonymous.
|
||||
%% protocol can be: sasl_anon|login_anon|both
|
||||
|
||||
is_sasl_anonymous_enabled(Host) when is_list(Host) ->
|
||||
case allow_anonymous(Host) of
|
||||
false -> false;
|
||||
true ->
|
||||
case anonymous_protocol(Host) of
|
||||
sasl_anon -> true;
|
||||
both -> true;
|
||||
_Other -> false
|
||||
end
|
||||
false -> false;
|
||||
true ->
|
||||
case anonymous_protocol(Host) of
|
||||
sasl_anon -> true;
|
||||
both -> true;
|
||||
_Other -> false
|
||||
end
|
||||
end.
|
||||
|
||||
is_login_anonymous_enabled(Host) ->
|
||||
%% @spec (Host) -> bool()
|
||||
%% Host = string()
|
||||
%% @doc Return true if anonymous login is enabled on the server.
|
||||
%% anonymous login can be use using standard authentication method (i.e. with
|
||||
%% clients that do not support anonymous login)
|
||||
|
||||
is_login_anonymous_enabled(Host) when is_list(Host) ->
|
||||
case allow_anonymous(Host) of
|
||||
false -> false;
|
||||
true ->
|
||||
case anonymous_protocol(Host) of
|
||||
login_anon -> true;
|
||||
both -> true;
|
||||
_Other -> false
|
||||
end
|
||||
false -> false;
|
||||
true ->
|
||||
case anonymous_protocol(Host) of
|
||||
login_anon -> true;
|
||||
both -> true;
|
||||
_Other -> false
|
||||
end
|
||||
end.
|
||||
|
||||
anonymous_protocol(Host) ->
|
||||
ejabberd_config:get_local_option(
|
||||
{anonymous_protocol, Host},
|
||||
fun(sasl_anon) -> sasl_anon;
|
||||
(login_anon) -> login_anon;
|
||||
(both) -> both
|
||||
end,
|
||||
sasl_anon).
|
||||
%% @spec (Host) -> sasl_anon | login_anon | both
|
||||
%% Host = string()
|
||||
%% @doc Return the anonymous protocol to use: sasl_anon|login_anon|both.
|
||||
%% defaults to login_anon
|
||||
|
||||
allow_multiple_connections(Host) ->
|
||||
ejabberd_config:get_local_option(
|
||||
{allow_multiple_connections, Host},
|
||||
fun(V) when is_boolean(V) -> V end,
|
||||
false).
|
||||
|
||||
anonymous_user_exist(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
Ss = case ejabberd_cluster:get_node(US) of
|
||||
Node when Node == node() ->
|
||||
catch mnesia:dirty_read({anonymous, US});
|
||||
Node ->
|
||||
catch rpc:call(Node, mnesia, dirty_read,
|
||||
[{anonymous, US}], 5000)
|
||||
end,
|
||||
case Ss of
|
||||
[_H | _T] -> true;
|
||||
_ -> false
|
||||
anonymous_protocol(Host) when is_list(Host) ->
|
||||
case ejabberd_config:get_local_option({anonymous_protocol, Host}) of
|
||||
sasl_anon -> sasl_anon;
|
||||
login_anon -> login_anon;
|
||||
both -> both;
|
||||
_Other -> sasl_anon
|
||||
end.
|
||||
|
||||
remove_connection(SID, LUser, LServer) ->
|
||||
%% @spec (Host) -> bool()
|
||||
%% Host = string()
|
||||
%% @doc Return true if multiple connections have been allowed in the
|
||||
%% config file.
|
||||
%% defaults to false
|
||||
|
||||
allow_multiple_connections(Host) when is_list(Host) ->
|
||||
ejabberd_config:get_local_option({allow_multiple_connections, Host})
|
||||
=:= true.
|
||||
|
||||
%% @spec (User, Server) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% @doc Check if user exist in the anonymous database.
|
||||
|
||||
anonymous_user_exist(User, Server) when is_list(User), is_list(Server) ->
|
||||
anonymous_user_exist(list_to_binary(User), list_to_binary(Server));
|
||||
anonymous_user_exist(User, Server) when is_binary(User), is_binary(Server) ->
|
||||
LUser = exmpp_stringprep:nodeprep(User),
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
F = fun () -> mnesia:delete_object({anonymous, US, SID})
|
||||
end,
|
||||
case catch mnesia:dirty_read({anonymous, US}) of
|
||||
[] ->
|
||||
false;
|
||||
[_H|_T] ->
|
||||
true
|
||||
end.
|
||||
|
||||
%% @spec (SID, LUser, LServer) -> term()
|
||||
%% SID = term()
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
%% @doc Remove connection from Mnesia tables.
|
||||
|
||||
remove_connection(SID, LUser, LServer) when is_binary(LUser), is_binary(LServer) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:delete_object({anonymous, US, SID})
|
||||
end,
|
||||
mnesia:async_dirty(F).
|
||||
|
||||
register_connection(SID,
|
||||
#jid{luser = LUser, lserver = LServer}, Info) ->
|
||||
AuthModule = list_to_atom(binary_to_list(xml:get_attr_s(<<"auth_module">>, Info))),
|
||||
case AuthModule == (?MODULE) of
|
||||
true ->
|
||||
ejabberd_hooks:run(register_user, LServer,
|
||||
[LUser, LServer]),
|
||||
US = {LUser, LServer},
|
||||
mnesia:async_dirty(fun () ->
|
||||
mnesia:write(#anonymous{us = US,
|
||||
sid = SID})
|
||||
end);
|
||||
false -> ok
|
||||
%% @spec (SID, JID, Info) -> term()
|
||||
%% SID = term()
|
||||
%% JID = exmpp_jid:jid()
|
||||
%% Info = [term()]
|
||||
%% @doc Register connection.
|
||||
|
||||
register_connection(SID, JID, Info) when ?IS_JID(JID) ->
|
||||
LUser = exmpp_jid:prep_node(JID),
|
||||
LServer = exmpp_jid:prep_domain(JID),
|
||||
case proplists:get_value(auth_module, Info) of
|
||||
undefined ->
|
||||
ok;
|
||||
?MODULE ->
|
||||
ejabberd_hooks:run(register_user, LServer, [LUser, LServer]),
|
||||
US = {LUser, LServer},
|
||||
mnesia:async_dirty(
|
||||
fun() -> mnesia:write(#anonymous{us = US, sid=SID})
|
||||
end);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
unregister_connection(SID,
|
||||
#jid{luser = LUser, lserver = LServer}, _) ->
|
||||
purge_hook(anonymous_user_exist(LUser, LServer), LUser,
|
||||
LServer),
|
||||
%% @spec (SID, JID, Ignored) -> term()
|
||||
%% SID = term()
|
||||
%% JID = exmpp_jid:jid()
|
||||
%% Ignored = term()
|
||||
%% @doc Remove an anonymous user from the anonymous users table.
|
||||
|
||||
unregister_connection(SID, JID, _) when ?IS_JID(JID) ->
|
||||
LUser = exmpp_jid:prep_node(JID),
|
||||
LServer = exmpp_jid:prep_domain(JID),
|
||||
purge_hook(anonymous_user_exist(LUser, LServer),
|
||||
LUser, LServer),
|
||||
remove_connection(SID, LUser, LServer).
|
||||
|
||||
unregister_migrated_connection(SID,
|
||||
#jid{luser = LUser, lserver = LServer}, _) ->
|
||||
remove_connection(SID, LUser, LServer).
|
||||
%% @spec (bool(), LUser, LServer) -> term()
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
%% @doc Launch the hook to purge user data only for anonymous users.
|
||||
|
||||
purge_hook(false, _LUser, _LServer) -> ok;
|
||||
purge_hook(true, LUser, LServer) ->
|
||||
ejabberd_hooks:run(anonymous_purge_hook, LServer,
|
||||
[LUser, LServer]).
|
||||
purge_hook(false, _LUser, _LServer) ->
|
||||
ok;
|
||||
purge_hook(true, LUser, LServer) when is_binary(LUser), is_binary(LServer) ->
|
||||
ejabberd_hooks:run(anonymous_purge_hook, LServer, [LUser, LServer]).
|
||||
|
||||
%% ---------------------------------
|
||||
%% Specific anonymous auth functions
|
||||
%% ---------------------------------
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
check_password(User, Server, Password, undefined,
|
||||
undefined).
|
||||
%% @spec (User, Server, Password) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% @doc When anonymous login is enabled, check the password for
|
||||
%% permenant users before allowing access.
|
||||
|
||||
check_password(User, Server, _Password, _Digest,
|
||||
_DigestGen) ->
|
||||
case
|
||||
ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
|
||||
User, Server)
|
||||
of
|
||||
%% If user exists in other module, reject anonnymous authentication
|
||||
true -> false;
|
||||
%% If we are not sure whether the user exists in other module, reject anon auth
|
||||
maybe -> false;
|
||||
false -> login(User, Server)
|
||||
check_password(User, Server, Password) ->
|
||||
check_password(User, Server, Password, undefined, undefined).
|
||||
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,
|
||||
User, Server) of
|
||||
%% If user exists in other module, reject anonnymous authentication
|
||||
true -> false;
|
||||
%% If we are not sure whether the user exists in other module, reject anon auth
|
||||
maybe -> false;
|
||||
false -> login(User, Server)
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
login(User, Server) ->
|
||||
case is_login_anonymous_enabled(Server) of
|
||||
false -> false;
|
||||
true ->
|
||||
case anonymous_user_exist(User, Server) of
|
||||
%% Reject the login if an anonymous user with the same login
|
||||
%% is already logged and if multiple login has not been enable
|
||||
%% in the config file.
|
||||
true -> allow_multiple_connections(Server);
|
||||
%% Accept login and add user to the anonymous table
|
||||
false -> true
|
||||
end
|
||||
false -> false;
|
||||
true ->
|
||||
case anonymous_user_exist(User, Server) of
|
||||
%% Reject the login if an anonymous user with the same login
|
||||
%% is already logged and if multiple login has not been enable
|
||||
%% in the config file.
|
||||
true -> allow_multiple_connections(Server);
|
||||
%% Accept login and add user to the anonymous table
|
||||
false -> true
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% @doc When anonymous login is enabled, check that the user is
|
||||
%% permanent before changing its password.
|
||||
|
||||
set_password(User, Server, _Password) ->
|
||||
case anonymous_user_exist(User, Server) of
|
||||
true -> ok;
|
||||
false -> {error, not_allowed}
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
{error, not_allowed}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% @doc When anonymous login is enabled, check if permanent users are
|
||||
%% allowed on the server:
|
||||
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
dirty_get_registered_users() -> [].
|
||||
%% @spec () -> nil()
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
[].
|
||||
|
||||
%% @spec (Server) -> nil()
|
||||
%% Server = string()
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
[{U, S}
|
||||
|| {U, S, _R}
|
||||
<- ejabberd_sm:get_vh_session_list(Server)].
|
||||
[{U, S} || {U, S, _R} <- ejabberd_sm:get_vh_session_list(list_to_binary(Server))].
|
||||
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
length(get_vh_registered_users(Server)).
|
||||
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
%% @spec (User, Server) -> Password | false
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = nil()
|
||||
%% @doc Return password of permanent user or false for anonymous users.
|
||||
|
||||
get_password(User, Server) ->
|
||||
get_password(User, Server, <<"">>).
|
||||
get_password(User, Server, "").
|
||||
|
||||
%% @spec (User, Server, DefaultValue) -> DefaultValue | false
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% DefaultValue = string()
|
||||
|
||||
get_password(User, Server, DefaultValue) ->
|
||||
case anonymous_user_exist(User, Server) or
|
||||
login(User, Server)
|
||||
of
|
||||
%% We return the default value if the user is anonymous
|
||||
true -> DefaultValue;
|
||||
%% We return the permanent user password otherwise
|
||||
false -> false
|
||||
case anonymous_user_exist(User, Server) or login(User, Server) of
|
||||
%% We return the default value if the user is anonymous
|
||||
true ->
|
||||
DefaultValue;
|
||||
%% We return the permanent user password otherwise
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case get_password(User, Server) of
|
||||
false ->
|
||||
<<"">>;
|
||||
Password ->
|
||||
Password
|
||||
end.
|
||||
%% @spec (User, Server) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% @doc Returns true if the user exists in the DB or if an anonymous
|
||||
%% user is logged under the given name.
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
anonymous_user_exist(User, Server).
|
||||
|
||||
remove_user(_User, _Server) -> {error, not_allowed}.
|
||||
%% @spec (User, Server) -> {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
remove_user(_User, _Server, _Password) -> not_allowed.
|
||||
remove_user(_User, _Server) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
plain_password_required() -> false.
|
||||
%% @spec (User, Server, Password) -> {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
remove_user(_User, _Server, _Password) ->
|
||||
not_allowed.
|
||||
|
||||
%% @spec () -> bool()
|
||||
|
||||
plain_password_required() ->
|
||||
false.
|
||||
|
||||
store_type() ->
|
||||
plain.
|
||||
|
||||
update_tables() ->
|
||||
case catch mnesia:table_info(anonymous, local_content)
|
||||
of
|
||||
false -> mnesia:delete_table(anonymous);
|
||||
_ -> ok
|
||||
case catch mnesia:table_info(anonymous, local_content) of
|
||||
false ->
|
||||
mnesia:delete_table(anonymous);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
store_type() -> plain.
|
||||
|
||||
+267
-152
@@ -5,7 +5,7 @@
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -25,265 +25,380 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_auth_external).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
%% External exports
|
||||
-export([start/1, set_password/3, check_password/3,
|
||||
check_password/5, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
-export([start/1,
|
||||
stop/1,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
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]).
|
||||
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
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%% @spec (Host) -> ok
|
||||
%% Host = string()
|
||||
|
||||
start(Host) ->
|
||||
Cmd = ejabberd_config:get_local_option(
|
||||
{extauth_program, Host},
|
||||
fun(V) ->
|
||||
binary_to_list(iolist_to_binary(V))
|
||||
end,
|
||||
"extauth"),
|
||||
extauth:start(Host, Cmd),
|
||||
extauth:start(
|
||||
Host, ejabberd_config:get_local_option({extauth_program, Host})),
|
||||
case check_cache_last_options(Host) of
|
||||
cache -> ok = ejabberd_auth_internal:start(Host);
|
||||
no_cache -> ok
|
||||
cache ->
|
||||
ok = ejabberd_auth_storage:start(Host);
|
||||
no_cache ->
|
||||
ok
|
||||
end.
|
||||
|
||||
stop(Host) ->
|
||||
extauth:stop(Host),
|
||||
case check_cache_last_options(Host) of
|
||||
cache ->
|
||||
ok = ejabberd_auth_storage:stop(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
|
||||
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.
|
||||
%% @spec () -> bool()
|
||||
|
||||
store_type() -> external.
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
store_type() ->
|
||||
external.
|
||||
|
||||
%% @spec (User, Server, Password) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> check_password_extauth(User, Server, Password);
|
||||
{true, CacheTime} ->
|
||||
check_password_cache(User, Server, Password, CacheTime)
|
||||
false -> check_password_extauth(User, Server, Password);
|
||||
{true, CacheTime} -> check_password_cache(User, Server, Password, CacheTime)
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password, _Digest,
|
||||
_DigestGen) ->
|
||||
%% @spec (User, Server, Password, Digest, DigestGen) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% Digest = string()
|
||||
%% DigestGen = function()
|
||||
|
||||
check_password(User, Server, Password, _Digest, _DigestGen) ->
|
||||
check_password(User, Server, Password).
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | {error, unknown_problem}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
case extauth:set_password(User, Server, Password) of
|
||||
true ->
|
||||
set_password_internal(User, Server, Password), ok;
|
||||
_ -> {error, unknown_problem}
|
||||
true -> set_password_storage(User, Server, Password),
|
||||
ok;
|
||||
_ -> {error, unknown_problem}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
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)
|
||||
false -> try_register_extauth(User, Server, Password);
|
||||
{true, _CacheTime} -> try_register_external_cache(User, Server, Password)
|
||||
end.
|
||||
|
||||
%% @spec () -> nil()
|
||||
%% @todo Write it.
|
||||
%% @doc Return the list of all users handled by external.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
ejabberd_auth_internal:dirty_get_registered_users().
|
||||
ejabberd_auth_storage:dirty_get_registered_users().
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users(Server).
|
||||
case check_cache_last_options(Server) of
|
||||
cache ->
|
||||
ejabberd_auth_storage:get_vh_registered_users(Server);
|
||||
no_cache ->
|
||||
[]
|
||||
end.
|
||||
|
||||
get_vh_registered_users(Server, Data) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users(Server,
|
||||
Data).
|
||||
get_vh_registered_users(Server, Data) ->
|
||||
case check_cache_last_options(Server) of
|
||||
cache ->
|
||||
ejabberd_auth_storage:get_vh_registered_users(Server, Data);
|
||||
no_cache ->
|
||||
[]
|
||||
end.
|
||||
|
||||
%% @spec (Server) -> nil()
|
||||
%% Server = string()
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users_number(Server).
|
||||
case check_cache_last_options(Server) of
|
||||
cache ->
|
||||
ejabberd_auth_storage:get_vh_registered_users_number(Server);
|
||||
no_cache ->
|
||||
0
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server, Data) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users_number(Server,
|
||||
Data).
|
||||
case check_cache_last_options(Server) of
|
||||
cache ->
|
||||
ejabberd_auth_storage:get_vh_registered_users_number(Server, Data);
|
||||
no_cache ->
|
||||
0
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% @doc Get the user cached password, if possible.
|
||||
%% 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)
|
||||
false -> false;
|
||||
{true, CacheTime} -> get_password_cache(User, Server, CacheTime)
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> nil()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case get_password(User, Server) of
|
||||
false -> <<"">>;
|
||||
Other -> Other
|
||||
false -> [];
|
||||
Other -> Other
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
try extauth:is_user_exists(User, Server) of
|
||||
Res -> Res
|
||||
Res -> Res
|
||||
catch
|
||||
_:Error -> {error, Error}
|
||||
_:Error -> {error, Error}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
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
|
||||
false -> false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_storage:remove_user(User, Server)
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> not_allowed
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
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
|
||||
false -> false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_storage: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},
|
||||
fun(I) when is_integer(I), I > 0 -> I end) of
|
||||
undefined -> false;
|
||||
CacheTime -> {true, CacheTime}
|
||||
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 /= <<"">>.
|
||||
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) ->
|
||||
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 ->
|
||||
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
|
||||
online ->
|
||||
check_password_storage(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_storage(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).
|
||||
get_password_storage(User, Server) ->
|
||||
ejabberd_auth_storage:get_password(User, Server).
|
||||
|
||||
%% @spec (User, Server, CacheTime) -> Password::string() | false
|
||||
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
|
||||
online ->
|
||||
get_password_storage(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_storage(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
|
||||
true ->
|
||||
set_password_storage(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}
|
||||
{atomic, ok} = R ->
|
||||
set_password_storage(User, Server, Password),
|
||||
R;
|
||||
_ -> {error, not_allowed}
|
||||
end.
|
||||
|
||||
check_password_internal(User, Server, Password) ->
|
||||
ejabberd_auth_internal:check_password(User, Server,
|
||||
Password).
|
||||
%% @spec (User, Server, Password) -> true | false
|
||||
check_password_storage(User, Server, Password) ->
|
||||
ejabberd_auth_storage:check_password(User, Server, Password).
|
||||
|
||||
set_password_internal(User, Server, Password) ->
|
||||
ejabberd_auth_internal:set_password(User, Server,
|
||||
Password).
|
||||
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
|
||||
set_password_storage(User, Server, Password) ->
|
||||
ejabberd_auth_storage: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.
|
||||
(TimeStampLast + CacheTime > Now).
|
||||
|
||||
get_last_access(User, Server) ->
|
||||
%% @spec (User, Server) -> TimeStamp::integer() | online | never | mod_last_required
|
||||
get_last_access(UserS, ServerS) ->
|
||||
%% Code copied from mod_configure.erl
|
||||
%% Code copied from web/ejabberd_web_admin.erl
|
||||
%% TODO: Update time format to XEP-0202: Entity Time
|
||||
User = list_to_binary(UserS),
|
||||
Server = list_to_binary(ServerS),
|
||||
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
|
||||
[] ->
|
||||
_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
|
||||
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(ServerB) when is_binary(ServerB)->
|
||||
Server = binary_to_list(ServerB),
|
||||
get_mod_last_enabled(Server);
|
||||
get_mod_last_enabled(Server) ->
|
||||
case gen_mod:is_loaded(Server, mod_last) of
|
||||
true -> mod_last;
|
||||
false -> no_mod_last
|
||||
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
|
||||
true -> mod_last;
|
||||
false -> no_mod_last
|
||||
end.
|
||||
|
||||
is_configured(Host, Module) ->
|
||||
gen_mod:is_loaded(Host, Module).
|
||||
lists:keymember(Module, 1, ejabberd_config:get_local_option({modules, Host})).
|
||||
|
||||
@@ -1,460 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_auth_internal.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Authentification via mnesia
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@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(ejabberd_auth_internal).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
%% External exports
|
||||
-export([start/1, set_password/3, check_password/3,
|
||||
check_password/5, 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, export/1,
|
||||
plain_password_required/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
|
||||
password = <<"">> :: binary() | scram() | '_'}).
|
||||
|
||||
-record(reg_users_counter, {vhost = <<"">> :: binary(),
|
||||
count = 0 :: integer() | '$1'}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
start(Host) ->
|
||||
mnesia:create_table(passwd,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, passwd)}]),
|
||||
mnesia:create_table(reg_users_counter,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, reg_users_counter)}]),
|
||||
update_table(),
|
||||
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),
|
||||
F = fun () ->
|
||||
mnesia:write(#reg_users_counter{vhost = LServer,
|
||||
count = Size})
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
plain_password_required() ->
|
||||
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}]
|
||||
when is_binary(Password) ->
|
||||
Password /= <<"">>;
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
is_password_scram_valid(Password, Scram);
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password, Digest,
|
||||
DigestGen) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Passwd}] when is_binary(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 = jlib:decode_base64(Scram#scram.storedkey),
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
true ->
|
||||
F = fun () ->
|
||||
Password2 = case is_scrammed() and is_binary(Password)
|
||||
of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
mnesia:write(#passwd{us = US, password = Password2})
|
||||
end,
|
||||
{atomic, ok} = mnesia:transaction(F),
|
||||
ok
|
||||
end.
|
||||
|
||||
try_register(User, Server, PasswordList) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
Password = iolist_to_binary(PasswordList),
|
||||
US = {LUser, LServer},
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
true ->
|
||||
F = fun () ->
|
||||
case mnesia:read({passwd, US}) of
|
||||
[] ->
|
||||
Password2 = case is_scrammed() and
|
||||
is_binary(Password)
|
||||
of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
mnesia:write(#passwd{us = US,
|
||||
password = Password2}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, 1),
|
||||
ok;
|
||||
[_E] -> exists
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
mnesia:dirty_all_keys(passwd).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
mnesia:dirty_select(passwd,
|
||||
[{#passwd{us = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, LServer}], ['$1']}]).
|
||||
|
||||
get_vh_registered_users(Server,
|
||||
[{from, Start}, {to, End}])
|
||||
when is_integer(Start) and is_integer(End) ->
|
||||
get_vh_registered_users(Server,
|
||||
[{limit, End - Start + 1}, {offset, Start}]);
|
||||
get_vh_registered_users(Server,
|
||||
[{limit, Limit}, {offset, Offset}])
|
||||
when is_integer(Limit) and is_integer(Offset) ->
|
||||
case get_vh_registered_users(Server) of
|
||||
[] -> [];
|
||||
Users ->
|
||||
Set = lists:keysort(1, Users),
|
||||
L = length(Set),
|
||||
Start = if Offset < 1 -> 1;
|
||||
Offset > L -> L;
|
||||
true -> Offset
|
||||
end,
|
||||
lists:sublist(Set, Start, Limit)
|
||||
end;
|
||||
get_vh_registered_users(Server, [{prefix, Prefix}])
|
||||
when is_binary(Prefix) ->
|
||||
Set = [{U, S}
|
||||
|| {U, S} <- get_vh_registered_users(Server),
|
||||
str:prefix(Prefix, U)],
|
||||
lists:keysort(1, Set);
|
||||
get_vh_registered_users(Server,
|
||||
[{prefix, Prefix}, {from, Start}, {to, End}])
|
||||
when is_binary(Prefix) and is_integer(Start) and
|
||||
is_integer(End) ->
|
||||
get_vh_registered_users(Server,
|
||||
[{prefix, Prefix}, {limit, End - Start + 1},
|
||||
{offset, Start}]);
|
||||
get_vh_registered_users(Server,
|
||||
[{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
|
||||
when is_binary(Prefix) and is_integer(Limit) and
|
||||
is_integer(Offset) ->
|
||||
case [{U, S}
|
||||
|| {U, S} <- get_vh_registered_users(Server),
|
||||
str:prefix(Prefix, U)]
|
||||
of
|
||||
[] -> [];
|
||||
Users ->
|
||||
Set = lists:keysort(1, Users),
|
||||
L = length(Set),
|
||||
Start = if Offset < 1 -> 1;
|
||||
Offset > L -> L;
|
||||
true -> Offset
|
||||
end,
|
||||
lists:sublist(Set, Start, Limit)
|
||||
end;
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
Query = mnesia:dirty_select(reg_users_counter,
|
||||
[{#reg_users_counter{vhost = LServer,
|
||||
count = '$1'},
|
||||
[], ['$1']}]),
|
||||
case Query of
|
||||
[Count] -> Count;
|
||||
_ -> 0
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server,
|
||||
[{prefix, Prefix}])
|
||||
when is_binary(Prefix) ->
|
||||
Set = [{U, S}
|
||||
|| {U, S} <- get_vh_registered_users(Server),
|
||||
str:prefix(Prefix, U)],
|
||||
length(Set);
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
|
||||
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}]
|
||||
when is_binary(Password) ->
|
||||
Password;
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
{jlib:decode_base64(Scram#scram.storedkey),
|
||||
jlib:decode_base64(Scram#scram.serverkey),
|
||||
jlib:decode_base64(Scram#scram.salt),
|
||||
Scram#scram.iterationcount};
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read(passwd, US) of
|
||||
[#passwd{password = Password}]
|
||||
when is_binary(Password) ->
|
||||
Password;
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
<<"">>;
|
||||
_ -> <<"">>
|
||||
end.
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[] -> false;
|
||||
[_] -> true;
|
||||
Other -> {error, Other}
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
F = fun () ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter, LServer,
|
||||
-1)
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
ok.
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
F = fun () ->
|
||||
case mnesia:read({passwd, US}) of
|
||||
[#passwd{password = Password}]
|
||||
when is_binary(Password) ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter, LServer,
|
||||
-1),
|
||||
ok;
|
||||
[#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
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} -> ok;
|
||||
{atomic, Res} -> Res;
|
||||
_ -> bad_request
|
||||
end.
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, passwd),
|
||||
case mnesia:table_info(passwd, attributes) of
|
||||
Fields ->
|
||||
convert_to_binary(Fields),
|
||||
maybe_scram_passwords(),
|
||||
ok;
|
||||
_ ->
|
||||
?INFO_MSG("Recreating passwd table", []),
|
||||
mnesia:transform_table(passwd, ignore, Fields)
|
||||
end.
|
||||
|
||||
convert_to_binary(Fields) ->
|
||||
ejabberd_config:convert_table_to_binary(
|
||||
passwd, Fields, set,
|
||||
fun(#passwd{us = {U, _}}) -> U end,
|
||||
fun(#passwd{us = {U, S}, password = Pass} = R) ->
|
||||
NewUS = {iolist_to_binary(U), iolist_to_binary(S)},
|
||||
NewPass = case Pass of
|
||||
#scram{storedkey = StoredKey,
|
||||
serverkey = ServerKey,
|
||||
salt = Salt} ->
|
||||
Pass#scram{
|
||||
storedkey = iolist_to_binary(StoredKey),
|
||||
serverkey = iolist_to_binary(ServerKey),
|
||||
salt = iolist_to_binary(Salt)};
|
||||
_ ->
|
||||
iolist_to_binary(Pass)
|
||||
end,
|
||||
R#passwd{us = NewUS, password = NewPass}
|
||||
end).
|
||||
|
||||
%%%
|
||||
%%% SCRAM
|
||||
%%%
|
||||
|
||||
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},
|
||||
fun(V) -> V end).
|
||||
|
||||
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 = jlib:encode_base64(StoredKey),
|
||||
serverkey = jlib:encode_base64(ServerKey),
|
||||
salt = jlib:encode_base64(Salt),
|
||||
iterationcount = IterationCount}.
|
||||
|
||||
is_password_scram_valid(Password, Scram) ->
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
Salt = jlib:decode_base64(Scram#scram.salt),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
jlib:decode_base64(Scram#scram.storedkey) == StoredKey.
|
||||
|
||||
export(_Server) ->
|
||||
[{passwd,
|
||||
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc:escape(Password),
|
||||
[[<<"delete from users where username='">>, Username, <<"';">>],
|
||||
[<<"insert into users(username, password) "
|
||||
"values ('">>, Username, <<"', '">>, Pass, <<"');">>]];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
+463
-268
@@ -5,7 +5,7 @@
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -25,378 +25,573 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_auth_ldap).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_info/2, handle_call/3,
|
||||
handle_cast/2, terminate/2, code_change/3]).
|
||||
-export([init/1,
|
||||
handle_info/2,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
terminate/2,
|
||||
code_change/3
|
||||
]).
|
||||
|
||||
%% External exports
|
||||
-export([start/1, stop/1, start_link/1, set_password/3,
|
||||
check_password/3, check_password/5, 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]).
|
||||
-export([start/1,
|
||||
stop/1,
|
||||
start_link/1,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
try_register/3,
|
||||
dirty_get_registered_users/0,
|
||||
get_vh_registered_users/1,
|
||||
get_vh_registered_users_number/1,
|
||||
get_password/2,
|
||||
get_password_s/2,
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-include("eldap/eldap.hrl").
|
||||
|
||||
-record(state,
|
||||
{host = <<"">> :: binary(),
|
||||
eldap_id = <<"">> :: binary(),
|
||||
bind_eldap_id = <<"">> :: binary(),
|
||||
servers = [] :: [binary()],
|
||||
backups = [] :: [binary()],
|
||||
port = ?LDAP_PORT :: inet:port_number(),
|
||||
tls_options = [] :: list(),
|
||||
dn = <<"">> :: binary(),
|
||||
password = <<"">> :: binary(),
|
||||
base = <<"">> :: binary(),
|
||||
uids = [] :: [{binary()} | {binary(), binary()}],
|
||||
ufilter = <<"">> :: binary(),
|
||||
sfilter = <<"">> :: binary(),
|
||||
lfilter :: {any(), any()},
|
||||
deref_aliases = never :: never | searching | finding | always,
|
||||
dn_filter :: binary(),
|
||||
dn_filter_attrs = [] :: [binary()]}).
|
||||
-record(state, {host,
|
||||
eldap_id,
|
||||
bind_eldap_id,
|
||||
servers,
|
||||
backups,
|
||||
port,
|
||||
tls_options,
|
||||
dn,
|
||||
password,
|
||||
base,
|
||||
uids,
|
||||
ufilter,
|
||||
lfilter, %% Local filter (performed by ejabberd, not LDAP)
|
||||
deref_aliases,
|
||||
dn_filter,
|
||||
dn_filter_attrs
|
||||
}).
|
||||
|
||||
handle_cast(_Request, State) -> {noreply, State}.
|
||||
%% Unused callbacks.
|
||||
handle_cast(_Request, State) ->
|
||||
{noreply, State}.
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
%% -----
|
||||
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
handle_info(_Info, State) -> {noreply, State}.
|
||||
-define(LDAP_SEARCH_TIMEOUT, 5). % Timeout for LDAP search queries in seconds
|
||||
|
||||
-define(LDAP_SEARCH_TIMEOUT, 5).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%% @spec (Host) -> term()
|
||||
%% Host = string()
|
||||
|
||||
start(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
ChildSpec = {Proc, {?MODULE, start_link, [Host]},
|
||||
transient, 1000, worker, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
?DEBUG("Starting ~p for ~p.", [?MODULE, Host]),
|
||||
Option = case ejabberd_config:get_host_option(Host, ldap_servers) of
|
||||
undefined -> check_local_config(Host);
|
||||
{host, _Host} -> nothing;
|
||||
_ ->
|
||||
{start, Host}
|
||||
end,
|
||||
case Option of
|
||||
nothing -> ok;
|
||||
{start, Host2} ->
|
||||
Proc = gen_mod:get_module_proc(Host2, ?MODULE),
|
||||
ChildSpec = {
|
||||
Proc, {?MODULE, start_link, [Host2]},
|
||||
transient, 1000, worker, [?MODULE]
|
||||
},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec)
|
||||
end.
|
||||
|
||||
check_local_config(Host) ->
|
||||
case ejabberd_config:get_local_option({ldap_servers, Host}) of
|
||||
undefined ->
|
||||
?ERROR_MSG("Can't start ~p for host ~p: missing ldap_servers "
|
||||
"configuration", [?MODULE, Host]),
|
||||
{error, bad_config};
|
||||
X when is_list(X)->
|
||||
{start, Host}
|
||||
end.
|
||||
|
||||
%% @spec (Host) -> term()
|
||||
%% Host = string()
|
||||
|
||||
stop(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:call(Proc, stop),
|
||||
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
case ejabberd_config:get_host_option(Host, ldap_servers) of
|
||||
undefined -> ok;
|
||||
{host, _Host} -> ok;
|
||||
_ ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:call(Proc, stop),
|
||||
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||
supervisor:delete_child(ejabberd_sup, Proc)
|
||||
end.
|
||||
|
||||
%% @spec (Host) -> term()
|
||||
%% Host = string()
|
||||
|
||||
start_link(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, Host, []).
|
||||
|
||||
terminate(_Reason, _State) -> ok.
|
||||
%% @hidden
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
%% @spec (Host) -> {ok, State}
|
||||
%% Host = string()
|
||||
%% State = term()
|
||||
|
||||
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.tls_options),
|
||||
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.tls_options),
|
||||
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.
|
||||
%% @spec () -> true
|
||||
|
||||
store_type() -> external.
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
store_type() ->
|
||||
external.
|
||||
|
||||
%% @spec (User, Server, Password) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
if Password == <<"">> -> false;
|
||||
true ->
|
||||
case catch check_password_ldap(User, Server, Password)
|
||||
of
|
||||
{'EXIT', _} -> false;
|
||||
Result -> Result
|
||||
end
|
||||
%% In LDAP spec: empty password means anonymous authentication.
|
||||
%% As ejabberd is providing other anonymous authentication mechanisms
|
||||
%% we simply prevent the use of LDAP anonymous authentication.
|
||||
if Password == "" ->
|
||||
false;
|
||||
true ->
|
||||
case catch check_password_ldap(User, Server, Password) of
|
||||
{'EXIT', _} -> false;
|
||||
Result -> Result
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password, _Digest,
|
||||
_DigestGen) ->
|
||||
%% @spec (User, Server, Password, Digest, DigestGen) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% Digest = string()
|
||||
%% DigestGen = function()
|
||||
|
||||
check_password(User, Server, Password, _Digest, _DigestGen) ->
|
||||
check_password(User, Server, Password).
|
||||
|
||||
%% @spec (User, Server, Password) -> {error, Reason} | ok
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% Reason = term()
|
||||
|
||||
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)
|
||||
case find_user_dn(User, Server, 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}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
%% @spec () -> [{LUser, LServer}]
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
Servers = ejabberd_config:get_vh_by_auth_method(ldap),
|
||||
lists:flatmap(fun (Server) ->
|
||||
get_vh_registered_users(Server)
|
||||
end,
|
||||
Servers).
|
||||
lists:flatmap(
|
||||
fun(Server) ->
|
||||
get_vh_registered_users(Server)
|
||||
end, Servers).
|
||||
|
||||
%% @spec (Server) -> [{LUser, LServer}]
|
||||
%% Server = string()
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
case catch get_vh_registered_users_ldap(Server) of
|
||||
{'EXIT', _} -> [];
|
||||
Result -> Result
|
||||
end.
|
||||
{'EXIT', _} -> [];
|
||||
Result -> Result
|
||||
end.
|
||||
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
%% @spec (Server) -> Users_Number
|
||||
%% Server = string()
|
||||
%% Users_Number = integer()
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
length(get_vh_registered_users(Server)).
|
||||
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
%% @spec (User, Server) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
get_password(_User, _Server) -> false.
|
||||
get_password(_User, _Server) ->
|
||||
false.
|
||||
|
||||
get_password_s(_User, _Server) -> <<"">>.
|
||||
%% @spec (User, Server) -> nil()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
get_password_s(_User, _Server) ->
|
||||
"".
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
case catch is_user_exists_ldap(User, Server) of
|
||||
{'EXIT', Error} -> {error, Error};
|
||||
Result -> Result
|
||||
{'EXIT', Error} ->
|
||||
{error, Error};
|
||||
Result ->
|
||||
Result
|
||||
end.
|
||||
|
||||
remove_user(_User, _Server) -> {error, not_allowed}.
|
||||
%% @spec (User, Server) -> {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
remove_user(_User, _Server, _Password) -> not_allowed.
|
||||
remove_user(_User, _Server) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
%% @spec (User, Server, Password) -> not_allowed
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
remove_user(_User, _Server, _Password) ->
|
||||
not_allowed.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%% @spec (User, Server, Password) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
check_password_ldap(User, Server, Password) ->
|
||||
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
|
||||
case find_user_dn(User, State) of
|
||||
false -> false;
|
||||
DN ->
|
||||
case eldap_pool:bind(State#state.bind_eldap_id, DN,
|
||||
Password)
|
||||
of
|
||||
ok -> true;
|
||||
_ -> false
|
||||
end
|
||||
{ok, State} = get_state(Server),
|
||||
case find_user_dn(User, Server, State) of
|
||||
false ->
|
||||
false;
|
||||
DN ->
|
||||
case eldap_pool:bind(State#state.bind_eldap_id, DN, Password) of
|
||||
ok -> true;
|
||||
_ -> false
|
||||
end
|
||||
end.
|
||||
|
||||
%% We need an ?MODULE server state to use for queries. This will
|
||||
%% either be Server if this is a statically configured host or the
|
||||
%% Server for a different host if this is a dynamically configured
|
||||
%% vhost.
|
||||
%% The {ldap_vhost, Server} -> Host. ejabberd config option specifies
|
||||
%% which actual ?MODULE server to use for a particular Host. The value
|
||||
%% of the option if it is defined or Server by default.
|
||||
get_state(Server) ->
|
||||
Host = case ejabberd_config:get_local_option({ldap_servers, Server}) of
|
||||
{host, H} -> H;
|
||||
_ -> Server
|
||||
end,
|
||||
eldap_utils:get_state(Host, ?MODULE).
|
||||
|
||||
%% @spec (Server) -> [{LUser, LServer}]
|
||||
%% Server = string()
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
|
||||
get_vh_registered_users_ldap(Server) ->
|
||||
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
|
||||
UIDs = State#state.uids,
|
||||
UIDs = eldap_utils:uids_domain_subst(Server, State#state.uids),
|
||||
Eldap_ID = State#state.eldap_id,
|
||||
Server = State#state.host,
|
||||
SearchFilter = build_sfilter(State, UIDs),
|
||||
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},
|
||||
{deref_aliases, State#state.deref_aliases},
|
||||
{attributes, ResAttrs}])
|
||||
of
|
||||
#eldap_search_result{entries = Entries} ->
|
||||
lists:flatmap(fun (#eldap_entry{attributes = Attrs,
|
||||
object_name = DN}) ->
|
||||
case is_valid_dn(DN, Attrs, State) of
|
||||
false -> [];
|
||||
_ ->
|
||||
case
|
||||
eldap_utils:find_ldap_attrs(UIDs,
|
||||
Attrs)
|
||||
of
|
||||
<<"">> -> [];
|
||||
{User, UIDFormat} ->
|
||||
case
|
||||
eldap_utils:get_user_part(User,
|
||||
UIDFormat)
|
||||
of
|
||||
{ok, U} ->
|
||||
case jlib:nodeprep(U) of
|
||||
error -> [];
|
||||
LU ->
|
||||
[{LU,
|
||||
jlib:nameprep(Server)}]
|
||||
end;
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
case eldap_filter:parse(SearchFilter) of
|
||||
{ok, EldapFilter} ->
|
||||
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,
|
||||
object_name = DN}) ->
|
||||
case is_valid_dn(DN, Server, Attrs, State) of
|
||||
false -> [];
|
||||
_ ->
|
||||
case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
|
||||
"" -> [];
|
||||
{User, UIDFormat} ->
|
||||
case eldap_utils:get_user_part(User, UIDFormat) of
|
||||
{ok, U} ->
|
||||
try
|
||||
[{exmpp_stringprep:nodeprep(U), exmpp_stringprep:nameprep(Server)}]
|
||||
catch
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
Entries);
|
||||
_ -> []
|
||||
end;
|
||||
_ -> []
|
||||
end.
|
||||
end, Entries);
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
is_user_exists_ldap(User, Server) ->
|
||||
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
|
||||
case find_user_dn(User, State) of
|
||||
false -> false;
|
||||
_DN -> true
|
||||
end.
|
||||
case find_user_dn(User, Server, State) of
|
||||
false -> false;
|
||||
_DN -> true
|
||||
end.
|
||||
|
||||
handle_call(get_state, _From, State) ->
|
||||
{reply, {ok, State}, State};
|
||||
{reply, {ok, State}, State};
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, ok, State};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, bad_request, State}.
|
||||
|
||||
find_user_dn(User, State) ->
|
||||
find_user_dn(User, Server, State) ->
|
||||
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},
|
||||
{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);
|
||||
_ -> false
|
||||
end;
|
||||
_ -> false
|
||||
UserFilter = build_ufilter(State, Server),
|
||||
case eldap_filter:parse(UserFilter, [{"%u", User}]) of
|
||||
{ok, Filter} ->
|
||||
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, Server, Attrs, State);
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
dn_filter(DN, Attrs, State) ->
|
||||
%% apply the dn filter and the local filter:
|
||||
dn_filter(DN, Server, Attrs, State) ->
|
||||
%% Check if user is denied access by attribute value (local check)
|
||||
case check_local_filter(Attrs, State) of
|
||||
false -> false;
|
||||
true -> is_valid_dn(DN, Attrs, State)
|
||||
false -> false;
|
||||
true -> is_valid_dn(DN, Server, Attrs, State)
|
||||
end.
|
||||
|
||||
is_valid_dn(DN, _, #state{dn_filter = undefined}) -> DN;
|
||||
is_valid_dn(DN, Attrs, State) ->
|
||||
%% Check that the DN is valid, based on the dn filter
|
||||
is_valid_dn(DN, _, _, #state{dn_filter = undefined}) ->
|
||||
DN;
|
||||
|
||||
is_valid_dn(DN, Server, Attrs, State) ->
|
||||
DNAttrs = State#state.dn_filter_attrs,
|
||||
UIDs = State#state.uids,
|
||||
Values = [{<<"%s">>,
|
||||
eldap_utils:get_ldap_attr(Attr, Attrs), 1}
|
||||
|| Attr <- DNAttrs],
|
||||
SubstValues = case eldap_utils:find_ldap_attrs(UIDs,
|
||||
Attrs)
|
||||
of
|
||||
<<"">> -> Values;
|
||||
{S, UAF} ->
|
||||
case eldap_utils:get_user_part(S, UAF) of
|
||||
{ok, U} -> [{<<"%u">>, U} | Values];
|
||||
_ -> Values
|
||||
end
|
||||
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},
|
||||
{deref_aliases, State#state.deref_aliases},
|
||||
{attributes, [<<"dn">>]}])
|
||||
of
|
||||
#eldap_search_result{entries = [_ | _]} -> DN;
|
||||
_ -> false
|
||||
end;
|
||||
_ -> false
|
||||
UIDs = eldap_utils:uids_domain_subst(Server, State#state.uids),
|
||||
Values = [{"%s", eldap_utils:get_ldap_attr(Attr, Attrs), 1} || Attr <- DNAttrs],
|
||||
SubstValues = case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
|
||||
"" -> Values;
|
||||
{S, UAF} ->
|
||||
case eldap_utils:get_user_part(S, UAF) of
|
||||
{ok, U} -> [{"%u", U} | Values];
|
||||
_ -> Values
|
||||
end
|
||||
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},
|
||||
{deref_aliases, State#state.deref_aliases},
|
||||
{attributes, ["dn"]}]) of
|
||||
#eldap_search_result{entries = [_|_]} ->
|
||||
DN;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
check_local_filter(_Attrs,
|
||||
#state{lfilter = undefined}) ->
|
||||
%% The local filter is used to check an attribute in ejabberd
|
||||
%% and not in LDAP to limit the load on the LDAP directory.
|
||||
%% A local rule can be either:
|
||||
%% {equal, {"accountStatus",["active"]}}
|
||||
%% {notequal, {"accountStatus",["disabled"]}}
|
||||
%% {ldap_local_filter, {notequal, {"accountStatus",["disabled"]}}}
|
||||
check_local_filter(_Attrs, #state{lfilter = undefined}) ->
|
||||
true;
|
||||
check_local_filter(Attrs,
|
||||
#state{lfilter = LocalFilter}) ->
|
||||
check_local_filter(Attrs, #state{lfilter = LocalFilter}) ->
|
||||
{Operation, FilterMatch} = LocalFilter,
|
||||
local_filter(Operation, Attrs, FilterMatch).
|
||||
|
||||
|
||||
local_filter(equal, Attrs, FilterMatch) ->
|
||||
{Attr, Value} = FilterMatch,
|
||||
case lists:keysearch(Attr, 1, Attrs) of
|
||||
false -> false;
|
||||
{value, {Attr, Value}} -> true;
|
||||
_ -> false
|
||||
false -> false;
|
||||
{value,{Attr,Value}} -> true;
|
||||
_ -> false
|
||||
end;
|
||||
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).
|
||||
result_attrs(#state{uids = UIDs, dn_filter_attrs = DNFilterAttrs}) ->
|
||||
lists:foldl(
|
||||
fun({UID}, Acc) ->
|
||||
[UID | Acc];
|
||||
({UID, _}, Acc) ->
|
||||
[UID | Acc]
|
||||
end, DNFilterAttrs, UIDs).
|
||||
|
||||
build_ufilter(State, VHost) ->
|
||||
UIDs = eldap_utils:uids_domain_subst(VHost, State#state.uids),
|
||||
SubFilter = lists:flatten(eldap_utils:generate_subfilter(UIDs)),
|
||||
case State#state.ufilter of
|
||||
"" -> SubFilter;
|
||||
F -> "(&" ++ SubFilter ++ F ++ ")"
|
||||
end.
|
||||
|
||||
build_sfilter(State, FormattedUIDs) ->
|
||||
SubFilter = lists:flatten(eldap_utils:generate_subfilter(FormattedUIDs)),
|
||||
UserFilter = case State#state.ufilter of
|
||||
"" -> SubFilter;
|
||||
F -> "(&" ++ SubFilter ++ F ++ ")"
|
||||
end,
|
||||
eldap_filter:do_sub(UserFilter, [{"%u", "*"}]).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Auxiliary functions
|
||||
%%%----------------------------------------------------------------------
|
||||
parse_options(Host) ->
|
||||
Cfg = eldap_utils:get_config(Host, []),
|
||||
Eldap_ID = jlib:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)),
|
||||
Bind_Eldap_ID = jlib:atom_to_binary(
|
||||
gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
|
||||
UIDsTemp = eldap_utils:get_opt(
|
||||
{ldap_uids, Host}, [],
|
||||
fun(Us) ->
|
||||
lists:map(
|
||||
fun({U, P}) ->
|
||||
{iolist_to_binary(U),
|
||||
iolist_to_binary(P)};
|
||||
({U}) ->
|
||||
{iolist_to_binary(U)}
|
||||
end, Us)
|
||||
end, [{<<"uid">>, <<"%u">>}]),
|
||||
UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp),
|
||||
SubFilter = eldap_utils:generate_subfilter(UIDs),
|
||||
UserFilter = case eldap_utils:get_opt(
|
||||
{ldap_filter, Host}, [],
|
||||
fun check_filter/1, <<"">>) of
|
||||
<<"">> ->
|
||||
SubFilter;
|
||||
F ->
|
||||
<<"(&", SubFilter/binary, F/binary, ")">>
|
||||
end,
|
||||
SearchFilter = eldap_filter:do_sub(UserFilter,
|
||||
[{<<"%u">>, <<"*">>}]),
|
||||
Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, ?MODULE)),
|
||||
Bind_Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
|
||||
LDAPServers = ejabberd_config:get_local_option({ldap_servers, Host}),
|
||||
LDAPBackups = case ejabberd_config:get_local_option({ldap_backups, Host}) of
|
||||
undefined -> [];
|
||||
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;
|
||||
starttls -> ?LDAP_PORT;
|
||||
_ -> ?LDAP_PORT
|
||||
end;
|
||||
P -> P
|
||||
end,
|
||||
RootDN = case ejabberd_config:get_local_option({ldap_rootdn, Host}) of
|
||||
undefined -> "";
|
||||
RDN -> RDN
|
||||
end,
|
||||
Password = case ejabberd_config:get_local_option({ldap_password, Host}) of
|
||||
undefined -> "";
|
||||
Pass -> Pass
|
||||
end,
|
||||
UIDs = case ejabberd_config:get_local_option({ldap_uids, Host}) of
|
||||
undefined -> [{"uid", "%u"}];
|
||||
UI -> eldap_utils:uids_domain_subst(Host, UI)
|
||||
end,
|
||||
SubFilter = lists:flatten(eldap_utils:generate_subfilter(UIDs)),
|
||||
UserFilter = case ejabberd_config:get_local_option({ldap_filter, Host}) of
|
||||
undefined -> "";
|
||||
"" -> "";
|
||||
F ->
|
||||
eldap_utils:check_filter(F),
|
||||
"(&" ++ SubFilter ++ F ++ ")"
|
||||
end,
|
||||
LDAPBase = ejabberd_config:get_local_option({ldap_base, Host}),
|
||||
{DNFilter, DNFilterAttrs} =
|
||||
eldap_utils:get_opt({ldap_dn_filter, Host}, [],
|
||||
fun({DNF, DNFA}) ->
|
||||
NewDNFA = case DNFA of
|
||||
undefined ->
|
||||
[];
|
||||
_ ->
|
||||
[iolist_to_binary(A)
|
||||
|| A <- DNFA]
|
||||
end,
|
||||
NewDNF = check_filter(DNF),
|
||||
{NewDNF, NewDNFA}
|
||||
end, {undefined, []}),
|
||||
LocalFilter = eldap_utils:get_opt(
|
||||
{ldap_local_filter, Host}, [], fun(V) -> V end),
|
||||
#state{host = Host, eldap_id = Eldap_ID,
|
||||
bind_eldap_id = Bind_Eldap_ID,
|
||||
servers = Cfg#eldap_config.servers,
|
||||
backups = Cfg#eldap_config.backups,
|
||||
port = Cfg#eldap_config.port,
|
||||
tls_options = Cfg#eldap_config.tls_options,
|
||||
dn = Cfg#eldap_config.dn,
|
||||
password = Cfg#eldap_config.password,
|
||||
base = Cfg#eldap_config.base,
|
||||
deref_aliases = Cfg#eldap_config.deref_aliases,
|
||||
uids = UIDs, ufilter = UserFilter,
|
||||
sfilter = SearchFilter, lfilter = LocalFilter,
|
||||
dn_filter = DNFilter, dn_filter_attrs = DNFilterAttrs}.
|
||||
|
||||
check_filter(F) ->
|
||||
NewF = iolist_to_binary(F),
|
||||
{ok, _} = eldap_filter:parse(NewF),
|
||||
NewF.
|
||||
case ejabberd_config:get_local_option({ldap_dn_filter, Host}) of
|
||||
undefined ->
|
||||
{undefined, []};
|
||||
{DNF, undefined} ->
|
||||
{DNF, []};
|
||||
{DNF, DNFA} ->
|
||||
{DNF, DNFA}
|
||||
end,
|
||||
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,
|
||||
tls_options = [{encrypt, LDAPEncrypt},
|
||||
{tls_verify, LDAPTLSVerify},
|
||||
{tls_cacertfile, LDAPTLSCAFile},
|
||||
{tls_depth, LDAPTLSDepth}],
|
||||
dn = RootDN,
|
||||
password = Password,
|
||||
base = LDAPBase,
|
||||
uids = UIDs,
|
||||
ufilter = UserFilter,
|
||||
lfilter = LocalFilter,
|
||||
deref_aliases = DerefAliases,
|
||||
dn_filter = DNFilter,
|
||||
dn_filter_attrs = DNFilterAttrs
|
||||
}.
|
||||
|
||||
@@ -1,242 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_auth_odbc.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Authentification via ODBC
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@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(ejabberd_auth_odbc).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
%% External exports
|
||||
-export([start/1, set_password/3, check_password/3,
|
||||
check_password/5, 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").
|
||||
|
||||
start(_Host) -> ok.
|
||||
|
||||
plain_password_required() -> false.
|
||||
|
||||
store_type() -> plain.
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error -> false;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
try odbc_queries:get_password(LServer, Username) of
|
||||
{selected, [<<"password">>], [[Password]]} ->
|
||||
Password /= <<"">>;
|
||||
{selected, [<<"password">>], [[_Password2]]} ->
|
||||
false; %% Password is not correct
|
||||
{selected, [<<"password">>], []} ->
|
||||
false; %% Account does not exist
|
||||
{error, _Error} ->
|
||||
false %% Typical error is that table doesn't exist
|
||||
catch
|
||||
_:_ ->
|
||||
false %% Typical error is database not accessible
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password, Digest,
|
||||
DigestGen) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error -> false;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
try odbc_queries:get_password(LServer, Username) of
|
||||
%% Account exists, check if password is valid
|
||||
{selected, [<<"password">>], [[Passwd]]} ->
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
{selected, [<<"password">>], []} ->
|
||||
false; %% Account does not exist
|
||||
{error, _Error} ->
|
||||
false %% Typical error is that table doesn't exist
|
||||
catch
|
||||
_:_ ->
|
||||
false %% Typical error is database not accessible
|
||||
end
|
||||
end.
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error -> {error, invalid_jid};
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc:escape(Password),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:set_password_t(LServer,
|
||||
Username, Pass)
|
||||
of
|
||||
{atomic, ok} -> ok;
|
||||
Other -> {error, Other}
|
||||
end
|
||||
end.
|
||||
|
||||
try_register(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error -> {error, invalid_jid};
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc:escape(Password),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:add_user(LServer, Username,
|
||||
Pass)
|
||||
of
|
||||
{updated, 1} -> {atomic, ok};
|
||||
_ -> {atomic, exists}
|
||||
end
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
Servers = ejabberd_config:get_vh_by_auth_method(odbc),
|
||||
lists:flatmap(fun (Server) ->
|
||||
get_vh_registered_users(Server)
|
||||
end,
|
||||
Servers).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:list_users(LServer) of
|
||||
{selected, [<<"username">>], Res} ->
|
||||
[{U, LServer} || [U] <- Res];
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
get_vh_registered_users(Server, Opts) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:list_users(LServer, Opts) of
|
||||
{selected, [<<"username">>], Res} ->
|
||||
[{U, LServer} || [U] <- Res];
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:users_number(LServer) of
|
||||
{selected, [_], [[Res]]} ->
|
||||
jlib:binary_to_integer(Res);
|
||||
_ -> 0
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server, Opts) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:users_number(LServer, Opts) of
|
||||
{selected, [_], [[Res]]} ->
|
||||
jlib:binary_to_integer(Res);
|
||||
_Other -> 0
|
||||
end.
|
||||
|
||||
get_password(User, Server) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error -> false;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:get_password(LServer, Username)
|
||||
of
|
||||
{selected, [<<"password">>], [[Password]]} -> Password;
|
||||
_ -> false
|
||||
end
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error -> <<"">>;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:get_password(LServer, Username)
|
||||
of
|
||||
{selected, [<<"password">>], [[Password]]} -> Password;
|
||||
_ -> <<"">>
|
||||
end
|
||||
end.
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error -> false;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
try odbc_queries:get_password(LServer, Username) of
|
||||
{selected, [<<"password">>], [[_Password]]} ->
|
||||
true; %% Account exists
|
||||
{selected, [<<"password">>], []} ->
|
||||
false; %% Account does not exist
|
||||
{error, Error} -> {error, Error}
|
||||
catch
|
||||
_:B -> {error, B}
|
||||
end
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error -> error;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
catch odbc_queries:del_user(LServer, Username),
|
||||
ok
|
||||
end.
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error -> error;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc:escape(Password),
|
||||
LServer = jlib:nameprep(Server),
|
||||
F = fun () ->
|
||||
Result = odbc_queries:del_user_return_password(LServer,
|
||||
Username,
|
||||
Pass),
|
||||
case Result of
|
||||
{selected, [<<"password">>], [[Password]]} -> ok;
|
||||
{selected, [<<"password">>], []} -> not_exists;
|
||||
_ -> not_allowed
|
||||
end
|
||||
end,
|
||||
{atomic, Result} = odbc_queries:sql_transaction(LServer,
|
||||
F),
|
||||
Result
|
||||
end.
|
||||
+135
-58
@@ -5,7 +5,7 @@
|
||||
%%% Created : 5 Jul 2007 by Evgeniy Khramtsov <xram@jabber.ru>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -24,94 +24,171 @@
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_auth_pam).
|
||||
|
||||
-author('xram@jabber.ru').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
%% External exports
|
||||
-export([start/1, set_password/3, check_password/3,
|
||||
check_password/5, 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]).
|
||||
-export([start/1,
|
||||
stop/1,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
try_register/3,
|
||||
dirty_get_registered_users/0,
|
||||
get_vh_registered_users/1,
|
||||
get_password/2,
|
||||
get_password_s/2,
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
|
||||
%% @spec (Host) -> ok | term()
|
||||
%% Host = string()
|
||||
|
||||
start(_Host) ->
|
||||
case epam:start() of
|
||||
{ok, _} -> ok;
|
||||
{error, {already_started, _}} -> ok;
|
||||
Err -> Err
|
||||
{ok, _} -> ok;
|
||||
{error,{already_started, _}} -> ok;
|
||||
Err -> Err
|
||||
end.
|
||||
|
||||
%% TODO: Stop epam if no other auth_pam are running.
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
%% @spec (User, Server, Password) -> {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
set_password(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
check_password(User, Server, Password, _Digest,
|
||||
_DigestGen) ->
|
||||
%% @spec (User, Server, Password, Digest, DigestGen) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% Digest = string()
|
||||
%% DigestGen = function()
|
||||
|
||||
check_password(User, Server, Password, _StreamID, _Digest) ->
|
||||
check_password(User, Server, Password).
|
||||
|
||||
check_password(User, Host, Password) ->
|
||||
Service = get_pam_service(Host),
|
||||
UserInfo = case get_pam_userinfotype(Host) of
|
||||
username -> User;
|
||||
jid -> <<User/binary, "@", Host/binary>>
|
||||
end,
|
||||
case catch epam:authenticate(Service, UserInfo,
|
||||
Password)
|
||||
of
|
||||
true -> true;
|
||||
_ -> false
|
||||
%% @spec (User, Server, Password) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
Service = get_pam_service(Server),
|
||||
UserInfo = case get_pam_userinfotype(Server) of
|
||||
username -> User;
|
||||
jid -> User++"@"++Server
|
||||
end,
|
||||
case catch epam:authenticate(Service, UserInfo, Password) of
|
||||
true -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
dirty_get_registered_users() -> [].
|
||||
%% @spec () -> [{LUser, LServer}]
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
|
||||
get_vh_registered_users(_Host) -> [].
|
||||
dirty_get_registered_users() ->
|
||||
[].
|
||||
|
||||
get_vh_registered_users(_Host, _) -> [].
|
||||
%% @spec (Server) -> [{LUser, LServer}]
|
||||
%% Server = string()
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
|
||||
get_vh_registered_users_number(_Host) -> 0.
|
||||
get_vh_registered_users(_Server) ->
|
||||
[].
|
||||
|
||||
get_vh_registered_users_number(_Host, _) -> 0.
|
||||
%% @spec (User, Server) -> Password | false
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
get_password(_User, _Server) -> false.
|
||||
get_password(_User, _Server) ->
|
||||
false.
|
||||
|
||||
get_password_s(_User, _Server) -> <<"">>.
|
||||
%% @spec (User, Server) -> Password | nil()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
is_user_exists(User, Host) ->
|
||||
Service = get_pam_service(Host),
|
||||
UserInfo = case get_pam_userinfotype(Host) of
|
||||
username -> User;
|
||||
jid -> <<User/binary, "@", Host/binary>>
|
||||
end,
|
||||
get_password_s(_User, _Server) ->
|
||||
"".
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% TODO: Improve this function to return an error instead of 'false' when
|
||||
%% connection to PAM failed
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
Service = get_pam_service(Server),
|
||||
UserInfo = case get_pam_userinfotype(Server) of
|
||||
username -> User;
|
||||
jid -> User++"@"++Server
|
||||
end,
|
||||
case catch epam:acct_mgmt(Service, UserInfo) of
|
||||
true -> true;
|
||||
_ -> false
|
||||
true -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
remove_user(_User, _Server) -> {error, not_allowed}.
|
||||
%% @spec (User, Server) -> {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
remove_user(_User, _Server, _Password) -> not_allowed.
|
||||
remove_user(_User, _Server) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
plain_password_required() -> true.
|
||||
%% @spec (User, Server, Password) -> not_allowed
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
store_type() -> external.
|
||||
remove_user(_User, _Server, _Password) ->
|
||||
not_allowed.
|
||||
|
||||
get_pam_service(Host) ->
|
||||
ejabberd_config:get_local_option(
|
||||
{pam_service, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"ejabberd">>).
|
||||
%% @spec () -> bool()
|
||||
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
store_type() ->
|
||||
external.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
|
||||
%% @spec (Server) -> string()
|
||||
%% Server = string()
|
||||
|
||||
get_pam_service(Server) ->
|
||||
case ejabberd_config:get_local_option({pam_service, Server}) of
|
||||
undefined -> "ejabberd";
|
||||
Service -> Service
|
||||
end.
|
||||
get_pam_userinfotype(Host) ->
|
||||
ejabberd_config:get_local_option(
|
||||
{pam_userinfotype, Host},
|
||||
fun(username) -> username;
|
||||
(jid) -> jid
|
||||
end,
|
||||
username).
|
||||
case ejabberd_config:get_local_option({pam_userinfotype, Host}) of
|
||||
undefined -> username;
|
||||
Type -> Type
|
||||
end.
|
||||
|
||||
@@ -1,285 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_auth_riak.erl
|
||||
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Purpose : Authentification via Riak
|
||||
%%% Created : 12 Nov 2012 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(ejabberd_auth_riak).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
%% External exports
|
||||
-export([start/1, set_password/3, check_password/3,
|
||||
check_password/5, 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, export/1,
|
||||
plain_password_required/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
|
||||
password = <<"">> :: binary() | scram() | '_'}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
start(_Host) ->
|
||||
ok.
|
||||
|
||||
plain_password_required() ->
|
||||
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),
|
||||
case ejabberd_riak:get(passwd, {LUser, LServer}) of
|
||||
{ok, #passwd{password = Password}} when is_binary(Password) ->
|
||||
Password /= <<"">>;
|
||||
{ok, #passwd{password = Scram}} when is_record(Scram, scram) ->
|
||||
is_password_scram_valid(Password, Scram);
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password, Digest,
|
||||
DigestGen) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, {LUser, LServer}) of
|
||||
{ok, #passwd{password = Passwd}} when is_binary(Passwd) ->
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
{ok, #passwd{password = Scram}}
|
||||
when is_record(Scram, scram) ->
|
||||
Passwd = jlib:decode_base64(Scram#scram.storedkey),
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
true ->
|
||||
Password2 = case is_scrammed() and is_binary(Password)
|
||||
of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
ok = ejabberd_riak:put(#passwd{us = US, password = Password2},
|
||||
[{'2i', [{<<"host">>, LServer}]}])
|
||||
end.
|
||||
|
||||
try_register(User, Server, PasswordList) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
Password = iolist_to_binary(PasswordList),
|
||||
US = {LUser, LServer},
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
true ->
|
||||
case ejabberd_riak:get(passwd, US) of
|
||||
{error, notfound} ->
|
||||
Password2 = case is_scrammed() and
|
||||
is_binary(Password)
|
||||
of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
{atomic, ejabberd_riak:put(
|
||||
#passwd{us = US,
|
||||
password = Password2},
|
||||
[{'2i', [{<<"host">>, LServer}]}])};
|
||||
{ok, _} ->
|
||||
exists;
|
||||
Err ->
|
||||
{atomic, Err}
|
||||
end
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
lists:flatmap(
|
||||
fun(Server) ->
|
||||
get_vh_registered_users(Server)
|
||||
end, ejabberd_config:get_vh_by_auth_method(riak)).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
case ejabberd_riak:get_keys_by_index(passwd, <<"host">>, LServer) of
|
||||
{ok, Users} ->
|
||||
Users;
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
case ejabberd_riak:count_by_index(passwd, <<"host">>, LServer) of
|
||||
{ok, N} ->
|
||||
N;
|
||||
_ ->
|
||||
0
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
|
||||
get_password(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, {LUser, LServer}) of
|
||||
{ok, #passwd{password = Password}}
|
||||
when is_binary(Password) ->
|
||||
Password;
|
||||
{ok, #passwd{password = Scram}}
|
||||
when is_record(Scram, scram) ->
|
||||
{jlib:decode_base64(Scram#scram.storedkey),
|
||||
jlib:decode_base64(Scram#scram.serverkey),
|
||||
jlib:decode_base64(Scram#scram.salt),
|
||||
Scram#scram.iterationcount};
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, {LUser, LServer}) of
|
||||
{ok, #passwd{password = Password}}
|
||||
when is_binary(Password) ->
|
||||
Password;
|
||||
{ok, #passwd{password = Scram}}
|
||||
when is_record(Scram, scram) ->
|
||||
<<"">>;
|
||||
_ -> <<"">>
|
||||
end.
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, {LUser, LServer}) of
|
||||
{error, notfound} -> false;
|
||||
{ok, _} -> true;
|
||||
Err -> Err
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
ejabberd_riak:delete(passwd, {LUser, LServer}),
|
||||
ok.
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, {LUser, LServer}) of
|
||||
{ok, #passwd{password = Password}}
|
||||
when is_binary(Password) ->
|
||||
ejabberd_riak:delete(passwd, {LUser, LServer}),
|
||||
ok;
|
||||
{ok, #passwd{password = Scram}}
|
||||
when is_record(Scram, scram) ->
|
||||
case is_password_scram_valid(Password, Scram) of
|
||||
true ->
|
||||
ejabberd_riak:delete(passwd, {LUser, LServer}),
|
||||
ok;
|
||||
false -> not_allowed
|
||||
end;
|
||||
_ -> not_exists
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% SCRAM
|
||||
%%%
|
||||
|
||||
is_scrammed() ->
|
||||
scram ==
|
||||
ejabberd_config:get_local_option({auth_password_format, ?MYNAME},
|
||||
fun(V) -> V end).
|
||||
|
||||
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 = jlib:encode_base64(StoredKey),
|
||||
serverkey = jlib:encode_base64(ServerKey),
|
||||
salt = jlib:encode_base64(Salt),
|
||||
iterationcount = IterationCount}.
|
||||
|
||||
is_password_scram_valid(Password, Scram) ->
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
Salt = jlib:decode_base64(Scram#scram.salt),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
jlib:decode_base64(Scram#scram.storedkey) == StoredKey.
|
||||
|
||||
export(_Server) ->
|
||||
[{passwd,
|
||||
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc:escape(Password),
|
||||
[[<<"delete from users where username='">>, Username, <<"';">>],
|
||||
[<<"insert into users(username, password) "
|
||||
"values ('">>, Username, <<"', '">>, Pass, <<"');">>]];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
@@ -0,0 +1,655 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_auth_storage.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>, Stephan Maka
|
||||
%%% Purpose : Authentification via gen_storage
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%% Database schema (version / storage / table)
|
||||
%%%
|
||||
%%% 2.1.x / mnesia / passwd
|
||||
%%% us = {Username::string(), Host::string()}
|
||||
%%% password = string()
|
||||
%%%
|
||||
%%% 2.1.x / odbc / users
|
||||
%%% username = varchar250
|
||||
%%% password = text
|
||||
%%%
|
||||
%%% 3.0.0-prealpha / mnesia / passwd
|
||||
%%% Same as 2.1.x
|
||||
%%%
|
||||
%%% 3.0.0-prealpha / odbc / users
|
||||
%%% Same as 2.1.x
|
||||
%%%
|
||||
%%% 3.0.0-alpha / mnesia / passwd
|
||||
%%% user_host = {Username::string(), Host::string()}
|
||||
%%% password = string()
|
||||
%%%
|
||||
%%% 3.0.0-beta / mnesia / passwd
|
||||
%%% user_host = {Username::string(), Host::string()}
|
||||
%%% password = string()
|
||||
%%% storedkey = base64 binary()
|
||||
%%% serverkey = base64 binary()
|
||||
%%% iterationcount = integer()
|
||||
%%% salt = base64 binary()
|
||||
%%%
|
||||
%%% 3.0.0-alpha / odbc / passwd
|
||||
%%% user = varchar150
|
||||
%%% host = varchar150
|
||||
%%% password = text
|
||||
%%%
|
||||
%%% 3.0.0-beta / odbc / passwd
|
||||
%%% user = varchar150
|
||||
%%% host = varchar150
|
||||
%%% password = base64 text
|
||||
%%% storedkey = base64 text
|
||||
%%% serverkey = base64 text
|
||||
%%% iterationcount = integer
|
||||
%%% salt = base64 text
|
||||
|
||||
-module(ejabberd_auth_storage).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
%% External exports
|
||||
-export([start/1,
|
||||
stop/1,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
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").
|
||||
|
||||
-record(passwd, {user_host, password, storedkey, serverkey, salt, iterationcount}).
|
||||
-record(reg_users_counter, {vhost, count}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%% @spec (Host) -> ok
|
||||
%% Host = string()
|
||||
|
||||
start(Host) ->
|
||||
Backend =
|
||||
case ejabberd_config:get_local_option({auth_storage, Host}) of
|
||||
undefined -> mnesia;
|
||||
B -> B
|
||||
end,
|
||||
HostB = list_to_binary(Host),
|
||||
gen_storage:create_table(Backend, HostB, passwd,
|
||||
[{odbc_host, Host},
|
||||
{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, passwd)},
|
||||
{types, [{user_host, {text, text}},
|
||||
{storedkey, binary},
|
||||
{serverkey, binary},
|
||||
{salt, binary},
|
||||
{iterationcount, int}]}
|
||||
]),
|
||||
update_table(Host, Backend),
|
||||
maybe_scram_passwords(HostB),
|
||||
mnesia:create_table(reg_users_counter,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, reg_users_counter)}]),
|
||||
update_reg_users_counter_table(Host),
|
||||
maybe_alert_password_scrammed_without_option(HostB),
|
||||
ok.
|
||||
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
update_reg_users_counter_table(Server) ->
|
||||
Set = get_vh_registered_users(Server),
|
||||
Size = length(Set),
|
||||
LServer = exmpp_jid:prep_domain(exmpp_jid:parse(Server)),
|
||||
F = fun() ->
|
||||
mnesia:write(#reg_users_counter{vhost = LServer,
|
||||
count = Size})
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
%% @spec () -> bool()
|
||||
|
||||
plain_password_required() ->
|
||||
case is_scrammed(?MYNAME) of
|
||||
false -> false;
|
||||
true -> true
|
||||
end.
|
||||
|
||||
store_type() ->
|
||||
case is_scrammed(?MYNAME) of
|
||||
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
|
||||
true -> scram %% allows: PLAIN SCRAM
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
LUser = exmpp_stringprep:nodeprep(User),
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch gen_storage:dirty_read(LServer, {passwd, US}) of
|
||||
[#passwd{password = ""} = Passwd] ->
|
||||
is_password_scram_valid(Password, Passwd);
|
||||
[#passwd{password = Password}] ->
|
||||
Password /= "";
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password, Digest, DigestGen) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% Digest = string()
|
||||
%% DigestGen = function()
|
||||
|
||||
check_password(User, Server, Password, Digest, DigestGen) ->
|
||||
LUser = exmpp_stringprep:nodeprep(User),
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch gen_storage:dirty_read(LServer, {passwd, US}) of
|
||||
[#passwd{password = ""} = Passwd] ->
|
||||
Storedkey = base64:decode(Passwd#passwd.storedkey),
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
Digest == DigestGen(Storedkey);
|
||||
true ->
|
||||
false
|
||||
end,
|
||||
if DigRes ->
|
||||
true;
|
||||
true ->
|
||||
(Storedkey == Password) and (Password /= "")
|
||||
end;
|
||||
[#passwd{password = Passwd}] ->
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true ->
|
||||
false
|
||||
end,
|
||||
if DigRes ->
|
||||
true;
|
||||
true ->
|
||||
(Passwd == Password) and (Password /= "")
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
LUser = (catch exmpp_stringprep:nodeprep(User)),
|
||||
LServer = (catch exmpp_stringprep:nameprep(Server)),
|
||||
case {LUser, LServer} of
|
||||
{{stringprep, _, invalid_string, _}, _} ->
|
||||
{error, invalid_jid};
|
||||
{_, {stringprep, _, invalid_string, _}} ->
|
||||
{error, invalid_jid};
|
||||
US ->
|
||||
%% TODO: why is this a transaction?
|
||||
F = fun() ->
|
||||
Passwd = case is_scrammed(LServer) and (Password /= "") of
|
||||
true -> password_to_scram(Password, #passwd{user_host=US});
|
||||
false -> #passwd{user_host = US, password = Password}
|
||||
end,
|
||||
gen_storage:write(LServer, Passwd)
|
||||
end,
|
||||
{atomic, ok} = gen_storage:transaction(LServer, passwd, F),
|
||||
ok
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {aborted, Reason}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
try_register(User, Server, Password) ->
|
||||
LUser = (catch exmpp_stringprep:nodeprep(User)),
|
||||
LServer = (catch exmpp_stringprep:nameprep(Server)),
|
||||
case {LUser, LServer} of
|
||||
{{stringprep, _, invalid_string, _}, _} ->
|
||||
{error, invalid_jid};
|
||||
{_, {stringprep, _, invalid_string, _}} ->
|
||||
{error, invalid_jid};
|
||||
US ->
|
||||
F = fun() ->
|
||||
case gen_storage:read(LServer, {passwd, US}) of
|
||||
[] ->
|
||||
Passwd = case is_scrammed(LServer) and (Password /= "") of
|
||||
true -> password_to_scram(Password, #passwd{user_host=US});
|
||||
false -> #passwd{user_host = US, password = Password}
|
||||
end,
|
||||
gen_storage:write(LServer, Passwd),
|
||||
mnesia:dirty_update_counter(
|
||||
reg_users_counter,
|
||||
exmpp_jid:prep_domain(exmpp_jid:parse(Server)), 1),
|
||||
ok;
|
||||
[_E] ->
|
||||
exists
|
||||
end
|
||||
end,
|
||||
%% TODO: transaction return value?
|
||||
gen_storage:transaction(LServer, passwd, F)
|
||||
end.
|
||||
|
||||
%% @spec () -> [{LUser, LServer}]
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
%% @doc Get all registered users in Mnesia.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
lists:foldl(
|
||||
fun(HostB, Res) ->
|
||||
get_vh_registered_users(binary_to_list(HostB)) ++ Res
|
||||
end,
|
||||
[],
|
||||
gen_storage:all_table_hosts(passwd)).
|
||||
|
||||
%% @spec (Server) -> [{LUser, LServer}]
|
||||
%% Server = string()
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
lists:map(fun(#passwd{user_host = US}) ->
|
||||
US
|
||||
end,
|
||||
gen_storage:dirty_select(LServer, passwd,
|
||||
[{'=', user_host, {'_', LServer}}])).
|
||||
|
||||
%% @spec (Server, Opts) -> [{LUser, LServer}]
|
||||
%% Server = string()
|
||||
%% Opts = [{Opt, Val}]
|
||||
%% Opt = atom()
|
||||
%% Val = term()
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
%% @doc Return the registered users for the specified host.
|
||||
%%
|
||||
%% `Opts' can be one of the following:
|
||||
%% <ul>
|
||||
%% <li>`[{from, integer()}, {to, integer()}]'</li>
|
||||
%% <li>`[{limit, integer()}, {offset, integer()}]'</li>
|
||||
%% <li>`[{prefix, string()}]'</li>
|
||||
%% <li>`[{prefix, string()}, {from, integer()}, {to, integer()}]'</li>
|
||||
%% <li>`[{prefix, string()}, {limit, integer()}, {offset, integer()}]'</li>
|
||||
%% </ul>
|
||||
|
||||
get_vh_registered_users(Server, [{from, Start}, {to, End}])
|
||||
when is_integer(Start) and is_integer(End) ->
|
||||
get_vh_registered_users(Server, [{limit, End-Start+1}, {offset, Start}]);
|
||||
|
||||
get_vh_registered_users(Server, [{limit, Limit}, {offset, Offset}])
|
||||
when is_integer(Limit) and is_integer(Offset) ->
|
||||
case get_vh_registered_users(Server) of
|
||||
[] ->
|
||||
[];
|
||||
Users ->
|
||||
Set = lists:keysort(1, Users),
|
||||
L = length(Set),
|
||||
Start = if Offset < 1 -> 1;
|
||||
Offset > L -> L;
|
||||
true -> Offset
|
||||
end,
|
||||
lists:sublist(Set, Start, Limit)
|
||||
end;
|
||||
|
||||
get_vh_registered_users(Server, [{prefix, Prefix}])
|
||||
when is_list(Prefix) ->
|
||||
Set = [{U,S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)],
|
||||
lists:keysort(1, Set);
|
||||
|
||||
get_vh_registered_users(Server, [{prefix, Prefix}, {from, Start}, {to, End}])
|
||||
when is_list(Prefix) and is_integer(Start) and is_integer(End) ->
|
||||
get_vh_registered_users(Server, [{prefix, Prefix}, {limit, End-Start+1}, {offset, Start}]);
|
||||
|
||||
get_vh_registered_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
|
||||
when is_list(Prefix) and is_integer(Limit) and is_integer(Offset) ->
|
||||
case [{U,S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)] of
|
||||
[] ->
|
||||
[];
|
||||
Users ->
|
||||
Set = lists:keysort(1, Users),
|
||||
L = length(Set),
|
||||
Start = if Offset < 1 -> 1;
|
||||
Offset > L -> L;
|
||||
true -> Offset
|
||||
end,
|
||||
lists:sublist(Set, Start, Limit)
|
||||
end;
|
||||
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
%% @spec (Server) -> Users_Number
|
||||
%% Server = string()
|
||||
%% Users_Number = integer()
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
LServer = exmpp_jid:prep_domain(exmpp_jid:parse(Server)),
|
||||
Query = mnesia:dirty_select(
|
||||
reg_users_counter,
|
||||
[{#reg_users_counter{vhost = LServer, count = '$1'},
|
||||
[],
|
||||
['$1']}]),
|
||||
case Query of
|
||||
[Count] ->
|
||||
Count;
|
||||
_ -> 0
|
||||
end.
|
||||
|
||||
%% @spec (Server, [{prefix, Prefix}]) -> Users_Number
|
||||
%% Server = string()
|
||||
%% Prefix = string()
|
||||
%% Users_Number = integer()
|
||||
|
||||
get_vh_registered_users_number(Server, [{prefix, Prefix}]) when is_list(Prefix) ->
|
||||
Set = [{U, S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)],
|
||||
length(Set);
|
||||
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
|
||||
%% @spec (User, Server) -> Password | false
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
get_password(User, Server) ->
|
||||
try
|
||||
LUser = exmpp_stringprep:nodeprep(User),
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch gen_storage:dirty_read(LServer, passwd, US) of
|
||||
[#passwd{password = ""} = Passwd] ->
|
||||
{base64:decode(Passwd#passwd.storedkey),
|
||||
base64:decode(Passwd#passwd.serverkey),
|
||||
base64:decode(Passwd#passwd.salt),
|
||||
Passwd#passwd.iterationcount};
|
||||
[#passwd{password = Password}] ->
|
||||
Password;
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
catch
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> Password | nil()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
try
|
||||
LUser = exmpp_stringprep:nodeprep(User),
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch gen_storage:dirty_read(LServer, passwd, US) of
|
||||
[#passwd{password = Password}] ->
|
||||
Password;
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
catch
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
try
|
||||
LUser = exmpp_stringprep:nodeprep(User),
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch gen_storage:dirty_read(LServer, {passwd, US}) of
|
||||
[] ->
|
||||
false;
|
||||
[_] ->
|
||||
true;
|
||||
Other ->
|
||||
{error, Other}
|
||||
end
|
||||
catch
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> ok
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% @doc Remove user.
|
||||
%% Note: it returns ok even if there was some problem removing the user.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
try
|
||||
LUser = exmpp_stringprep:nodeprep(User),
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
gen_storage:delete(LServer, {passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
exmpp_jid:prep_domain(exmpp_jid:parse(Server)), -1)
|
||||
end,
|
||||
gen_storage:transaction(LServer, passwd, F),
|
||||
ok
|
||||
catch
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% @doc Remove user if the provided password is correct.
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
try
|
||||
LUser = exmpp_stringprep:nodeprep(User),
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
case gen_storage:read(LServer, {passwd, US}) of
|
||||
[#passwd{password = ""} = Passwd] ->
|
||||
case is_password_scram_valid(Password, Passwd) of
|
||||
true ->
|
||||
gen_storage:delete(LServer, {passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, -1),
|
||||
ok;
|
||||
false ->
|
||||
not_allowed
|
||||
end;
|
||||
[#passwd{password = Password}] ->
|
||||
gen_storage:delete(LServer, {passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
exmpp_jid:prep_domain(exmpp_jid:parse(Server)), -1),
|
||||
ok;
|
||||
_ ->
|
||||
not_exists
|
||||
end
|
||||
end,
|
||||
case gen_storage:transaction(LServer, passwd, F) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{atomic, Res} ->
|
||||
Res
|
||||
end
|
||||
catch
|
||||
_ ->
|
||||
bad_request
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% SCRAM
|
||||
%%%
|
||||
|
||||
%% The passwords are stored scrammed in the table either if the option says so,
|
||||
%% or if at least the first password is empty.
|
||||
is_scrammed(Host) ->
|
||||
case action_password_format(Host) of
|
||||
scram -> true;
|
||||
must_scram -> true;
|
||||
plain -> false;
|
||||
forced_scram -> true
|
||||
end.
|
||||
|
||||
action_password_format(HostB) ->
|
||||
OptionScram = is_option_scram(),
|
||||
case {OptionScram, get_format_first_element(HostB)} of
|
||||
{true, scram} -> scram;
|
||||
{true, any} -> scram;
|
||||
{true, plain} -> must_scram;
|
||||
{false, plain} -> plain;
|
||||
{false, any} -> plain;
|
||||
{false, scram} -> forced_scram
|
||||
end.
|
||||
|
||||
get_format_first_element(HostB) ->
|
||||
case gen_storage:dirty_select(HostB, passwd, []) of
|
||||
[] -> any;
|
||||
[#passwd{password = ""} | _] -> scram;
|
||||
[#passwd{} | _] -> plain
|
||||
end.
|
||||
|
||||
is_option_scram() ->
|
||||
scram == ejabberd_config:get_local_option({auth_password_format, ?MYNAME}).
|
||||
|
||||
maybe_alert_password_scrammed_without_option(Host) ->
|
||||
case is_scrammed(Host) 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(Host) ->
|
||||
case action_password_format(Host) of
|
||||
must_scram -> scram_passwords(Host);
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
scram_passwords(Host) ->
|
||||
Backend =
|
||||
case ejabberd_config:get_local_option({auth_storage, Host}) of
|
||||
undefined -> mnesia;
|
||||
B -> B
|
||||
end,
|
||||
scram_passwords(Host, Backend).
|
||||
scram_passwords(Host, mnesia) ->
|
||||
?INFO_MSG("Converting the passwords stored in mnesia for host ~p into SCRAM bits", [Host]),
|
||||
gen_storage_migration:migrate_mnesia(
|
||||
Host, passwd,
|
||||
[{passwd, [user_host, password, storedkey, serverkey, salt, iterationcount],
|
||||
fun(#passwd{password = Password} = Passwd) ->
|
||||
password_to_scram(Password, Passwd)
|
||||
end}]);
|
||||
scram_passwords(Host, odbc) ->
|
||||
?INFO_MSG("Converting the passwords stored in odbc for host ~p into SCRAM bits", [Host]),
|
||||
gen_storage_migration:migrate_odbc(
|
||||
Host, [passwd],
|
||||
[{"passwd", ["user", "host", "password", "storedkey", "serverkey", "salt", "iterationcount"],
|
||||
fun(_, User, Host2, Password, _Storedkey, _Serverkey, _Iterationcount, _Salt) ->
|
||||
password_to_scram(Password, #passwd{user_host = {User, Host2}})
|
||||
end}]).
|
||||
|
||||
password_to_scram(Password, Passwd) ->
|
||||
password_to_scram(Password, Passwd, ?SCRAM_DEFAULT_ITERATION_COUNT).
|
||||
|
||||
password_to_scram(Password, Passwd, 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),
|
||||
Passwd#passwd{password = "",
|
||||
storedkey = base64:encode(StoredKey),
|
||||
salt = base64:encode(Salt),
|
||||
iterationcount = IterationCount,
|
||||
serverkey = base64:encode(ServerKey)}.
|
||||
|
||||
is_password_scram_valid(Password, Passwd) ->
|
||||
IterationCount = Passwd#passwd.iterationcount,
|
||||
Salt = base64:decode(Passwd#passwd.salt),
|
||||
SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
|
||||
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
(base64:decode(Passwd#passwd.storedkey) == StoredKey).
|
||||
|
||||
|
||||
update_table(Host, mnesia) ->
|
||||
gen_storage_migration:migrate_mnesia(
|
||||
Host, passwd,
|
||||
[{passwd, [us, password],
|
||||
fun({passwd, {User, _Host}, Password}) ->
|
||||
case is_list(Password) of
|
||||
true ->
|
||||
#passwd{user_host = {User, Host},
|
||||
password = Password};
|
||||
false ->
|
||||
#passwd{user_host = {User, Host},
|
||||
password = "",
|
||||
storedkey = Password#scram.storedkey,
|
||||
serverkey = Password#scram.serverkey,
|
||||
salt = Password#scram.salt,
|
||||
iterationcount = Password#scram.iterationcount}
|
||||
end
|
||||
end}]);
|
||||
update_table(Host, odbc) ->
|
||||
gen_storage_migration:migrate_odbc(
|
||||
Host, [passwd],
|
||||
[{"users", ["username", "password"],
|
||||
fun(_, User, Password) ->
|
||||
#passwd{user_host = {User, Host},
|
||||
password = Password}
|
||||
end}]).
|
||||
+2013
-3046
File diff suppressed because it is too large
Load Diff
@@ -1,93 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% 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
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-ifndef(mod_privacy_hrl).
|
||||
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
-endif.
|
||||
|
||||
%-define(SETS, gb_sets).
|
||||
-define(SETS, ejabberd_sets).
|
||||
|
||||
-define(DICT, dict).
|
||||
|
||||
-record(state,
|
||||
{socket,
|
||||
sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
|
||||
socket_monitor = make_ref() :: reference(),
|
||||
xml_socket = false :: boolean(),
|
||||
streamid = <<"">> :: binary(),
|
||||
sasl_state :: any(),
|
||||
access :: atom(),
|
||||
shaper = none :: shaper:shaper(),
|
||||
zlib = false :: boolean(),
|
||||
tls = false :: boolean(),
|
||||
tls_required = false :: boolean(),
|
||||
tls_enabled = false :: boolean(),
|
||||
tls_options = [] :: list(),
|
||||
authenticated = false :: boolean() | replaced | rebinded,
|
||||
jid = #jid{} :: jid(),
|
||||
user = <<"">> :: binary(),
|
||||
server = ?MYNAME :: binary(),
|
||||
resource = <<"">> :: binary(),
|
||||
sid = {now(), self()} :: ejabberd_sm:sid(),
|
||||
pres_t = (?SETS):new() :: ?SETS:ej_set() | {pres_t, non_neg_integer()},
|
||||
pres_f = (?SETS):new() :: ?SETS:ej_set() | {pres_f, non_neg_integer()},
|
||||
pres_a = (?SETS):new() :: ?SETS:ej_set() | {pres_a, non_neg_integer()},
|
||||
pres_last :: xmlel(),
|
||||
pres_timestamp :: calendar:datetime(),
|
||||
privacy_list = #userlist{} :: userlist(),
|
||||
conn = unknown :: atom(),
|
||||
auth_module = unknown :: atom(),
|
||||
ip :: {inet:ip_address(), inet:port_number()},
|
||||
redirect = false :: boolean(),
|
||||
aux_fields = [] :: [{atom(), any()}],
|
||||
fsm_limit_opts = [] :: [{atom(), any()}],
|
||||
lang = ?MYLANG :: binary(),
|
||||
debug = false :: boolean(),
|
||||
flash_hack = false :: boolean(),
|
||||
flash_connection = false :: boolean(),
|
||||
reception = true :: boolean(),
|
||||
standby = false :: boolean(),
|
||||
queue = queue:new() :: queue(),
|
||||
queue_len = 0 :: integer(),
|
||||
pres_queue = gb_trees:empty() :: gb_tree(),
|
||||
keepalive_timer :: reference(),
|
||||
keepalive_timeout :: timeout(),
|
||||
oor_timeout :: timeout(),
|
||||
oor_status = <<"">> :: binary(),
|
||||
oor_show = <<"">> :: binary(),
|
||||
oor_notification :: xmlel(),
|
||||
oor_send_body = all :: first_per_user | first | all | none,
|
||||
oor_send_groupchat = false :: boolean(),
|
||||
oor_send_from = jid :: jid | username | name | none,
|
||||
oor_appid = <<"">> :: binary(),
|
||||
oor_unread = 0 :: integer(),
|
||||
oor_unread_users = (?SETS):new() :: ?SETS:ej_set(),
|
||||
oor_unread_client = 0 :: integer(),
|
||||
oor_offline = false :: boolean(),
|
||||
ack_enabled = false :: boolean(),
|
||||
ack_counter = 0 :: integer(),
|
||||
ack_queue = queue:new() :: queue(),
|
||||
ack_timer :: reference()}).
|
||||
|
||||
-type c2s_state() :: #state{}.
|
||||
+22
-25
@@ -6,7 +6,7 @@
|
||||
%%% Created : 2 Nov 2007 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -26,38 +26,35 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_c2s_config).
|
||||
|
||||
-author('mremond@process-one.net').
|
||||
|
||||
-export([get_c2s_limits/0]).
|
||||
|
||||
%% Get first c2s configuration limitations to apply it to other c2s
|
||||
%% connectors.
|
||||
get_c2s_limits() ->
|
||||
case ejabberd_config:get_local_option(listen, fun(V) -> V end) of
|
||||
undefined -> [];
|
||||
C2SFirstListen ->
|
||||
case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of
|
||||
false -> [];
|
||||
{value, {_Port, ejabberd_c2s, Opts}} ->
|
||||
select_opts_values(Opts)
|
||||
end
|
||||
case ejabberd_config:get_local_option(listen) of
|
||||
undefined ->
|
||||
[];
|
||||
C2SFirstListen ->
|
||||
case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of
|
||||
false ->
|
||||
[];
|
||||
{value, {_Port, ejabberd_c2s, Opts}} ->
|
||||
select_opts_values(Opts)
|
||||
end
|
||||
end.
|
||||
|
||||
%% Only get access, shaper and max_stanza_size values
|
||||
select_opts_values(Opts) ->
|
||||
select_opts_values(Opts, []).
|
||||
|
||||
select_opts_values([], SelectedValues) ->
|
||||
SelectedValues;
|
||||
select_opts_values([{access, Value} | Opts],
|
||||
SelectedValues) ->
|
||||
select_opts_values(Opts,
|
||||
[{access, Value} | SelectedValues]);
|
||||
select_opts_values([{shaper, Value} | Opts],
|
||||
SelectedValues) ->
|
||||
select_opts_values(Opts,
|
||||
[{shaper, Value} | SelectedValues]);
|
||||
select_opts_values([{max_stanza_size, Value} | Opts],
|
||||
SelectedValues) ->
|
||||
select_opts_values(Opts,
|
||||
[{max_stanza_size, Value} | SelectedValues]);
|
||||
select_opts_values([_Opt | Opts], SelectedValues) ->
|
||||
select_opts_values([{access,Value}|Opts], SelectedValues) ->
|
||||
select_opts_values(Opts, [{access, Value}|SelectedValues]);
|
||||
select_opts_values([{shaper,Value}|Opts], SelectedValues) ->
|
||||
select_opts_values(Opts, [{shaper, Value}|SelectedValues]);
|
||||
select_opts_values([{max_stanza_size,Value}|Opts], SelectedValues) ->
|
||||
select_opts_values(Opts, [{max_stanza_size, Value}|SelectedValues]);
|
||||
select_opts_values([_Opt|Opts], SelectedValues) ->
|
||||
select_opts_values(Opts, SelectedValues).
|
||||
|
||||
|
||||
+657
-556
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
%%% Created : 27 Feb 2008 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -31,6 +31,8 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_config.hrl").
|
||||
|
||||
-compile([export_all]).
|
||||
|
||||
%% TODO:
|
||||
%% We want to implement library checking at launch time to issue
|
||||
%% human readable user messages.
|
||||
@@ -44,6 +46,8 @@ config() ->
|
||||
check_database_modules() ->
|
||||
[check_database_module(M)||M<-get_db_used()].
|
||||
|
||||
check_database_module(host) ->
|
||||
ok;
|
||||
check_database_module(odbc) ->
|
||||
check_modules(odbc, [odbc, odbc_app, odbc_sup, ejabberd_odbc, ejabberd_odbc_sup, odbc_queries]);
|
||||
check_database_module(mysql) ->
|
||||
@@ -84,8 +88,8 @@ get_db_used() ->
|
||||
DBs = lists:foldr(
|
||||
fun([Domain, DB], Acc) ->
|
||||
case check_odbc_option(
|
||||
ejabberd_config:get_local_option(
|
||||
{auth_method, Domain}, fun(V) -> V end)) of
|
||||
ejabberd_config:get_local_option
|
||||
({auth_method, Domain})) of
|
||||
true -> [get_db_type(DB)|Acc];
|
||||
_ -> Acc
|
||||
end
|
||||
|
||||
+87
-167
@@ -1,7 +1,7 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : ejabberd_cluster.erl
|
||||
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Description :
|
||||
%%% Description :
|
||||
%%%
|
||||
%%% Created : 2 Apr 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
@@ -10,248 +10,168 @@
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/0, get_node/1, get_node_new/1,
|
||||
announce/1, shutdown/0, node_id/0, get_node_by_id/1,
|
||||
get_nodes/0, rehash_timeout/0, start/0,
|
||||
shutdown_migrate/1, migrate_timeout/0]).
|
||||
-export([start_link/0, get_node/1, get_node_new/1, announce/0,
|
||||
node_id/0, get_node_by_id/1, get_nodes/0, rehash_timeout/0]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-define(HASHTBL, nodes_hash).
|
||||
|
||||
-define(HASHTBL_NEW, nodes_hash_new).
|
||||
|
||||
-define(POINTS, 64).
|
||||
|
||||
-define(REHASH_TIMEOUT, timer:seconds(30)).
|
||||
|
||||
-define(MIGRATE_TIMEOUT, timer:minutes(2)).
|
||||
|
||||
-define(LOCK, {migrate, node()}).
|
||||
-define(POINTS, 16).
|
||||
-define(REHASH_TIMEOUT, 5000).
|
||||
|
||||
-record(state, {}).
|
||||
-record(?HASHTBL, {hash, node}).
|
||||
-record(?HASHTBL_NEW, {hash, node}).
|
||||
|
||||
start() ->
|
||||
ChildSpec = {?MODULE, {?MODULE, start_link, []},
|
||||
permanent, brutal_kill, worker, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
start_link() -> gen_server:start_link(?MODULE, [], []).
|
||||
|
||||
-spec get_node(any()) -> atom().
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
get_node(Key) ->
|
||||
Hash = erlang:phash2(Key),
|
||||
get_node_by_hash(?HASHTBL, Hash).
|
||||
|
||||
-spec get_node_new(any()) -> atom().
|
||||
|
||||
get_node_new(Key) ->
|
||||
Hash = erlang:phash2(Key),
|
||||
get_node_by_hash(?HASHTBL_NEW, Hash).
|
||||
|
||||
-spec get_nodes() -> [atom()].
|
||||
get_nodes() ->
|
||||
%% TODO
|
||||
mnesia:system_info(running_db_nodes).
|
||||
|
||||
get_nodes() -> mnesia:system_info(running_db_nodes).
|
||||
|
||||
-spec announce(pid()) -> any().
|
||||
|
||||
announce(Pid) ->
|
||||
gen_server:call(Pid, announce, infinity).
|
||||
announce() ->
|
||||
gen_server:call(?MODULE, announce, infinity).
|
||||
|
||||
node_id() ->
|
||||
jlib:integer_to_binary(erlang:phash2(node())).
|
||||
integer_to_list(erlang:phash2(node())).
|
||||
|
||||
rehash_timeout() ->
|
||||
case ejabberd_config:get_local_option(
|
||||
rehash_timeout,
|
||||
fun(I) when is_integer(I), I > 0 -> I end) of
|
||||
undefined -> ?REHASH_TIMEOUT;
|
||||
Secs -> timer:seconds(Secs)
|
||||
end.
|
||||
?REHASH_TIMEOUT.
|
||||
|
||||
migrate_timeout() ->
|
||||
case ejabberd_config:get_local_option(
|
||||
migrate_timeout,
|
||||
fun(N) when is_integer(N), N > 0 -> N end) of
|
||||
undefined -> ?MIGRATE_TIMEOUT;
|
||||
Secs -> timer:seconds(Secs)
|
||||
end.
|
||||
|
||||
-spec get_node_by_id(binary() | atom()) -> atom().
|
||||
|
||||
get_node_by_id(NodeID) when is_binary(NodeID) ->
|
||||
case catch list_to_existing_atom(binary_to_list(NodeID)) of
|
||||
{'EXIT', _} -> node();
|
||||
Res -> get_node_by_id(Res)
|
||||
get_node_by_id(NodeID) when is_list(NodeID) ->
|
||||
case catch list_to_existing_atom(NodeID) of
|
||||
{'EXIT', _} ->
|
||||
node();
|
||||
Res ->
|
||||
get_node_by_id(Res)
|
||||
end;
|
||||
get_node_by_id(NodeID) ->
|
||||
case global:whereis_name(NodeID) of
|
||||
Pid when is_pid(Pid) -> node(Pid);
|
||||
_ -> node()
|
||||
Pid when is_pid(Pid) ->
|
||||
node(Pid);
|
||||
_ ->
|
||||
node()
|
||||
end.
|
||||
|
||||
shutdown() ->
|
||||
lists:foreach(fun (Node) when Node /= node() ->
|
||||
{ejabberd_cluster, Node} ! {node_down, node()};
|
||||
(_) -> ok
|
||||
end,
|
||||
get_nodes()).
|
||||
|
||||
shutdown_migrate(WaitTime) ->
|
||||
delete_node(?HASHTBL_NEW, node()),
|
||||
ejabberd_hooks:run(node_down, [node()]),
|
||||
shutdown(),
|
||||
delete_node(?HASHTBL, node()),
|
||||
ejabberd_hooks:run(node_hash_update,
|
||||
[node(), down, WaitTime]),
|
||||
?INFO_MSG("Waiting ~p seconds for the migration "
|
||||
"to be completed.",
|
||||
[WaitTime div 1000]),
|
||||
timer:sleep(WaitTime),
|
||||
ok.
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([]) ->
|
||||
{A, B, C} = now(),
|
||||
random:seed(A, B, C),
|
||||
net_kernel:monitor_nodes(true, [{node_type, visible}]),
|
||||
mnesia:create_table(?HASHTBL,
|
||||
[{ram_copies, [node()]},
|
||||
{type, ordered_set},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, ?HASHTBL)}]),
|
||||
mnesia:create_table(?HASHTBL_NEW,
|
||||
[{ram_copies, [node()]},
|
||||
{type, ordered_set},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, ?HASHTBL_NEW)}]),
|
||||
mnesia:add_table_copy(?HASHTBL, node(), ram_copies),
|
||||
mnesia:add_table_copy(?HASHTBL_NEW, node(), ram_copies),
|
||||
mnesia:clear_table(?HASHTBL),
|
||||
mnesia:clear_table(?HASHTBL_NEW),
|
||||
ets:new(?HASHTBL, [named_table, public, ordered_set]),
|
||||
ets:new(?HASHTBL_NEW, [named_table, public, ordered_set]),
|
||||
register_node(),
|
||||
AllNodes = get_nodes(),
|
||||
AllNodes = mnesia:system_info(running_db_nodes),
|
||||
OtherNodes = case AllNodes of
|
||||
[_MyNode] -> AllNodes;
|
||||
_ -> AllNodes -- [node()]
|
||||
[_] ->
|
||||
AllNodes;
|
||||
_ ->
|
||||
AllNodes -- [node()]
|
||||
end,
|
||||
append_nodes(?HASHTBL, OtherNodes),
|
||||
append_nodes(?HASHTBL_NEW, AllNodes),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call(announce, _From, State) ->
|
||||
Migrate_timeout = migrate_timeout(),
|
||||
case global:set_lock(?LOCK, get_nodes(), 0) of
|
||||
false ->
|
||||
?WARNING_MSG("Another node is recently attached to "
|
||||
"the cluster and is being rebalanced. "
|
||||
"Waiting for the rebalancing to be completed "
|
||||
"before starting this node. This will "
|
||||
"take at least ~p seconds. Please, be "
|
||||
"patient.",
|
||||
[Migrate_timeout div 1000]),
|
||||
global:set_lock(?LOCK, get_nodes(), infinity);
|
||||
true -> ok
|
||||
case mnesia:system_info(running_db_nodes) of
|
||||
[_MyNode] ->
|
||||
ok;
|
||||
Nodes ->
|
||||
OtherNodes = Nodes -- [node()],
|
||||
lists:foreach(
|
||||
fun(Node) ->
|
||||
{?MODULE, Node} ! {node_ready, node()}
|
||||
end, OtherNodes),
|
||||
?INFO_MSG("waiting for migration from nodes: ~w",
|
||||
[OtherNodes]),
|
||||
timer:sleep(?REHASH_TIMEOUT),
|
||||
append_node(?HASHTBL, node())
|
||||
end,
|
||||
case get_nodes() of
|
||||
[_MyNode] ->
|
||||
register(?MODULE, self()), global:del_lock(?LOCK);
|
||||
Nodes ->
|
||||
OtherNodes = Nodes -- [node()],
|
||||
?INFO_MSG("waiting for migration from nodes: ~w",
|
||||
[OtherNodes]),
|
||||
{_Res, BadNodes} = gen_server:multi_call(OtherNodes,
|
||||
?MODULE,
|
||||
{node_ready, node()},
|
||||
?REHASH_TIMEOUT),
|
||||
append_node(?HASHTBL, node()),
|
||||
register(?MODULE, self()),
|
||||
case OtherNodes -- BadNodes of
|
||||
[] -> global:del_lock(?LOCK);
|
||||
WorkingNodes ->
|
||||
gen_server:abcast(WorkingNodes, ?MODULE,
|
||||
{node_ready, node()}),
|
||||
erlang:send_after(Migrate_timeout, self(), del_lock)
|
||||
end
|
||||
end,
|
||||
{reply, ok, State};
|
||||
handle_call({node_ready, Node}, _From, State) ->
|
||||
?INFO_MSG("node ~p is ready, preparing migration",
|
||||
[Node]),
|
||||
append_node(?HASHTBL_NEW, Node),
|
||||
ejabberd_hooks:run(node_up, [Node]),
|
||||
{reply, ok, State};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok, {reply, Reply, State}.
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast({node_ready, Node}, State) ->
|
||||
?INFO_MSG("adding node ~p to hash and starting "
|
||||
"migration",
|
||||
[Node]),
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({node_ready, Node}, State) ->
|
||||
?INFO_MSG("node ~p is ready, starting migration", [Node]),
|
||||
append_node(?HASHTBL_NEW, Node),
|
||||
ejabberd_hooks:run(node_hash_update, [?REHASH_TIMEOUT]),
|
||||
timer:sleep(?REHASH_TIMEOUT),
|
||||
?INFO_MSG("adding node ~p to hash", [Node]),
|
||||
append_node(?HASHTBL, Node),
|
||||
ejabberd_hooks:run(node_hash_update,
|
||||
[Node, up, migrate_timeout()]),
|
||||
{noreply, State};
|
||||
handle_cast(_Msg, State) -> {noreply, State}.
|
||||
|
||||
handle_info(del_lock, State) ->
|
||||
global:del_lock(?LOCK), {noreply, State};
|
||||
handle_info({node_down, Node}, State) ->
|
||||
delete_node(?HASHTBL, Node),
|
||||
delete_node(?HASHTBL_NEW, Node),
|
||||
{noreply, State};
|
||||
handle_info({nodedown, Node, _}, State) ->
|
||||
?INFO_MSG("node ~p goes down", [Node]),
|
||||
ejabberd_hooks:run(node_down, [Node]),
|
||||
delete_node(?HASHTBL, Node),
|
||||
delete_node(?HASHTBL_NEW, Node),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) -> {noreply, State}.
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) -> ok.
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
append_nodes(Tab, Nodes) ->
|
||||
lists:foreach(fun (Node) -> append_node(Tab, Node) end,
|
||||
Nodes).
|
||||
lists:foreach(
|
||||
fun(Node) ->
|
||||
append_node(Tab, Node)
|
||||
end, Nodes).
|
||||
|
||||
append_node(Tab, Node) ->
|
||||
lists:foreach(
|
||||
fun(I) ->
|
||||
Hash = erlang:phash2({I, Node}),
|
||||
mnesia:dirty_write({Tab, Hash, Node})
|
||||
Hash = erlang:phash2({I, Node}),
|
||||
ets:insert(Tab, {Hash, Node})
|
||||
end, lists:seq(1, ?POINTS)).
|
||||
|
||||
delete_node(Tab, Node) ->
|
||||
lists:foreach(
|
||||
fun(I) ->
|
||||
Hash = erlang:phash2({I, Node}),
|
||||
mnesia:dirty_delete(Tab, Hash)
|
||||
Hash = erlang:phash2({I, Node}),
|
||||
ets:delete(Tab, Hash)
|
||||
end, lists:seq(1, ?POINTS)).
|
||||
|
||||
get_node_by_hash(Tab, Hash) ->
|
||||
NodeHash = case ets:next(Tab, Hash) of
|
||||
'$end_of_table' -> ets:first(Tab);
|
||||
NH -> NH
|
||||
'$end_of_table' ->
|
||||
ets:first(Tab);
|
||||
NH ->
|
||||
NH
|
||||
end,
|
||||
if NodeHash == '$end_of_table' ->
|
||||
node();
|
||||
erlang:error(no_running_nodes);
|
||||
true ->
|
||||
case ets:lookup(Tab, NodeHash) of
|
||||
[] ->
|
||||
get_node_by_hash(Tab, Hash);
|
||||
[{_, _, Node}] ->
|
||||
[{_, Node}] ->
|
||||
Node
|
||||
end
|
||||
end.
|
||||
|
||||
register_node() ->
|
||||
global:register_name(jlib:binary_to_atom(node_id()),
|
||||
self()).
|
||||
global:register_name(list_to_atom(node_id()), self()).
|
||||
|
||||
+14
-36
@@ -5,7 +5,7 @@
|
||||
%%% Created : 20 May 2008 by Badlop <badlop@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -226,22 +226,9 @@
|
||||
|
||||
init() ->
|
||||
ets:new(ejabberd_commands, [named_table, set, public,
|
||||
{keypos, #ejabberd_commands.name}]),
|
||||
register_commands([
|
||||
#ejabberd_commands{name = gen_xmlrpc_docs, tags = [documentation],
|
||||
desc = "Generates html documentation for ejabberd_commands",
|
||||
module = ejabberd_commands_doc, function = generate_output,
|
||||
args = [{file, binary}, {regexp, binary}],
|
||||
result = {res, rescode},
|
||||
args_desc = [<<"Path to file where generated documentation should be stored">>,
|
||||
<<"Commands which name or module is matched by it will be included in output">>],
|
||||
result_desc = <<"0 if command failed, 1 when succedded">>,
|
||||
args_example = [<<"/home/me/docs/api.html">>, <<"mod_admin">>],
|
||||
result_example = ok}]).
|
||||
|
||||
|
||||
-spec register_commands([ejabberd_commands()]) -> ok.
|
||||
{keypos, #ejabberd_commands.name}]).
|
||||
|
||||
%% @spec ([ejabberd_commands()]) -> ok
|
||||
%% @doc Register ejabberd commands.
|
||||
%% If a command is already registered, a warning is printed and the old command is preserved.
|
||||
register_commands(Commands) ->
|
||||
@@ -256,8 +243,7 @@ register_commands(Commands) ->
|
||||
end,
|
||||
Commands).
|
||||
|
||||
-spec unregister_commands([ejabberd_commands()]) -> ok.
|
||||
|
||||
%% @spec ([ejabberd_commands()]) -> ok
|
||||
%% @doc Unregister ejabberd commands.
|
||||
unregister_commands(Commands) ->
|
||||
lists:foreach(
|
||||
@@ -266,8 +252,7 @@ unregister_commands(Commands) ->
|
||||
end,
|
||||
Commands).
|
||||
|
||||
-spec list_commands() -> [{atom(), [aterm()], string()}].
|
||||
|
||||
%% @spec () -> [{Name::atom(), Args::[aterm()], Desc::string()}]
|
||||
%% @doc Get a list of all the available commands, arguments and description.
|
||||
list_commands() ->
|
||||
Commands = ets:match(ejabberd_commands,
|
||||
@@ -277,8 +262,7 @@ list_commands() ->
|
||||
_ = '_'}),
|
||||
[{A, B, C} || [A, B, C] <- Commands].
|
||||
|
||||
-spec get_command_format(atom()) -> {[aterm()], rterm()} | {error, command_unknown}.
|
||||
|
||||
%% @spec (Name::atom()) -> {Args::[aterm()], Result::rterm()} | {error, command_unknown}
|
||||
%% @doc Get the format of arguments and result of a command.
|
||||
get_command_format(Name) ->
|
||||
Matched = ets:match(ejabberd_commands,
|
||||
@@ -293,8 +277,7 @@ get_command_format(Name) ->
|
||||
{Args, Result}
|
||||
end.
|
||||
|
||||
-spec get_command_definition(atom()) -> ejabberd_commands() | command_not_found.
|
||||
|
||||
%% @spec (Name::atom()) -> ejabberd_commands() | command_not_found
|
||||
%% @doc Get the definition record of a command.
|
||||
get_command_definition(Name) ->
|
||||
case ets:lookup(ejabberd_commands, Name) of
|
||||
@@ -308,7 +291,7 @@ execute_command(Name, Arguments) ->
|
||||
execute_command([], noauth, Name, Arguments).
|
||||
|
||||
%% @spec (AccessCommands, Auth, Name::atom(), Arguments) -> ResultTerm | {error, Error}
|
||||
%% where
|
||||
%% where
|
||||
%% AccessCommands = [{Access, CommandNames, Arguments}]
|
||||
%% Auth = {User::string(), Server::string(), Password::string()} | noauth
|
||||
%% Method = atom()
|
||||
@@ -331,8 +314,6 @@ execute_command2(Command, Arguments) ->
|
||||
?DEBUG("Executing command ~p:~p with Args=~p", [Module, Function, Arguments]),
|
||||
apply(Module, Function, Arguments).
|
||||
|
||||
-spec get_tags_commands() -> [{string(), [string()]}].
|
||||
|
||||
%% @spec () -> [{Tag::string(), [CommandName::string()]}]
|
||||
%% @doc Get all the tags and associated commands.
|
||||
get_tags_commands() ->
|
||||
@@ -367,7 +348,7 @@ get_tags_commands() ->
|
||||
%% -----------------------------
|
||||
|
||||
%% @spec (AccessCommands, Auth, Method, Command, Arguments) -> ok
|
||||
%% where
|
||||
%% where
|
||||
%% AccessCommands = [ {Access, CommandNames, Arguments} ]
|
||||
%% Auth = {User::string(), Server::string(), Password::string()} | noauth
|
||||
%% Method = atom()
|
||||
@@ -396,9 +377,6 @@ check_access_commands(AccessCommands, Auth, Method, Command, Arguments) ->
|
||||
L when is_list(L) -> ok
|
||||
end.
|
||||
|
||||
-spec check_auth(noauth) -> noauth_provided;
|
||||
({binary(), binary(), binary()}) -> {ok, binary(), binary()}.
|
||||
|
||||
check_auth(noauth) ->
|
||||
no_auth_provided;
|
||||
check_auth({User, Server, Password}) ->
|
||||
@@ -412,15 +390,15 @@ check_auth({User, Server, Password}) ->
|
||||
end.
|
||||
|
||||
get_md5(AccountPass) ->
|
||||
list_to_binary([io_lib:format("~.16B", [X])
|
||||
|| X <- binary_to_list(crypto:md5(AccountPass))]).
|
||||
lists:flatten([io_lib:format("~.16B", [X])
|
||||
|| X <- binary_to_list(crypto:md5(AccountPass))]).
|
||||
|
||||
check_access(all, _) ->
|
||||
true;
|
||||
check_access(Access, Auth) ->
|
||||
{ok, User, Server} = check_auth(Auth),
|
||||
%% Check this user has access permission
|
||||
case acl:match_rule(Server, Access, jlib:make_jid(User, Server, <<"">>)) of
|
||||
case acl:match_rule(Server, Access, exmpp_jid:make(User, Server, "")) of
|
||||
allow -> true;
|
||||
deny -> false
|
||||
end.
|
||||
@@ -444,8 +422,8 @@ check_access_arguments(Command, ArgumentRestrictions, Arguments) ->
|
||||
|
||||
tag_arguments(ArgsDefs, Args) ->
|
||||
lists:zipwith(
|
||||
fun({ArgName, _ArgType}, ArgValue) ->
|
||||
fun({ArgName, _ArgType}, ArgValue) ->
|
||||
{ArgName, ArgValue}
|
||||
end,
|
||||
end,
|
||||
ArgsDefs,
|
||||
Args).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -19,40 +19,10 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-type aterm() :: {atom(), atype()}.
|
||||
-type atype() :: integer | string | binary |
|
||||
{tuple, [aterm()]} | {list, aterm()}.
|
||||
-type rterm() :: {atom(), rtype()}.
|
||||
-type rtype() :: integer | string | atom |
|
||||
{tuple, [rterm()]} | {list, rterm()} |
|
||||
rescode | restuple.
|
||||
|
||||
-record(ejabberd_commands,
|
||||
{name :: atom(),
|
||||
tags = [] :: [atom()] | '_' | '$2',
|
||||
desc = "" :: string() | '_' | '$3',
|
||||
longdesc = "" :: string() | '_',
|
||||
module :: atom(),
|
||||
function :: atom(),
|
||||
args = [] :: [aterm()] | '_' | '$1' | '$2',
|
||||
result = {res, rescode} :: rterm() | '_' | '$2',
|
||||
args_desc = none :: none | [string()],
|
||||
result_desc = none :: none | string(),
|
||||
args_example = none :: [any()],
|
||||
result_example = none :: any()}).
|
||||
|
||||
-type ejabberd_commands() :: #ejabberd_commands{name :: atom(),
|
||||
tags :: [atom()],
|
||||
desc :: string(),
|
||||
longdesc :: string(),
|
||||
module :: atom(),
|
||||
function :: atom(),
|
||||
args :: [aterm()],
|
||||
result :: rterm(),
|
||||
args_desc :: none | [string()],
|
||||
result_desc :: none | string(),
|
||||
args_example :: [any()],
|
||||
result_example :: any()}.
|
||||
-record(ejabberd_commands, {name, tags = [],
|
||||
desc = "", longdesc = "",
|
||||
module, function,
|
||||
args = [], result = rescode}).
|
||||
|
||||
%% @type ejabberd_commands() = #ejabberd_commands{
|
||||
%% name = atom(),
|
||||
|
||||
@@ -1,415 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_commands_doc.erl
|
||||
%%% Author : Badlop <badlop@process-one.net>
|
||||
%%% Purpose : Management of ejabberd commands
|
||||
%%% Created : 20 May 2008 by Badlop <badlop@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-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_commands_doc).
|
||||
-author('pawel@process-one.net').
|
||||
|
||||
-export([generate_output/2]).
|
||||
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-define(RAW(V), xml:crypt(iolist_to_binary(V))).
|
||||
-define(TAG(N), <<"<", ??N, "/>">>).
|
||||
-define(TAG(N, V), <<"<", ??N, ">">>, V, <<"</", ??N, ">">>).
|
||||
-define(TAG(N, C, V), <<"<", ??N, " class='", C, "'>">>, V, <<"</", ??N, ">">>).
|
||||
-define(TAG_R(N, V), ?TAG(N, ?RAW(V))).
|
||||
-define(TAG_R(N, C, V), ?TAG(N, C, ?RAW(V))).
|
||||
-define(SPAN(N, V), ?TAG_R(span, ??N, V)).
|
||||
|
||||
-define(STR(A), ?SPAN(str,[<<"\"">>, A, <<"\"">>])).
|
||||
-define(NUM(A), ?SPAN(num,jlib:integer_to_binary(A))).
|
||||
-define(FIELD(A), ?SPAN(field,A)).
|
||||
-define(ID(A), ?SPAN(id,A)).
|
||||
-define(OP(A), ?SPAN(op,A)).
|
||||
-define(ARG(A), ?FIELD(atom_to_list(A))).
|
||||
-define(KW(A), ?SPAN(kw,A)).
|
||||
-define(BR, <<"\n">>).
|
||||
|
||||
-define(RAW_L(A), ?RAW(<<A>>)).
|
||||
-define(STR_L(A), ?STR(<<A>>)).
|
||||
-define(FIELD_L(A), ?FIELD(<<A>>)).
|
||||
-define(ID_L(A), ?ID(<<A>>)).
|
||||
-define(OP_L(A), ?OP(<<A>>)).
|
||||
-define(KW_L(A), ?KW(<<A>>)).
|
||||
|
||||
-define(STR_A(A), ?STR(atom_to_list(A))).
|
||||
-define(ID_A(A), ?ID(atom_to_list(A))).
|
||||
|
||||
list_join_with([], _M) ->
|
||||
[];
|
||||
list_join_with([El|Tail], M) ->
|
||||
lists:reverse(lists:foldl(fun(E, Acc) ->
|
||||
[E, M | Acc]
|
||||
end, [El], Tail)).
|
||||
|
||||
rescode_to_int(ok) ->
|
||||
0;
|
||||
rescode_to_int(true) ->
|
||||
0;
|
||||
rescode_to_int(_) ->
|
||||
1.
|
||||
|
||||
perl_gen({Name, integer}, Int, _Indent) ->
|
||||
[?ARG(Name), ?OP_L(" => "), ?NUM(Int)];
|
||||
perl_gen({Name, string}, Str, _Indent) ->
|
||||
[?ARG(Name), ?OP_L(" => "), ?STR(Str)];
|
||||
perl_gen({Name, binary}, Str, _Indent) ->
|
||||
[?ARG(Name), ?OP_L(" => "), ?STR(Str)];
|
||||
perl_gen({Name, atom}, Atom, _Indent) ->
|
||||
[?ARG(Name), ?OP_L(" => "), ?STR(atom_to_list(Atom))];
|
||||
perl_gen({Name, {tuple, Fields}}, Tuple, Indent) ->
|
||||
Res = lists:map(fun({A,B})->perl_gen(A, B, Indent) end, lists:zip(Fields, tuple_to_list(Tuple))),
|
||||
[?ARG(Name), ?OP_L(" => {"), list_join_with(Res, [?OP_L(", ")]), ?OP_L("}")];
|
||||
perl_gen({Name, {list, ElDesc}}, List, Indent) ->
|
||||
Res = lists:map(fun(E) -> [?OP_L("{"), perl_gen(ElDesc, E, Indent), ?OP_L("}")] end, List),
|
||||
[?ARG(Name), ?OP_L(" => ["), list_join_with(Res, [?OP_L(", ")]), ?OP_L("]")].
|
||||
|
||||
perl_call(Name, ArgsDesc, Values) ->
|
||||
[?ID_L("XMLRPC::Lite"), ?OP_L("->"), ?ID_L("proxy"), ?OP_L("("), ?ID_L("$url"), ?OP_L(")->"),
|
||||
?ID_L("call"), ?OP_L("("), ?STR_A(Name), ?OP_L(", {"), ?BR, <<" ">>,
|
||||
list_join_with(lists:map(fun({A,B})->perl_gen(A, B, <<" ">>) end, lists:zip(ArgsDesc, Values)), [?OP_L(","), ?BR, <<" ">>]),
|
||||
?BR, ?OP_L("})->"), ?ID_L("results"), ?OP_L("()")].
|
||||
|
||||
java_gen_map(Vals, Indent) ->
|
||||
{Split, NL} = case Indent of
|
||||
none -> {<<" ">>, <<" ">>};
|
||||
_ -> {[?BR, <<" ", Indent/binary>>], [?BR, Indent]}
|
||||
end,
|
||||
[?KW_L("new "), ?ID_L("HashMap"), ?OP_L("<"), ?ID_L("String"), ?OP_L(", "), ?ID_L("Object"),
|
||||
?OP_L(">() {{"), Split, list_join_with(Vals, Split), NL, ?OP_L("}}")].
|
||||
|
||||
java_gen({Name, integer}, Int, _Indent) ->
|
||||
[?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?KW_L("new "), ?ID_L("Integer"), ?OP_L("("), ?NUM(Int), ?OP_L("));")];
|
||||
java_gen({Name, string}, Str, _Indent) ->
|
||||
[?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?STR(Str), ?OP_L(");")];
|
||||
java_gen({Name, binary}, Str, _Indent) ->
|
||||
[?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?STR(Str), ?OP_L(");")];
|
||||
java_gen({Name, atom}, Atom, _Indent) ->
|
||||
[?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?STR(atom_to_list(Atom)), ?OP_L(");")];
|
||||
java_gen({Name, {tuple, Fields}}, Tuple, Indent) ->
|
||||
NewIndent = <<" ", Indent/binary>>,
|
||||
Res = lists:map(fun({A, B}) -> [java_gen(A, B, NewIndent)] end, lists:zip(Fields, tuple_to_list(Tuple))),
|
||||
[?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), java_gen_map(Res, Indent), ?OP_L(")")];
|
||||
java_gen({Name, {list, ElDesc}}, List, Indent) ->
|
||||
{NI, NI2, I} = case List of
|
||||
[_] -> {" ", " ", Indent};
|
||||
_ -> {[?BR, <<" ", Indent/binary>>],
|
||||
[?BR, <<" ", Indent/binary>>],
|
||||
<<" ", Indent/binary>>}
|
||||
end,
|
||||
Res = lists:map(fun(E) -> java_gen_map([java_gen(ElDesc, E, I)], none) end, List),
|
||||
[?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?KW_L("new "), ?ID_L("Object"), ?OP_L("[] {"), NI,
|
||||
list_join_with(Res, [?OP_L(","), NI]), NI2, ?OP_L("});")].
|
||||
|
||||
java_call(Name, ArgsDesc, Values) ->
|
||||
[?ID_L("XmlRpcClientConfigImpl config"), ?OP_L(" = "), ?KW_L("new "), ?ID_L("XmlRpcClientConfigImpl"), ?OP_L("();"), ?BR,
|
||||
?ID_L("config"), ?OP_L("."), ?ID_L("setServerURL"), ?OP_L("("), ?ID_L("url"), ?OP_L(");"), ?BR, ?BR,
|
||||
?ID_L("XmlRpcClient client"), ?OP_L(" = "), ?KW_L("new "), ?ID_L("XmlRpcClient"), ?OP_L("();"), ?BR,
|
||||
?ID_L("client"), ?OP_L("."), ?ID_L("setConfig"), ?OP_L("("), ?ID_L("config"), ?OP_L(");"), ?BR, ?BR,
|
||||
?ID_L("client"), ?OP_L("."), ?ID_L("execute"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "),
|
||||
java_gen_map(lists:map(fun({A,B})->java_gen(A, B, <<"">>) end, lists:zip(ArgsDesc, Values)), <<"">>), ?OP_L(");"), ?BR].
|
||||
|
||||
-define(XML_S(N, V), ?OP_L("<"), ?FIELD_L(??N), ?OP_L(">"), V).
|
||||
-define(XML_E(N), ?OP_L("</"), ?FIELD_L(??N), ?OP_L(">")).
|
||||
-define(XML(N, Indent, V), ?BR, Indent, ?XML_S(N, V), ?BR, Indent, ?XML_E(N)).
|
||||
-define(XML(N, Indent, D, V), ?XML(N, [Indent, lists:duplicate(D, <<" ">>)], V)).
|
||||
-define(XML_L(N, Indent, V), ?BR, Indent, ?XML_S(N, V), ?XML_E(N)).
|
||||
-define(XML_L(N, Indent, D, V), ?XML_L(N, [Indent, lists:duplicate(D, <<" ">>)], V)).
|
||||
|
||||
xml_gen({Name, integer}, Int, Indent) ->
|
||||
[?XML(member, Indent,
|
||||
[?XML_L(name, Indent, 1, ?ID_A(Name)),
|
||||
?XML(value, Indent, 1,
|
||||
[?XML_L(integer, Indent, 2, ?ID(jlib:integer_to_binary(Int)))])])];
|
||||
xml_gen({Name, string}, Str, Indent) ->
|
||||
[?XML(member, Indent,
|
||||
[?XML_L(name, Indent, 1, ?ID_A(Name)),
|
||||
?XML(value, Indent, 1,
|
||||
[?XML_L(string, Indent, 2, ?ID(Str))])])];
|
||||
xml_gen({Name, binary}, Str, Indent) ->
|
||||
[?XML(member, Indent,
|
||||
[?XML_L(name, Indent, 1, ?ID_A(Name)),
|
||||
?XML(value, Indent, 1,
|
||||
[?XML_L(string, Indent, 2, ?ID(Str))])])];
|
||||
xml_gen({Name, atom}, Atom, Indent) ->
|
||||
[?XML(member, Indent,
|
||||
[?XML_L(name, Indent, 1, ?ID_A(Name)),
|
||||
?XML(value, Indent, 1,
|
||||
[?XML_L(string, Indent, 2, ?ID(atom_to_list(Atom)))])])];
|
||||
xml_gen({Name, {tuple, Fields}}, Tuple, Indent) ->
|
||||
NewIndent = <<" ", Indent/binary>>,
|
||||
Res = lists:map(fun({A, B}) -> xml_gen(A, B, NewIndent) end, lists:zip(Fields, tuple_to_list(Tuple))),
|
||||
[?XML(member, Indent,
|
||||
[?XML_L(name, Indent, 1, ?ID_A(Name)),
|
||||
?XML(value, Indent, 1, [?XML(struct, NewIndent, Res)])])];
|
||||
xml_gen({Name, {list, ElDesc}}, List, Indent) ->
|
||||
Ind1 = <<" ", Indent/binary>>,
|
||||
Ind2 = <<" ", Ind1/binary>>,
|
||||
Res = lists:map(fun(E) -> [?XML(value, Ind1, [?XML(struct, Ind1, 1, xml_gen(ElDesc, E, Ind2))])] end, List),
|
||||
[?XML(member, Indent,
|
||||
[?XML_L(name, Indent, 1, ?ID_A(Name)),
|
||||
?XML(value, Indent, 1, [?XML(array, Indent, 2, [?XML(data, Indent, 3, Res)])])])].
|
||||
|
||||
xml_call(Name, ArgsDesc, Values) ->
|
||||
Ind = <<"">>,
|
||||
Res = lists:map(fun({A, B}) -> xml_gen(A, B, <<" ">>) end, lists:zip(ArgsDesc, Values)),
|
||||
[?XML(methodCall, Ind,
|
||||
[?XML_L(methodName, Ind, 1, ?ID_A(Name)),
|
||||
?XML(params, Ind, 1,
|
||||
[?XML(param, Ind, 2,
|
||||
[?XML(value, Ind, 3,
|
||||
[?XML(struct, Ind, 4, Res)])])])])].
|
||||
|
||||
generate_example_input({_Name, integer}, {LastStr, LastNum}) ->
|
||||
{LastNum+1, {LastStr, LastNum+1}};
|
||||
generate_example_input({_Name, string}, {LastStr, LastNum}) ->
|
||||
{string:chars(LastStr+1, 5), {LastStr+1, LastNum}};
|
||||
generate_example_input({_Name, binary}, {LastStr, LastNum}) ->
|
||||
{iolist_to_binary(string:chars(LastStr+1, 5)), {LastStr+1, LastNum}};
|
||||
generate_example_input({_Name, atom}, {LastStr, LastNum}) ->
|
||||
{list_to_atom(string:chars(LastStr+1, 5)), {LastStr+1, LastNum}};
|
||||
generate_example_input({_Name, {tuple, Fields}}, Data) ->
|
||||
{R, D} = lists:foldl(fun(Field, {Res2, Data2}) ->
|
||||
{Res3, Data3} = generate_example_input(Field, Data2),
|
||||
{[Res3 | Res2], Data3}
|
||||
end, {[], Data}, Fields),
|
||||
{list_to_tuple(lists:reverse(R)), D};
|
||||
generate_example_input({_Name, {list, Desc}}, Data) ->
|
||||
{R1, D1} = generate_example_input(Desc, Data),
|
||||
{R2, D2} = generate_example_input(Desc, D1),
|
||||
{[R1, R2], D2}.
|
||||
|
||||
|
||||
gen_calls(#ejabberd_commands{args_example=none, args=ArgsDesc} = C) ->
|
||||
{R, D} = lists:foldl(fun(Arg, {Res2, Data2}) ->
|
||||
{Res3, Data3} = generate_example_input(Arg, Data2),
|
||||
{[Res3 | Res2], Data3}
|
||||
end, {[], {$a-1, 0}}, ArgsDesc),
|
||||
gen_calls(C#ejabberd_commands{args_example=lists:reverse(R)});
|
||||
gen_calls(#ejabberd_commands{args_example=Values, args=ArgsDesc, name=Name}) ->
|
||||
Perl = perl_call(Name, ArgsDesc, Values),
|
||||
Java = java_call(Name, ArgsDesc, Values),
|
||||
XML = xml_call(Name, ArgsDesc, Values),
|
||||
[?TAG(ul, "code-samples-names",
|
||||
[?TAG(li, <<"Java">>),
|
||||
?TAG(li, <<"Perl">>),
|
||||
?TAG(li, <<"XML">>)]),
|
||||
?TAG(ul, "code-samples",
|
||||
[?TAG(li, ?TAG(pre, Java)),
|
||||
?TAG(li, ?TAG(pre, Perl)),
|
||||
?TAG(li, ?TAG(pre, XML))])].
|
||||
|
||||
gen_doc(#ejabberd_commands{name=Name, tags=Tags, desc=Desc, longdesc=LongDesc,
|
||||
args=Args, args_desc=ArgsDesc,
|
||||
result=Result, result_desc=ResultDesc}=Cmd) ->
|
||||
LDesc = case LongDesc of
|
||||
"" -> Desc;
|
||||
_ -> LongDesc
|
||||
end,
|
||||
ArgsText = case ArgsDesc of
|
||||
none ->
|
||||
[?TAG(ul, "args-list", lists:map(fun({AName, Type}) ->
|
||||
[?TAG(li, [?TAG_R(strong, atom_to_list(AName)), <<" :: ">>,
|
||||
?RAW(io_lib:format("~p", [Type]))])]
|
||||
end, Args))];
|
||||
_ ->
|
||||
[?TAG(dl, "args-list", lists:map(fun({{AName, Type}, ADesc}) ->
|
||||
[?TAG(dt, [?TAG_R(strong, atom_to_list(AName)), <<" :: ">>,
|
||||
?RAW(io_lib:format("~p", [Type]))]),
|
||||
?TAG(dd, ?RAW(ADesc))]
|
||||
end, lists:zip(Args, ArgsDesc)))]
|
||||
end,
|
||||
ResultText = case ResultDesc of
|
||||
none ->
|
||||
[?RAW(io_lib:format("~p", [Result]))];
|
||||
_ ->
|
||||
[?RAW(io_lib:format("~p", [Result])),
|
||||
?TAG_R(p, ResultDesc)]
|
||||
end,
|
||||
|
||||
[?TAG(h1, [?TAG(strong, atom_to_list(Name)), <<" - ">>, ?RAW(Desc)]),
|
||||
?TAG(p, ?RAW(LDesc)),
|
||||
?TAG(h2, <<"Arguments:">>),
|
||||
ArgsText,
|
||||
?TAG(h2, <<"Result:">>),
|
||||
ResultText,
|
||||
?TAG(h2, <<"Examples:">>),
|
||||
gen_calls(Cmd)].
|
||||
|
||||
generate_output(File, RegExp) ->
|
||||
Cmds = lists:map(fun({N, _, _}) ->
|
||||
ejabberd_commands:get_command_definition(N)
|
||||
end, ejabberd_commands:list_commands()),
|
||||
{ok, RE} = re:compile(RegExp),
|
||||
Cmds2 = lists:filter(fun(#ejabberd_commands{name=Name, module=Module}) ->
|
||||
re:run(atom_to_list(Name), RE, [{capture, none}]) == match orelse
|
||||
re:run(atom_to_list(Module), RE, [{capture, none}]) == match
|
||||
end, Cmds),
|
||||
Cmds3 = lists:sort(fun(#ejabberd_commands{name=N1}, #ejabberd_commands{name=N2}) ->
|
||||
N1 =< N2
|
||||
end, Cmds2),
|
||||
Out = lists:map(fun(C) -> gen_doc(C) end, Cmds3),
|
||||
{ok, Fh} = file:open(File, [write]),
|
||||
io:format(Fh, "~s", [[html_pre(), Out, html_post()]]),
|
||||
file:close(Fh),
|
||||
ok.
|
||||
|
||||
html_pre() ->
|
||||
"<!DOCTYPE>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv='content-type' content='text/html; charset=utf-8' />
|
||||
<style>
|
||||
body {
|
||||
margin: 0 auto;
|
||||
font-family: Georgia, Palatino, serif;
|
||||
color: #000;
|
||||
line-height: 1;
|
||||
max-width: 80%;
|
||||
padding: 10px;
|
||||
}
|
||||
h1, h2, h3, h4 {
|
||||
color: #111111;
|
||||
font-weight: 400;
|
||||
}
|
||||
h1, h2, h3, h4, h5, p {
|
||||
margin-bottom: 24px;
|
||||
padding: 0;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 80px;
|
||||
font-size: 36px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
margin: 24px 0 6px;
|
||||
}
|
||||
ul, ol {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
li {
|
||||
line-height: 24px;
|
||||
}
|
||||
li ul, li ul {
|
||||
margin-left: 24px;
|
||||
}
|
||||
p, ul, ol {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
max-width: 80%;
|
||||
}
|
||||
.id {color: #bbb}
|
||||
.lit {color: #aaa}
|
||||
.op {color: #9f9}
|
||||
.str {color: #f00}
|
||||
.num {color: white}
|
||||
.field {color: #faa}
|
||||
.kw {font-weight: bold; color: #ff6}
|
||||
.code-samples li {
|
||||
font-family: Consolas, Monaco, Andale Mono, monospace;
|
||||
line-height: 1.5;
|
||||
font-size: 13px;
|
||||
background: #333;
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.code-samples pre {
|
||||
margin: 0;
|
||||
padding: 0.5em 0.5em;
|
||||
}
|
||||
.code-samples {
|
||||
position: relative;
|
||||
}
|
||||
.code-samples-names li {
|
||||
display: block;
|
||||
}
|
||||
.code-samples-names li {
|
||||
color: white;
|
||||
background: #9c1;
|
||||
float: left;
|
||||
margin: 0 1px -4px 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
border: 4px solid #9c1;
|
||||
border-bottom: 0;
|
||||
border-radius: 9px 9px 0 0;
|
||||
padding: 0.2em 1em 4px 1em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.code-samples-names li.selected {
|
||||
background: #333;
|
||||
}
|
||||
.code-samples {
|
||||
clear: both;
|
||||
}
|
||||
.code-samples li {
|
||||
display: block;
|
||||
border: 4px solid #9c1;
|
||||
border-radius: 9px;
|
||||
border-top-left-radius: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.args-list li {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
function changeTab2(tab, addClickHandlers) {
|
||||
var els = tab.parentNode.childNodes;
|
||||
var els2 = tab.parentNode.nextSibling.childNodes;
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
if (addClickHandlers)
|
||||
els[i].addEventListener('click', changeTab, false);
|
||||
|
||||
if (els[i] == tab) {
|
||||
els[i].setAttribute('class', 'selected');
|
||||
els2[i].style.display = 'block';
|
||||
} else {
|
||||
els[i].removeAttribute('class');
|
||||
els2[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
function changeTab(event) {
|
||||
changeTab2(event.target);
|
||||
}
|
||||
</script>".
|
||||
|
||||
html_post() ->
|
||||
"<script>
|
||||
var ul = document.getElementsByTagName('ul');
|
||||
for (var i = 0; i < ul.length; i++) {
|
||||
if (ul[i].className == 'code-samples-names')
|
||||
changeTab2(ul[i].firstChild, true);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>".
|
||||
+199
-280
@@ -5,7 +5,7 @@
|
||||
%%% Created : 14 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -27,20 +27,32 @@
|
||||
-module(ejabberd_config).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0, load_file/1,
|
||||
-export([start/0, load_file/1, get_host_option/2,
|
||||
add_global_option/2, add_local_option/2,
|
||||
get_global_option/2, get_local_option/2,
|
||||
get_global_option/3, get_local_option/3]).
|
||||
mne_add_local_option/2, mne_del_local_option/1,
|
||||
del_global_option/1, del_local_option/1,
|
||||
get_global_option/1, get_local_option/1]).
|
||||
|
||||
-export([for_host/1
|
||||
,configure_host/2
|
||||
,delete_host/1
|
||||
]).
|
||||
|
||||
-export([search/1]).
|
||||
|
||||
-export([get_vh_by_auth_method/1]).
|
||||
-export([is_file_readable/1]).
|
||||
-export([get_version/0, get_myhosts/0, get_mylang/0]).
|
||||
-export([prepare_opt_val/4]).
|
||||
-export([convert_table_to_binary/5]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_config.hrl").
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
-record(state, {opts = [],
|
||||
hosts = [],
|
||||
override_local = false,
|
||||
override_global = false,
|
||||
override_acls = false}).
|
||||
|
||||
%% @type macro() = {macro_key(), macro_value()}
|
||||
|
||||
@@ -64,14 +76,6 @@ start() ->
|
||||
load_file(Config),
|
||||
%% This start time is used by mod_last:
|
||||
add_local_option(node_start, now()),
|
||||
SharedKey = case erlang:get_cookie() of
|
||||
nocookie ->
|
||||
sha:sha(randoms:get_string());
|
||||
Cookie ->
|
||||
sha:sha(jlib:atom_to_binary(Cookie))
|
||||
end,
|
||||
add_local_option(shared_key, SharedKey),
|
||||
add_local_option(flash_hack, ?FLASH_HACK),
|
||||
ok.
|
||||
|
||||
%% @doc Get the filename of the ejabberd configuration file.
|
||||
@@ -107,15 +111,12 @@ load_file(File) ->
|
||||
%% Returns a list of plain terms,
|
||||
%% in which the options 'include_config_file' were parsed
|
||||
%% and the terms in those files were included.
|
||||
%% @spec(iolist()) -> [term()]
|
||||
get_plain_terms_file(File) when is_binary(File) ->
|
||||
get_plain_terms_file(binary_to_list(File));
|
||||
%% @spec(string()) -> [term()]
|
||||
get_plain_terms_file(File1) ->
|
||||
File = get_absolute_path(File1),
|
||||
case file:consult(File) of
|
||||
{ok, Terms} ->
|
||||
BinTerms = strings_to_binary(Terms),
|
||||
include_config_files(BinTerms);
|
||||
include_config_files(Terms);
|
||||
{error, {LineNumber, erl_parse, _ParseMessage} = Reason} ->
|
||||
ExitText = describe_config_problem(File, Reason, LineNumber),
|
||||
?ERROR_MSG(ExitText, []),
|
||||
@@ -166,7 +167,16 @@ search_hosts(Term, State) ->
|
||||
end.
|
||||
|
||||
add_hosts_to_option(Hosts, State) ->
|
||||
PrepHosts = normalize_hosts(Hosts),
|
||||
PrepHosts1 = normalize_hosts(Hosts),
|
||||
PrepHosts = ensure_localhost_is_first(PrepHosts1),
|
||||
mnesia:transaction(
|
||||
fun() ->
|
||||
lists:foreach(
|
||||
fun(H) ->
|
||||
mnesia:write(#local_config{key = {H, host},
|
||||
value = []})
|
||||
end, PrepHosts)
|
||||
end),
|
||||
add_option(hosts, PrepHosts, State#state{hosts = PrepHosts}).
|
||||
|
||||
normalize_hosts(Hosts) ->
|
||||
@@ -174,32 +184,57 @@ normalize_hosts(Hosts) ->
|
||||
normalize_hosts([], PrepHosts) ->
|
||||
lists:reverse(PrepHosts);
|
||||
normalize_hosts([Host|Hosts], PrepHosts) ->
|
||||
case jlib:nodeprep(iolist_to_binary(Host)) of
|
||||
error ->
|
||||
try
|
||||
PrepHost = exmpp_stringprep:nodeprep(Host),
|
||||
normalize_hosts(Hosts, [PrepHost|PrepHosts])
|
||||
catch
|
||||
_ ->
|
||||
?ERROR_MSG("Can't load config file: "
|
||||
"invalid host name [~p]", [Host]),
|
||||
exit("invalid hostname");
|
||||
PrepHost ->
|
||||
normalize_hosts(Hosts, [PrepHost|PrepHosts])
|
||||
exit("invalid hostname")
|
||||
end.
|
||||
|
||||
%% @spec (Hosts::[string()]) -> [Localhost::string() | [string()]]
|
||||
%% @doc Return the list where the first is surely "localhost".
|
||||
ensure_localhost_is_first(Hosts) ->
|
||||
case lists:all(fun is_list/1, Hosts) of
|
||||
true ->
|
||||
ensure_localhost_is_first1(Hosts);
|
||||
false ->
|
||||
?ERROR_MSG("This list of hosts is bad formed:~n~p", [Hosts]),
|
||||
ensure_localhost_is_first1([])
|
||||
end.
|
||||
|
||||
ensure_localhost_is_first1(["localhost" | _] = Hosts) ->
|
||||
Hosts;
|
||||
ensure_localhost_is_first1(Hosts) ->
|
||||
case lists:member("localhost", Hosts) of
|
||||
true ->
|
||||
["localhost" | lists:delete("localhost", Hosts)];
|
||||
false ->
|
||||
?INFO_MSG("ejabberd added the default virtual host \"localhost\""
|
||||
" to the list of hosts.", []),
|
||||
["localhost" | Hosts]
|
||||
end.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% Errors reading the config file
|
||||
|
||||
describe_config_problem(Filename, Reason) ->
|
||||
ExitText = ["Problem loading ejabberd config file ", Filename,
|
||||
": ", file:format_error(Reason)],
|
||||
binary_to_list(iolist_to_binary(ExitText)).
|
||||
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) ->
|
||||
ExitText =
|
||||
["Problem loading ejabberd config file ", Filename,
|
||||
" approximately in the line ", file:format_error(Reason)],
|
||||
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]),
|
||||
binary_to_list(iolist_to_binary(ExitText)).
|
||||
ExitText.
|
||||
|
||||
get_config_lines(Filename, TargetNumber, PreContext, PostContext) ->
|
||||
{ok, Fd} = file:open(Filename, [read]),
|
||||
@@ -387,12 +422,11 @@ 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);
|
||||
{clusterid, ClusterID} ->
|
||||
add_option(clusterid, ClusterID, State);
|
||||
{listen, Listeners} ->
|
||||
Listeners2 =
|
||||
lists:map(
|
||||
@@ -409,6 +443,8 @@ process_term(Term, State) ->
|
||||
add_option(outgoing_s2s_port, Port, State);
|
||||
{outgoing_s2s_options, Methods, Timeout} ->
|
||||
add_option(outgoing_s2s_options, {Methods, Timeout}, State);
|
||||
{outgoing_s2s_local_address, Addr} ->
|
||||
add_option(outgoing_s2s_local_address, Addr, State);
|
||||
{s2s_dns_options, PropList} ->
|
||||
add_option(s2s_dns_options, PropList, State);
|
||||
{s2s_use_starttls, Port} ->
|
||||
@@ -443,30 +479,19 @@ process_term(Term, State) ->
|
||||
add_option(watchdog_large_heap, LH, State);
|
||||
{registration_timeout, Timeout} ->
|
||||
add_option(registration_timeout, Timeout, State);
|
||||
{ejabberdctl_access_commands, ACs} ->
|
||||
add_option(ejabberdctl_access_commands, ACs, State);
|
||||
{captcha_cmd, Cmd} ->
|
||||
add_option(captcha_cmd, Cmd, State);
|
||||
{captcha_host, Host} ->
|
||||
add_option(captcha_host, Host, State);
|
||||
{captcha_limit, Limit} ->
|
||||
add_option(captcha_limit, Limit, State);
|
||||
{ejabberdctl_access_commands, ACs} ->
|
||||
add_option(ejabberdctl_access_commands, ACs, State);
|
||||
{loglevel, Loglevel} ->
|
||||
ejabberd_loglevel:set(Loglevel),
|
||||
State;
|
||||
{max_fsm_queue, N} ->
|
||||
add_option(max_fsm_queue, N, State);
|
||||
{hostname, Host} ->
|
||||
add_option(hostname, Host, State);
|
||||
{rehash_timeout, Secs} ->
|
||||
add_option(rehash_timeout, Secs, State);
|
||||
{migrate_timeout, Secs} ->
|
||||
add_option(migrate_timeout, Secs, State);
|
||||
{riak_server, ServerPort} ->
|
||||
add_option(riak_server, ServerPort, State);
|
||||
{_Opt, _Val} ->
|
||||
lists:foldl(fun(Host, S) -> process_host_term(Term, Host, S) end,
|
||||
State, State#state.hosts)
|
||||
process_host_term(Term, global, State)
|
||||
end.
|
||||
|
||||
process_host_term(Term, Host, State) ->
|
||||
@@ -488,8 +513,15 @@ 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);
|
||||
{auth_method, Methods} ->
|
||||
{Methods2, StorageOption} = replace_storage_auth(Host, Methods),
|
||||
State2 = case StorageOption of
|
||||
{auth_storage, Storage} ->
|
||||
add_option({auth_storage, Host}, Storage, State);
|
||||
undefined ->
|
||||
State
|
||||
end,
|
||||
add_option({auth_method, Host}, Methods2, State2);
|
||||
{Opt, Val} ->
|
||||
add_option({Opt, Host}, Val, State)
|
||||
end.
|
||||
@@ -585,6 +617,7 @@ set_opts(State) ->
|
||||
exit("Error reading Mnesia database")
|
||||
end.
|
||||
|
||||
|
||||
add_global_option(Opt, Val) ->
|
||||
mnesia:transaction(fun() ->
|
||||
mnesia:write(#config{key = Opt,
|
||||
@@ -592,67 +625,65 @@ add_global_option(Opt, Val) ->
|
||||
end).
|
||||
|
||||
add_local_option(Opt, Val) ->
|
||||
mnesia:transaction(fun mne_add_local_option/2, [Opt, Val]).
|
||||
|
||||
mne_add_local_option(Opt, Val) ->
|
||||
mnesia:write(#local_config{key = Opt,
|
||||
value = Val}).
|
||||
|
||||
del_global_option(Opt) ->
|
||||
mnesia:transaction(fun() ->
|
||||
mnesia:write(#local_config{key = Opt,
|
||||
value = Val})
|
||||
mnesia:delete({config, Opt})
|
||||
end).
|
||||
|
||||
-spec prepare_opt_val(any(), any(), check_fun(), any()) -> any().
|
||||
del_local_option(Opt) ->
|
||||
mnesia:transaction(fun() ->
|
||||
mnesia:delete({local_config, Opt})
|
||||
end).
|
||||
|
||||
prepare_opt_val(Opt, Val, F, Default) ->
|
||||
Res = case F of
|
||||
{Mod, Fun} ->
|
||||
catch Mod:Fun(Val);
|
||||
_ ->
|
||||
catch F(Val)
|
||||
end,
|
||||
case Res of
|
||||
{'EXIT', _} ->
|
||||
?INFO_MSG("Configuration problem:~n"
|
||||
"** Option: ~s~n"
|
||||
"** Invalid value: ~s~n"
|
||||
"** Using as fallback: ~s",
|
||||
[format_term(Opt),
|
||||
format_term(Val),
|
||||
format_term(Default)]),
|
||||
Default;
|
||||
_ ->
|
||||
Res
|
||||
end.
|
||||
|
||||
-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
|
||||
|
||||
-spec get_global_option(any(), check_fun()) -> any().
|
||||
|
||||
get_global_option(Opt, F) ->
|
||||
get_global_option(Opt, F, undefined).
|
||||
|
||||
-spec get_global_option(any(), check_fun(), any()) -> any().
|
||||
|
||||
get_global_option(Opt, F, Default) ->
|
||||
get_global_option({Opt1, Host} = Opt) when is_list(Host) ->
|
||||
case ets:lookup(config, Opt) of
|
||||
[#config{value = Val}] ->
|
||||
prepare_opt_val(Opt, Val, F, Default);
|
||||
Val;
|
||||
_ ->
|
||||
Default
|
||||
get_global_option({Opt1, global})
|
||||
end;
|
||||
get_global_option(Opt) ->
|
||||
case ets:lookup(config, Opt) of
|
||||
[#config{value = Val}] when Opt == hosts ->
|
||||
ensure_localhost_is_first(Val);
|
||||
[#config{value = Val}] ->
|
||||
Val;
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
-spec get_local_option(any(), check_fun()) -> any().
|
||||
|
||||
get_local_option(Opt, F) ->
|
||||
get_local_option(Opt, F, undefined).
|
||||
|
||||
-spec get_local_option(any(), check_fun(), any()) -> any().
|
||||
|
||||
get_local_option(Opt, F, Default) ->
|
||||
get_local_option({Opt1, Host} = Opt) when is_list(Host) ->
|
||||
case ets:lookup(local_config, Opt) of
|
||||
[#local_config{value = Val}] ->
|
||||
prepare_opt_val(Opt, Val, F, Default);
|
||||
Val;
|
||||
_ ->
|
||||
Default
|
||||
get_local_option({Opt1, global})
|
||||
end;
|
||||
get_local_option(Opt) ->
|
||||
case ets:lookup(local_config, Opt) of
|
||||
[#local_config{value = Val}] ->
|
||||
Val;
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
-spec get_vh_by_auth_method(atom()) -> [binary()].
|
||||
get_host_option(Host, Option) ->
|
||||
case ets:lookup(local_config, {Option, Host}) of
|
||||
[#local_config{value=V}] -> V;
|
||||
_ -> undefined
|
||||
end.
|
||||
|
||||
mne_del_local_option({_OptName, Host} = Opt) when is_list(Host) ->
|
||||
mnesia:delete({local_config, Opt});
|
||||
mne_del_local_option({Host, host} = Opt) when is_list(Host) ->
|
||||
mnesia:delete({local_config, Opt}).
|
||||
|
||||
%% Return the list of hosts handled by a given module
|
||||
get_vh_by_auth_method(AuthMethod) ->
|
||||
@@ -673,197 +704,85 @@ is_file_readable(Path) ->
|
||||
false
|
||||
end.
|
||||
|
||||
get_version() ->
|
||||
list_to_binary(element(2, application:get_key(ejabberd, vsn))).
|
||||
search(Pattern) ->
|
||||
{atomic, Res} = mnesia:transaction(fun mnesia:select/2, [local_config, Pattern]),
|
||||
Res.
|
||||
|
||||
-spec get_myhosts() -> [binary()].
|
||||
for_host(Host) ->
|
||||
mnesia:read({local_config, {Host, host}})
|
||||
++ mnesia:select(local_config,
|
||||
ets:fun2ms(fun (#local_config{key={_, H}})
|
||||
when H =:= Host ->
|
||||
object()
|
||||
end))
|
||||
++ acl:for_host(Host).
|
||||
|
||||
get_myhosts() ->
|
||||
ejabberd_config:get_global_option(hosts, fun(V) -> V end).
|
||||
delete_host(Host) ->
|
||||
mnesia_delete_objects(for_host(Host)),
|
||||
ok.
|
||||
|
||||
-spec get_mylang() -> binary().
|
||||
configure_host(Host, Config) ->
|
||||
HostExistenceTerm = {{Host, host}, []},
|
||||
Records = host_terms_to_records(Host, [HostExistenceTerm | Config]),
|
||||
mnesia_write_objects(Records),
|
||||
ok.
|
||||
|
||||
get_mylang() ->
|
||||
ejabberd_config:get_global_option(
|
||||
language,
|
||||
fun iolist_to_binary/1,
|
||||
<<"en">>).
|
||||
host_terms_to_records(Host, Terms) ->
|
||||
lists:foldl(fun (Term, Acc) ->
|
||||
host_term_to_record(Term, Host, Acc)
|
||||
end, [], Terms).
|
||||
|
||||
replace_module(mod_announce_odbc) -> {mod_announce, odbc};
|
||||
replace_module(mod_blocking_odbc) -> {mod_blocking, odbc};
|
||||
replace_module(mod_caps_odbc) -> {mod_caps, odbc};
|
||||
replace_module(mod_irc_odbc) -> {mod_irc, odbc};
|
||||
replace_module(mod_last_odbc) -> {mod_last, odbc};
|
||||
replace_module(mod_muc_odbc) -> {mod_muc, odbc};
|
||||
replace_module(mod_offline_odbc) -> {mod_offline, odbc};
|
||||
replace_module(mod_privacy_odbc) -> {mod_privacy, odbc};
|
||||
replace_module(mod_private_odbc) -> {mod_private, odbc};
|
||||
replace_module(mod_roster_odbc) -> {mod_roster, odbc};
|
||||
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, odbc};
|
||||
replace_module(mod_vcard_odbc) -> {mod_vcard, odbc};
|
||||
replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, odbc};
|
||||
replace_module(Module) -> Module.
|
||||
host_term_to_record({acl, ACLName, ACLData}, Host, Acc) ->
|
||||
[acl:to_record(Host, ACLName, ACLData) | Acc];
|
||||
host_term_to_record({access, RuleName, Rules}, Host, Acc) ->
|
||||
[#config{key={access, RuleName, Host}, value=Rules} | Acc];
|
||||
host_term_to_record({shaper, Name, Data}, Host, Acc) ->
|
||||
[#config{key={shaper, Name, Host}, value=Data} | Acc];
|
||||
host_term_to_record({host, _}, _Host, Acc) -> Acc;
|
||||
host_term_to_record({hosts, _}, _Host, Acc) -> Acc;
|
||||
host_term_to_record({{Host, host}, []}, Host, Acc) ->
|
||||
[#local_config{key={Host, host}, value=[]} | Acc];
|
||||
host_term_to_record({Opt, Val}, Host, Acc) when is_atom(Opt) ->
|
||||
[#local_config{key={Opt, Host}, value=Val} | Acc].
|
||||
|
||||
replace_modules(Modules) ->
|
||||
lists:map(
|
||||
fun({Module, Opts}) ->
|
||||
case replace_module(Module) of
|
||||
{NewModule, DBType} ->
|
||||
emit_deprecation_warning(Module, NewModule, DBType),
|
||||
NewOpts = [{db_type, DBType} |
|
||||
lists:keydelete(db_type, 1, Opts)],
|
||||
{NewModule, NewOpts};
|
||||
NewModule ->
|
||||
if Module /= NewModule ->
|
||||
emit_deprecation_warning(Module, NewModule);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{NewModule, Opts}
|
||||
end
|
||||
end, Modules).
|
||||
|
||||
mnesia_delete_objects(List) when is_list(List) ->
|
||||
true = lists:all(fun (I) ->
|
||||
ok =:= mnesia:delete_object(I)
|
||||
end, List).
|
||||
mnesia_write_objects(List) when is_list(List) ->
|
||||
true = lists:all(fun (I) ->
|
||||
ok =:= mnesia:write(I)
|
||||
end, List).
|
||||
|
||||
strings_to_binary([]) ->
|
||||
[];
|
||||
strings_to_binary(L) when is_list(L) ->
|
||||
case is_string(L) of
|
||||
true ->
|
||||
list_to_binary(L);
|
||||
false ->
|
||||
strings_to_binary1(L)
|
||||
end;
|
||||
strings_to_binary(T) when is_tuple(T) ->
|
||||
list_to_tuple(strings_to_binary(tuple_to_list(T)));
|
||||
strings_to_binary(X) ->
|
||||
X.
|
||||
%% Replace internal and odbc auth_methods with storage.
|
||||
%% Only one storage type can be used, either internal or odbc.
|
||||
replace_storage_auth(Host, Val) when not is_list(Val) ->
|
||||
replace_storage_auth(Host, [Val]);
|
||||
replace_storage_auth(Host, Val) ->
|
||||
replace_storage_auth(Host, Val, [], undefined).
|
||||
|
||||
strings_to_binary1([El|L]) ->
|
||||
[strings_to_binary(El)|strings_to_binary1(L)];
|
||||
strings_to_binary1([]) ->
|
||||
[];
|
||||
strings_to_binary1(T) ->
|
||||
T.
|
||||
replace_storage_auth(_Host, [], Val2, Storage) ->
|
||||
{lists:reverse(Val2), Storage};
|
||||
|
||||
is_string([C|T]) when (C >= 0) and (C =< 255) ->
|
||||
is_string(T);
|
||||
is_string([]) ->
|
||||
true;
|
||||
is_string(_) ->
|
||||
false.
|
||||
replace_storage_auth(Host, [internal = Val | ValT], Val2, undefined) ->
|
||||
Storage = {auth_storage, mnesia},
|
||||
?WARNING_MSG("The auth method '~p' is deprecated.~nReplace it with 'storage'"
|
||||
" and also add this option: ~n~p.", [Val, Storage]),
|
||||
replace_storage_auth(Host, ValT, [storage | Val2], Storage);
|
||||
|
||||
binary_to_strings(B) when is_binary(B) ->
|
||||
binary_to_list(B);
|
||||
binary_to_strings([H|T]) ->
|
||||
[binary_to_strings(H)|binary_to_strings(T)];
|
||||
binary_to_strings(T) when is_tuple(T) ->
|
||||
list_to_tuple(binary_to_strings(tuple_to_list(T)));
|
||||
binary_to_strings(T) ->
|
||||
T.
|
||||
replace_storage_auth(Host, [odbc = Val | ValT], Val2, undefined) ->
|
||||
Storage = {auth_storage, odbc},
|
||||
?WARNING_MSG("The auth method '~p' is deprecated.~nReplace it with 'storage'"
|
||||
" and also add this option: ~n~p.", [Val, Storage]),
|
||||
replace_storage_auth(Host, ValT, [storage | Val2], Storage);
|
||||
|
||||
format_term(Bin) when is_binary(Bin) ->
|
||||
io_lib:format("\"~s\"", [Bin]);
|
||||
format_term(S) when is_list(S), S /= [] ->
|
||||
case lists:all(fun(C) -> (C>=0) and (C=<255) end, S) of
|
||||
true ->
|
||||
io_lib:format("\"~s\"", [S]);
|
||||
false ->
|
||||
io_lib:format("~p", [binary_to_strings(S)])
|
||||
end;
|
||||
format_term(T) ->
|
||||
io_lib:format("~p", [binary_to_strings(T)]).
|
||||
replace_storage_auth(Host, [Val | ValT], Val2, Storage)
|
||||
when (Val /= internal) and (Val /= odbc) ->
|
||||
replace_storage_auth(Host, ValT, [Val | Val2], Storage);
|
||||
|
||||
-spec convert_table_to_binary(atom(), [atom()], atom(),
|
||||
fun(), fun()) -> ok.
|
||||
|
||||
convert_table_to_binary(Tab, Fields, Type, DetectFun, ConvertFun) ->
|
||||
case is_table_still_list(Tab, DetectFun) of
|
||||
true ->
|
||||
?INFO_MSG("Converting '~s' table from strings to binaries.", [Tab]),
|
||||
TmpTab = list_to_atom(atom_to_list(Tab) ++ "_tmp_table"),
|
||||
catch mnesia:delete_table(TmpTab),
|
||||
case mnesia:create_table(TmpTab,
|
||||
[{disc_only_copies, [node()]},
|
||||
{type, Type},
|
||||
{local_content, true},
|
||||
{record_name, Tab},
|
||||
{attributes, Fields}]) of
|
||||
{atomic, ok} ->
|
||||
mnesia:transform_table(Tab, ignore, Fields),
|
||||
case mnesia:transaction(
|
||||
fun() ->
|
||||
mnesia:write_lock_table(TmpTab),
|
||||
mnesia:foldl(
|
||||
fun(R, _) ->
|
||||
NewR = ConvertFun(R),
|
||||
mnesia:dirty_write(TmpTab, NewR)
|
||||
end, ok, Tab)
|
||||
end) of
|
||||
{atomic, ok} ->
|
||||
mnesia:clear_table(Tab),
|
||||
case mnesia:transaction(
|
||||
fun() ->
|
||||
mnesia:write_lock_table(Tab),
|
||||
mnesia:foldl(
|
||||
fun(R, _) ->
|
||||
mnesia:dirty_write(R)
|
||||
end, ok, TmpTab)
|
||||
end) of
|
||||
{atomic, ok} ->
|
||||
mnesia:delete_table(TmpTab);
|
||||
Err ->
|
||||
report_and_stop(Tab, Err)
|
||||
end;
|
||||
Err ->
|
||||
report_and_stop(Tab, Err)
|
||||
end;
|
||||
Err ->
|
||||
report_and_stop(Tab, Err)
|
||||
end;
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
is_table_still_list(Tab, DetectFun) ->
|
||||
is_table_still_list(Tab, DetectFun, mnesia:dirty_first(Tab)).
|
||||
|
||||
is_table_still_list(_Tab, _DetectFun, '$end_of_table') ->
|
||||
false;
|
||||
is_table_still_list(Tab, DetectFun, Key) ->
|
||||
Rs = mnesia:dirty_read(Tab, Key),
|
||||
Res = lists:foldl(fun(_, true) ->
|
||||
true;
|
||||
(_, false) ->
|
||||
false;
|
||||
(R, _) ->
|
||||
case DetectFun(R) of
|
||||
'$next' ->
|
||||
'$next';
|
||||
El ->
|
||||
is_list(El)
|
||||
end
|
||||
end, '$next', Rs),
|
||||
case Res of
|
||||
true ->
|
||||
true;
|
||||
false ->
|
||||
false;
|
||||
'$next' ->
|
||||
is_table_still_list(Tab, DetectFun, mnesia:dirty_next(Tab, Key))
|
||||
end.
|
||||
|
||||
report_and_stop(Tab, Err) ->
|
||||
ErrTxt = lists:flatten(
|
||||
io_lib:format(
|
||||
"Failed to convert '~s' table to binary: ~p",
|
||||
[Tab, Err])),
|
||||
?CRITICAL_MSG(ErrTxt, []),
|
||||
timer:sleep(1000),
|
||||
halt(string:substr(ErrTxt, 1, 199)).
|
||||
|
||||
emit_deprecation_warning(Module, NewModule, DBType) ->
|
||||
?WARNING_MSG("Module ~s is deprecated, use {~s, [{db_type, ~s}, ...]}"
|
||||
" instead", [Module, NewModule, DBType]).
|
||||
|
||||
emit_deprecation_warning(Module, NewModule) ->
|
||||
?WARNING_MSG("Module ~s is deprecated, use ~s instead",
|
||||
[Module, NewModule]).
|
||||
replace_storage_auth(Host, [Val | _ValT], _Val2, Storage) ->
|
||||
?CRITICAL_MSG("The auth method '~p' conflicts with~n~p in the host \"~p\"."
|
||||
"~nOnly one of them can be used in each host.",
|
||||
[Val, Storage, Host]),
|
||||
throw({unacceptable_auth_conflict, Host, Val, Storage}).
|
||||
|
||||
+3
-14
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -19,16 +19,5 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(config, {key :: any(), value :: any()}).
|
||||
|
||||
-record(local_config, {key :: any(), value :: any()}).
|
||||
|
||||
-type config() :: #config{}.
|
||||
-type local_config() :: #local_config{}.
|
||||
|
||||
-record(state,
|
||||
{opts = [] :: [acl:acl() | config() | local_config()],
|
||||
hosts = [] :: [binary()],
|
||||
override_local = false :: boolean(),
|
||||
override_global = false :: boolean(),
|
||||
override_acls = false :: boolean()}).
|
||||
-record(config, {key, value}).
|
||||
-record(local_config, {key, value}).
|
||||
|
||||
+71
-83
@@ -5,7 +5,7 @@
|
||||
%%% Created : 11 Jan 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -72,10 +72,10 @@ start() ->
|
||||
_ ->
|
||||
case net_kernel:longnames() of
|
||||
true ->
|
||||
lists:flatten([SNode, "@", inet_db:gethostname(),
|
||||
".", inet_db:res_option(domain)]);
|
||||
SNode ++ "@" ++ inet_db:gethostname() ++
|
||||
"." ++ inet_db:res_option(domain);
|
||||
false ->
|
||||
lists:flatten([SNode, "@", inet_db:gethostname()]);
|
||||
SNode ++ "@" ++ inet_db:gethostname();
|
||||
_ ->
|
||||
SNode
|
||||
end
|
||||
@@ -124,8 +124,6 @@ unregister_commands(CmdDescs, Module, Function) ->
|
||||
%% Process
|
||||
%%-----------------------------
|
||||
|
||||
-spec process([string()]) -> non_neg_integer().
|
||||
|
||||
%% The commands status, stop and restart are defined here to ensure
|
||||
%% they are usable even if ejabberd is completely stopped.
|
||||
process(["status"]) ->
|
||||
@@ -161,7 +159,7 @@ process(["mnesia", "info"]) ->
|
||||
mnesia:info(),
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["mnesia", Arg]) ->
|
||||
process(["mnesia", Arg]) when is_list(Arg) ->
|
||||
case catch mnesia:system_info(list_to_atom(Arg)) of
|
||||
{'EXIT', Error} -> ?PRINT("Error: ~p~n", [Error]);
|
||||
Return -> ?PRINT("~p~n", [Return])
|
||||
@@ -192,9 +190,8 @@ process(["help" | Mode]) ->
|
||||
print_usage_help(MaxC, ShCode),
|
||||
?STATUS_SUCCESS;
|
||||
[CmdString | _] ->
|
||||
CmdStringU = ejabberd_regexp:greplace(
|
||||
list_to_binary(CmdString), <<"-">>, <<"_">>),
|
||||
print_usage_commands(binary_to_list(CmdStringU), MaxC, ShCode),
|
||||
CmdStringU = re:replace(CmdString, "-", "_", [global, {return, list}]),
|
||||
print_usage_commands(CmdStringU, MaxC, ShCode),
|
||||
?STATUS_SUCCESS
|
||||
end;
|
||||
|
||||
@@ -217,27 +214,30 @@ process2(Args, AccessCommands) ->
|
||||
process2(Args, Auth, AccessCommands) ->
|
||||
case try_run_ctp(Args, Auth, AccessCommands) of
|
||||
{String, wrong_command_arguments}
|
||||
when is_list(String) ->
|
||||
when is_list(String) ->
|
||||
io:format(lists:flatten(["\n" | String]++["\n"])),
|
||||
[CommandString | _] = Args,
|
||||
process(["help" | [CommandString]]),
|
||||
{lists:flatten(String), ?STATUS_ERROR};
|
||||
{String, Code}
|
||||
when is_list(String) and is_integer(Code) ->
|
||||
when is_list(String) and is_integer(Code) ->
|
||||
{lists:flatten(String), Code};
|
||||
String
|
||||
when is_list(String) ->
|
||||
when is_list(String) ->
|
||||
{lists:flatten(String), ?STATUS_SUCCESS};
|
||||
Code
|
||||
when is_integer(Code) ->
|
||||
when is_integer(Code) ->
|
||||
{"", Code};
|
||||
Other ->
|
||||
{"Erroneous result: " ++ io_lib:format("~p", [Other]), ?STATUS_ERROR}
|
||||
end.
|
||||
|
||||
get_accesscommands() ->
|
||||
ejabberd_config:get_local_option(ejabberdctl_access_commands,
|
||||
fun(V) when is_list(V) -> V end, []).
|
||||
case ejabberd_config:get_local_option(ejabberdctl_access_commands) of
|
||||
ACs when is_list(ACs) -> ACs;
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
|
||||
%%-----------------------------
|
||||
%% Command calling
|
||||
@@ -267,10 +267,8 @@ try_run_ctp(Args, Auth, AccessCommands) ->
|
||||
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()}
|
||||
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_command_arguments} ->
|
||||
{"Error: wrong arguments", ?STATUS_ERROR};
|
||||
{error, String} ->
|
||||
{String, ?STATUS_ERROR};
|
||||
Res ->
|
||||
Res
|
||||
catch
|
||||
@@ -279,29 +277,27 @@ try_call_command(Args, Auth, AccessCommands) ->
|
||||
{io_lib:format("Problem '~p ~p' occurred executing the command.~nStacktrace: ~p", [A, Why, Stack]), ?STATUS_ERROR}
|
||||
end.
|
||||
|
||||
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType}
|
||||
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, string()}
|
||||
call_command([CmdString | Args], Auth, AccessCommands) ->
|
||||
CmdStringU = ejabberd_regexp:greplace(
|
||||
list_to_binary(CmdString), <<"-">>, <<"_">>),
|
||||
Command = list_to_atom(binary_to_list(CmdStringU)),
|
||||
CmdStringU = re:replace(CmdString, "-", "_", [global,{return,list}]),
|
||||
Command = list_to_atom(CmdStringU),
|
||||
case ejabberd_commands:get_command_format(Command) of
|
||||
{error, command_unknown} ->
|
||||
{error, command_unknown};
|
||||
{error, io_lib:format("Error: command ~p not known.", [CmdString])};
|
||||
{ArgsFormat, ResultFormat} ->
|
||||
case (catch format_args(Args, ArgsFormat)) of
|
||||
ArgsFormatted when is_list(ArgsFormatted) ->
|
||||
Result = ejabberd_commands:execute_command(AccessCommands, Auth, Command,
|
||||
ArgsFormatted),
|
||||
format_result(Result, ResultFormat);
|
||||
{'EXIT', {function_clause,[{lists,zip,[A1, A2], _} | _]}} ->
|
||||
{'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}
|
||||
{error, io_lib:format("Error: the command ~p requires ~p ~s.",
|
||||
[CmdString, NumCompa, TextCompa])}
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -321,12 +317,10 @@ format_args(Args, ArgsFormat) ->
|
||||
|
||||
format_arg(Arg, integer) ->
|
||||
format_arg2(Arg, "~d");
|
||||
format_arg(Arg, binary) ->
|
||||
list_to_binary(format_arg(Arg, string));
|
||||
format_arg("", string) ->
|
||||
"";
|
||||
format_arg(Arg, string) ->
|
||||
NumChars = integer_to_list(length(Arg)),
|
||||
NumChars = integer_to_list(string:len(Arg)),
|
||||
Parse = "~" ++ NumChars ++ "c",
|
||||
format_arg2(Arg, Parse).
|
||||
|
||||
@@ -347,12 +341,9 @@ format_result(Atom, {_Name, atom}) ->
|
||||
format_result(Int, {_Name, integer}) ->
|
||||
io_lib:format("~p", [Int]);
|
||||
|
||||
format_result(String, {_Name, string}) when is_list(String) ->
|
||||
format_result(String, {_Name, string}) ->
|
||||
io_lib:format("~s", [String]);
|
||||
|
||||
format_result(Binary, {_Name, string}) when is_binary(Binary) ->
|
||||
io_lib:format("~s", [binary_to_list(Binary)]);
|
||||
|
||||
format_result(Code, {_Name, rescode}) ->
|
||||
make_status(Code);
|
||||
|
||||
@@ -447,8 +438,7 @@ print_usage() ->
|
||||
print_usage(dual, MaxC, ShCode).
|
||||
print_usage(HelpMode, MaxC, ShCode) ->
|
||||
AllCommands =
|
||||
[
|
||||
{"status", [], "Get ejabberd status"},
|
||||
[{"status", [], "Get ejabberd status"},
|
||||
{"stop", [], "Stop ejabberd"},
|
||||
{"restart", [], "Restart ejabberd"},
|
||||
{"help", ["[--tags [tag] | com?*]"], "Show help (try: ejabberdctl help help)"},
|
||||
@@ -535,25 +525,24 @@ split_desc_segments(MaxL, Words) ->
|
||||
join(L, Words) ->
|
||||
join(L, Words, 0, [], []).
|
||||
|
||||
join(_Len, [], _CurSegLen, CurSeg, AllSegs) ->
|
||||
lists:reverse([CurSeg | AllSegs]);
|
||||
join(Len, [Word | Tail], CurSegLen, CurSeg, AllSegs) ->
|
||||
WordLen = length(Word),
|
||||
SegSize = WordLen + CurSegLen + 1,
|
||||
{NewCurSeg, NewAllSegs, NewCurSegLen} =
|
||||
if SegSize < Len ->
|
||||
{[CurSeg, " ", Word], AllSegs, SegSize};
|
||||
true ->
|
||||
{Word, [CurSeg | AllSegs], WordLen}
|
||||
end,
|
||||
NewLen = case string:str(Word, "\n") of
|
||||
0 ->
|
||||
NewCurSegLen;
|
||||
_ ->
|
||||
0
|
||||
end,
|
||||
join(Len, Tail, NewLen, NewCurSeg, NewAllSegs).
|
||||
|
||||
join(_L, [], _LenLastSeg, LastSeg, ResSeg) ->
|
||||
ResSeg2 = [lists:reverse(LastSeg) | ResSeg],
|
||||
lists:reverse(ResSeg2);
|
||||
join(L, [Word | Words], LenLastSeg, LastSeg, ResSeg) ->
|
||||
LWord = length(Word),
|
||||
case LWord + LenLastSeg < L of
|
||||
true ->
|
||||
%% This word fits in the last segment
|
||||
%% If this word ends with "\n", reset column counter
|
||||
case string:str(Word, "\n") of
|
||||
0 ->
|
||||
join(L, Words, LenLastSeg+LWord+1, [" ", Word | LastSeg], ResSeg);
|
||||
_ ->
|
||||
join(L, Words, LWord+1, [" ", Word | LastSeg], ResSeg)
|
||||
end;
|
||||
false ->
|
||||
join(L, Words, LWord, [" ", Word], [lists:reverse(LastSeg) | ResSeg])
|
||||
end.
|
||||
|
||||
format_command_lines(CALD, MaxCmdLen, MaxC, ShCode, dual)
|
||||
when MaxC - MaxCmdLen < 40 ->
|
||||
@@ -564,8 +553,7 @@ format_command_lines(CALD, MaxCmdLen, MaxC, ShCode, dual) ->
|
||||
lists:map(
|
||||
fun({Cmd, Args, CmdArgsL, Desc}) ->
|
||||
DescFmt = prepare_description(MaxCmdLen+4, MaxC, Desc),
|
||||
[" ", ?B(Cmd), " ", [[?U(Arg), " "] || Arg <- Args],
|
||||
string:chars($\s, MaxCmdLen - CmdArgsL + 1),
|
||||
[" ", ?B(Cmd), " ", [[?U(Arg), " "] || Arg <- Args], string:chars($\s, MaxCmdLen - CmdArgsL + 1),
|
||||
DescFmt, "\n"]
|
||||
end, CALD);
|
||||
|
||||
@@ -605,8 +593,7 @@ print_usage_tags(Tag, MaxC, ShCode) ->
|
||||
end,
|
||||
CommandsList = lists:map(
|
||||
fun(NameString) ->
|
||||
C = ejabberd_commands:get_command_definition(
|
||||
list_to_atom(NameString)),
|
||||
C = ejabberd_commands:get_command_definition(list_to_atom(NameString)),
|
||||
#ejabberd_commands{name = Name,
|
||||
args = Args,
|
||||
desc = Desc} = C,
|
||||
@@ -623,30 +610,30 @@ print_usage_tags(Tag, MaxC, ShCode) ->
|
||||
|
||||
print_usage_help(MaxC, ShCode) ->
|
||||
LongDesc =
|
||||
["The special 'help' ejabberdctl command provides help of ejabberd commands.\n\n"
|
||||
"The format is:\n ", ?B("ejabberdctl"), " ", ?B("help"), " [", ?B("--tags"), " ", ?U("[tag]"), " | ", ?U("com?*"), "]\n\n"
|
||||
"The special 'help' ejabberdctl command provides help of ejabberd commands.\n\n"
|
||||
"The format is:\n "++?B("ejabberdctl")++" "++?B("help")++" ["++?B("--tags")++" "++?U("[tag]")++" | "++?U("com?*")++"]\n\n"
|
||||
"The optional arguments:\n"
|
||||
" ",?B("--tags")," Show all tags and the names of commands in each tag\n"
|
||||
" ",?B("--tags"), " ", ?U("tag")," Show description of commands in this tag\n"
|
||||
" ",?U("command")," Show detailed description of the command\n"
|
||||
" ",?U("com?*")," Show detailed description of commands that match this glob.\n"
|
||||
" "++?B("--tags")++" Show all tags and the names of commands in each tag\n"
|
||||
" "++?B("--tags")++" "++?U("tag")++" Show description of commands in this tag\n"
|
||||
" "++?U("command")++" Show detailed description of the command\n"
|
||||
" "++?U("com?*")++" Show detailed description of commands that match this glob.\n"
|
||||
" You can use ? to match a simple character,\n"
|
||||
" and * to match several characters.\n"
|
||||
"\n",
|
||||
"Some example usages:\n",
|
||||
" ejabberdctl help\n",
|
||||
" ejabberdctl help --tags\n",
|
||||
" ejabberdctl help --tags accounts\n",
|
||||
" ejabberdctl help register\n",
|
||||
" ejabberdctl help regist*\n",
|
||||
"\n",
|
||||
"Please note that 'ejabberdctl help' shows all ejabberd commands,\n",
|
||||
"even those that cannot be used in the shell with ejabberdctl.\n",
|
||||
"Those commands can be identified because the description starts with: *"],
|
||||
"\n"
|
||||
"Some example usages:\n"
|
||||
" ejabberdctl help\n"
|
||||
" ejabberdctl help --tags\n"
|
||||
" ejabberdctl help --tags accounts\n"
|
||||
" ejabberdctl help register\n"
|
||||
" ejabberdctl help regist*\n"
|
||||
"\n"
|
||||
"Please note that 'ejabberdctl help' shows all ejabberd commands,\n"
|
||||
"even those that cannot be used in the shell with ejabberdctl.\n"
|
||||
"Those commands can be identified because the description starts with: *",
|
||||
ArgsDef = [],
|
||||
C = #ejabberd_commands{
|
||||
desc = "Show help of ejabberd commands",
|
||||
longdesc = lists:flatten(LongDesc),
|
||||
longdesc = LongDesc,
|
||||
args = ArgsDef,
|
||||
result = {help, string}},
|
||||
print_usage_command("help", C, MaxC, ShCode).
|
||||
@@ -687,10 +674,10 @@ filter_commands(All, SubString) ->
|
||||
end.
|
||||
|
||||
filter_commands_regexp(All, Glob) ->
|
||||
RegExp = ejabberd_regexp:sh_to_awk(list_to_binary(Glob)),
|
||||
RegExp = xmerl_regexp:sh_to_awk(Glob),
|
||||
lists:filter(
|
||||
fun(Command) ->
|
||||
case ejabberd_regexp:run(list_to_binary(Command), RegExp) of
|
||||
case re:run(Command, RegExp, [{capture, none}]) of
|
||||
match ->
|
||||
true;
|
||||
nomatch ->
|
||||
@@ -709,6 +696,7 @@ print_usage_command(Cmd, MaxC, ShCode) ->
|
||||
print_usage_command(Cmd, C, MaxC, ShCode)
|
||||
end.
|
||||
|
||||
%% @spec (Cmd::string(), C::ejabberd_commands(), MaxC::integer(), ShCode::boolean()) -> ok
|
||||
print_usage_command(Cmd, C, MaxC, ShCode) ->
|
||||
#ejabberd_commands{
|
||||
tags = TagsAtoms,
|
||||
@@ -751,11 +739,11 @@ 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==binary) or (Type==rescode) or (Type==restuple)->
|
||||
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==binary) or (Type==rescode) or (Type==restuple)->
|
||||
when (Type==atom) or (Type==integer) or (Type==string) or (Type==rescode) or (Type==restuple)->
|
||||
io_lib:format("~p::~p", [Name, Type]);
|
||||
|
||||
format_usage_ctype({Name, {list, ElementDef}}, Indentation) ->
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -20,9 +20,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-define(STATUS_SUCCESS, 0).
|
||||
|
||||
-define(STATUS_ERROR, 1).
|
||||
|
||||
-define(STATUS_USAGE, 2).
|
||||
|
||||
-define(STATUS_BADRPC, 3).
|
||||
-define(STATUS_ERROR, 1).
|
||||
-define(STATUS_USAGE, 2).
|
||||
-define(STATUS_BADRPC, 3).
|
||||
|
||||
+184
-103
@@ -5,7 +5,7 @@
|
||||
%%% Created : 23 Aug 2006 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -25,59 +25,75 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_frontend_socket).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start/4, start_link/5, starttls/2, starttls/3,
|
||||
compress/1, compress/2, reset_stream/1, send/2,
|
||||
change_shaper/2, monitor/1, get_sockmod/1,
|
||||
get_peer_certificate/1, get_verify_result/1, close/1,
|
||||
setopts/2, change_controller/2, sockname/1,
|
||||
peername/1]).
|
||||
|
||||
%connect/3,
|
||||
-export([start/4,
|
||||
start_link/5,
|
||||
%connect/3,
|
||||
starttls/2,
|
||||
starttls/3,
|
||||
compress/1,
|
||||
compress/2,
|
||||
reset_stream/1,
|
||||
send/2,
|
||||
change_shaper/2,
|
||||
monitor/1,
|
||||
get_sockmod/1,
|
||||
get_peer_certificate/1,
|
||||
get_verify_result/1,
|
||||
close/1,
|
||||
setopts/2,
|
||||
change_controller/2,
|
||||
sockname/1, peername/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-record(socket_state, {sockmod, socket, receiver}).
|
||||
-record(state, {sockmod, socket, receiver}).
|
||||
|
||||
-define(HIBERNATE_TIMEOUT, 90000).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link(Module, SockMod, Socket, Opts, Receiver) ->
|
||||
gen_server:start_link(?MODULE,
|
||||
[Module, SockMod, Socket, Opts, Receiver], []).
|
||||
|
||||
start(Module, SockMod, Socket, Opts) ->
|
||||
case Module:socket_type() of
|
||||
xml_stream ->
|
||||
MaxStanzaSize = case lists:keysearch(max_stanza_size, 1,
|
||||
Opts)
|
||||
of
|
||||
{value, {_, Size}} -> Size;
|
||||
_ -> infinity
|
||||
end,
|
||||
Receiver = ejabberd_receiver:start(Socket, SockMod,
|
||||
none, MaxStanzaSize),
|
||||
case SockMod:controlling_process(Socket, Receiver) of
|
||||
ok -> ok;
|
||||
{error, _Reason} -> SockMod:close(Socket)
|
||||
end,
|
||||
supervisor:start_child(ejabberd_frontend_socket_sup,
|
||||
[Module, SockMod, Socket, Opts, Receiver]);
|
||||
raw ->
|
||||
%{ok, Pid} = Module:start({SockMod, Socket}, Opts),
|
||||
%case SockMod:controlling_process(Socket, Pid) of
|
||||
% ok ->
|
||||
% ok;
|
||||
% {error, _Reason} ->
|
||||
% SockMod:close(Socket)
|
||||
%end
|
||||
todo
|
||||
xml_stream ->
|
||||
MaxStanzaSize =
|
||||
case lists:keysearch(max_stanza_size, 1, Opts) of
|
||||
{value, {_, Size}} -> Size;
|
||||
_ -> infinity
|
||||
end,
|
||||
Receiver = ejabberd_receiver:start(Socket, SockMod, none, MaxStanzaSize),
|
||||
case SockMod:controlling_process(Socket, Receiver) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, _Reason} ->
|
||||
SockMod:close(Socket)
|
||||
end,
|
||||
supervisor:start_child(ejabberd_frontend_socket_sup,
|
||||
[Module, SockMod, Socket, Opts, Receiver]);
|
||||
raw ->
|
||||
%{ok, Pid} = Module:start({SockMod, Socket}, Opts),
|
||||
%case SockMod:controlling_process(Socket, Pid) of
|
||||
% ok ->
|
||||
% ok;
|
||||
% {error, _Reason} ->
|
||||
% SockMod:close(Socket)
|
||||
%end
|
||||
todo
|
||||
end.
|
||||
|
||||
starttls(FsmRef, TLSOpts) ->
|
||||
@@ -87,10 +103,12 @@ starttls(FsmRef, TLSOpts, Data) ->
|
||||
gen_server:call(FsmRef, {starttls, TLSOpts, Data}),
|
||||
FsmRef.
|
||||
|
||||
compress(FsmRef) -> compress(FsmRef, undefined).
|
||||
compress(FsmRef) ->
|
||||
compress(FsmRef, undefined).
|
||||
|
||||
compress(FsmRef, Data) ->
|
||||
gen_server:call(FsmRef, {compress, Data}), FsmRef.
|
||||
gen_server:call(FsmRef, {compress, Data}),
|
||||
FsmRef.
|
||||
|
||||
reset_stream(FsmRef) ->
|
||||
gen_server:call(FsmRef, reset_stream).
|
||||
@@ -101,7 +119,8 @@ send(FsmRef, Data) ->
|
||||
change_shaper(FsmRef, Shaper) ->
|
||||
gen_server:call(FsmRef, {change_shaper, Shaper}).
|
||||
|
||||
monitor(FsmRef) -> erlang:monitor(process, FsmRef).
|
||||
monitor(FsmRef) ->
|
||||
erlang:monitor(process, FsmRef).
|
||||
|
||||
get_sockmod(FsmRef) ->
|
||||
gen_server:call(FsmRef, get_sockmod).
|
||||
@@ -112,9 +131,11 @@ get_peer_certificate(FsmRef) ->
|
||||
get_verify_result(FsmRef) ->
|
||||
gen_server:call(FsmRef, get_verify_result).
|
||||
|
||||
close(FsmRef) -> gen_server:call(FsmRef, close).
|
||||
close(FsmRef) ->
|
||||
gen_server:call(FsmRef, close).
|
||||
|
||||
sockname(FsmRef) -> gen_server:call(FsmRef, sockname).
|
||||
sockname(FsmRef) ->
|
||||
gen_server:call(FsmRef, sockname).
|
||||
|
||||
setopts(FsmRef, Opts) ->
|
||||
gen_server:call(FsmRef, {setopts, Opts}).
|
||||
@@ -122,120 +143,180 @@ setopts(FsmRef, Opts) ->
|
||||
change_controller(FsmRef, C2SPid) ->
|
||||
gen_server:call(FsmRef, {change_controller, C2SPid}).
|
||||
|
||||
peername(FsmRef) -> gen_server:call(FsmRef, peername).
|
||||
peername(FsmRef) ->
|
||||
gen_server:call(FsmRef, peername).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: init(Args) -> {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([Module, SockMod, Socket, Opts, Receiver]) ->
|
||||
%% TODO: monitor the receiver
|
||||
Node = ejabberd_node_groups:get_closest_node(backend),
|
||||
IP = case peername(SockMod, Socket) of
|
||||
{ok, IP1} -> IP1;
|
||||
_ -> undefined
|
||||
{ok, IP1} ->
|
||||
IP1;
|
||||
_ ->
|
||||
undefined
|
||||
end,
|
||||
{SockMod2, Socket2} = check_starttls(SockMod, Socket,
|
||||
Receiver, Opts),
|
||||
{ok, Pid} = rpc:call(Node, Module, start,
|
||||
[{?MODULE, self()}, [{frontend_ip, IP} | Opts]]),
|
||||
{SockMod2, Socket2} = check_starttls(SockMod, Socket, Receiver, Opts),
|
||||
{ok, Pid} =
|
||||
rpc:call(Node, Module, start,
|
||||
[{?MODULE, self()}, [{frontend_ip, IP} | Opts]]),
|
||||
ejabberd_receiver:become_controller(Receiver, Pid),
|
||||
{ok,
|
||||
#socket_state{sockmod = SockMod2, socket = Socket2,
|
||||
receiver = Receiver}}.
|
||||
{ok, #state{sockmod = SockMod2,
|
||||
socket = Socket2,
|
||||
receiver = Receiver}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
%% {reply, Reply, State, Timeout} |
|
||||
%% {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, Reply, State} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call({starttls, TLSOpts, Data}, _From, State) ->
|
||||
{ok, TLSSocket} =
|
||||
ejabberd_receiver:starttls(State#socket_state.receiver,
|
||||
TLSOpts, Data),
|
||||
{ok, TLSSocket} = ejabberd_receiver:starttls(
|
||||
State#state.receiver, TLSOpts, Data),
|
||||
Reply = ok,
|
||||
{reply, Reply,
|
||||
State#socket_state{socket = TLSSocket, sockmod = tls},
|
||||
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call({compress, Data}, _From, State) ->
|
||||
{ok, ZlibSocket} =
|
||||
ejabberd_receiver:compress(State#socket_state.receiver, Data),
|
||||
{ok, ZlibSocket} = ejabberd_receiver:compress(
|
||||
State#state.receiver, Data),
|
||||
Reply = ok,
|
||||
{reply, Reply,
|
||||
State#socket_state{socket = ZlibSocket,
|
||||
sockmod = ejabberd_zlib},
|
||||
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(reset_stream, _From, State) ->
|
||||
ejabberd_receiver:reset_stream(State#socket_state.receiver),
|
||||
ejabberd_receiver:reset_stream(State#state.receiver),
|
||||
Reply = ok,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call({send, Data}, _From, State) ->
|
||||
catch (State#socket_state.sockmod):send(State#socket_state.socket,
|
||||
Data),
|
||||
catch (State#state.sockmod):send(
|
||||
State#state.socket, Data),
|
||||
Reply = ok,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call({change_shaper, Shaper}, _From, State) ->
|
||||
ejabberd_receiver:change_shaper(State#socket_state.receiver,
|
||||
Shaper),
|
||||
ejabberd_receiver:change_shaper(State#state.receiver, Shaper),
|
||||
Reply = ok,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(get_sockmod, _From, State) ->
|
||||
Reply = State#socket_state.sockmod,
|
||||
Reply = State#state.sockmod,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(get_peer_certificate, _From, State) ->
|
||||
Reply = tls:get_peer_certificate(State#socket_state.socket),
|
||||
Reply = tls:get_peer_certificate(State#state.socket),
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(get_verify_result, _From, State) ->
|
||||
Reply = tls:get_verify_result(State#socket_state.socket),
|
||||
Reply = tls:get_verify_result(State#state.socket),
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(close, _From, State) ->
|
||||
ejabberd_receiver:close(State#socket_state.receiver),
|
||||
ejabberd_receiver:close(State#state.receiver),
|
||||
Reply = ok,
|
||||
{stop, normal, Reply, State};
|
||||
|
||||
handle_call(sockname, _From, State) ->
|
||||
#socket_state{sockmod = SockMod, socket = Socket} = State,
|
||||
#state{sockmod = SockMod, socket = Socket} = State,
|
||||
Reply = peername(SockMod, Socket),
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
handle_call(peername, _From, State) ->
|
||||
#socket_state{sockmod = SockMod, socket = Socket} = State,
|
||||
Reply = case SockMod of
|
||||
gen_tcp -> inet:peername(Socket);
|
||||
_ -> SockMod:peername(Socket)
|
||||
end,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
handle_call({setopts, Opts}, _From, State) ->
|
||||
ejabberd_receiver:setopts(State#socket_state.receiver, Opts),
|
||||
{reply, ok, State, ?HIBERNATE_TIMEOUT};
|
||||
handle_call({change_controller, Pid}, _From, State) ->
|
||||
ejabberd_receiver:change_controller(State#socket_state.receiver,
|
||||
Pid),
|
||||
{reply, ok, State, ?HIBERNATE_TIMEOUT};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok, {reply, Reply, State, ?HIBERNATE_TIMEOUT}.
|
||||
|
||||
handle_call(peername, _From, State) ->
|
||||
#state{sockmod = SockMod, socket = Socket} = State,
|
||||
Reply =
|
||||
case SockMod of
|
||||
gen_tcp ->
|
||||
inet:peername(Socket);
|
||||
_ ->
|
||||
SockMod:peername(Socket)
|
||||
end,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call({setopts, Opts}, _From, State) ->
|
||||
ejabberd_receiver:setopts(State#state.receiver, Opts),
|
||||
{reply, ok, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call({change_controller, Pid}, _From, State) ->
|
||||
ejabberd_receiver:change_controller(State#state.receiver, Pid),
|
||||
{reply, ok, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info(timeout, State) ->
|
||||
proc_lib:hibernate(gen_server, enter_loop,
|
||||
[?MODULE, [], State]),
|
||||
proc_lib:hibernate(gen_server, enter_loop, [?MODULE, [], State]),
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT}.
|
||||
|
||||
terminate(_Reason, _State) -> ok.
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
%% Description: This function is called by a gen_server when it is about to
|
||||
%% terminate. It should be the opposite of Module:init/1 and do any necessary
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
check_starttls(SockMod, Socket, Receiver, Opts) ->
|
||||
TLSEnabled = lists:member(tls, Opts),
|
||||
TLSOpts = lists:filter(fun ({certfile, _}) -> true;
|
||||
(_) -> false
|
||||
end,
|
||||
Opts),
|
||||
if TLSEnabled ->
|
||||
{ok, TLSSocket} = ejabberd_receiver:starttls(Receiver,
|
||||
TLSOpts),
|
||||
{tls, TLSSocket};
|
||||
true -> {SockMod, Socket}
|
||||
TLSOpts = lists:filter(fun({certfile, _}) -> true;
|
||||
(_) -> false
|
||||
end, Opts),
|
||||
if
|
||||
TLSEnabled ->
|
||||
{ok, TLSSocket} = ejabberd_receiver:starttls(Receiver, TLSOpts),
|
||||
{tls, TLSSocket};
|
||||
true ->
|
||||
{SockMod, Socket}
|
||||
end.
|
||||
|
||||
peername(SockMod, Socket) ->
|
||||
case SockMod of
|
||||
gen_tcp -> inet:peername(Socket);
|
||||
_ -> SockMod:peername(Socket)
|
||||
gen_tcp ->
|
||||
inet:peername(Socket);
|
||||
_ ->
|
||||
SockMod:peername(Socket)
|
||||
end.
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_global_router.erl
|
||||
%%% Author : Geoff Cant <gcant@process-one.net>
|
||||
%%% Purpose : Router for virtual global routes
|
||||
%%% Created : 07 Aug 2002 by Geoff Cant <gcant@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_global_router).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_global_router.hrl").
|
||||
|
||||
%% API
|
||||
-export([start_link/0
|
||||
,route/3
|
||||
,route/4
|
||||
,find_route/1
|
||||
,register_route/1
|
||||
,register_route/2
|
||||
,unregister_route/1
|
||||
,unregister_route/2
|
||||
,expand_routes/1
|
||||
,all_prefixes/0
|
||||
,server_host/2
|
||||
]).
|
||||
|
||||
%% Eunit test exports.
|
||||
-export([remove_prefix/2
|
||||
,find_prefix/2
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% @doc Starts the server
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
route(From, To, Packet) ->
|
||||
case find_route(To) of
|
||||
Route = #global_route{} ->
|
||||
route(Route, From, To, Packet);
|
||||
no_route ->
|
||||
ok
|
||||
end.
|
||||
|
||||
route(#global_route{pids=Pids}, From, To, Packet) ->
|
||||
Pid = pick_route(Pids),
|
||||
Pid ! {route, From, To, Packet},
|
||||
ok.
|
||||
|
||||
find_route(Domain) ->
|
||||
case prefix_match(Domain, all_prefixes()) of
|
||||
nomatch ->
|
||||
no_route;
|
||||
Prefix when is_list(Prefix) ->
|
||||
dirty_get_route(Prefix)
|
||||
end.
|
||||
|
||||
register_route(Prefix) ->
|
||||
register_route(Prefix, self()).
|
||||
|
||||
register_route(Prefix, Pid) ->
|
||||
Route = #global_route{prefix=Prefix,
|
||||
pids=[Pid]},
|
||||
{atomic, ok} = mnesia:transaction(fun write_route/1,
|
||||
[Route]),
|
||||
ok.
|
||||
|
||||
unregister_route(Prefix) ->
|
||||
unregister_route(Prefix, self()).
|
||||
|
||||
unregister_route(Prefix, Pid) ->
|
||||
mnesia:transaction(fun delete_route/2, [Prefix, Pid]).
|
||||
|
||||
expand_routes(Domain) ->
|
||||
[expand_route(Prefix, Domain) || Prefix <- all_prefixes()].
|
||||
|
||||
expand_route(Prefix, Domain) ->
|
||||
lists:append([Prefix, ".", Domain]).
|
||||
|
||||
all_prefixes() ->
|
||||
mnesia:dirty_all_keys(global_route).
|
||||
|
||||
server_host(Host, Pid) ->
|
||||
#global_route{prefix=P,pids=Pids} = find_route(Host),
|
||||
true = lists:member(Pid, Pids),
|
||||
remove_prefix(P, Host).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @spec init(Args) -> {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%% @doc Initialises the server's state
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
update_tables(),
|
||||
mnesia:create_table(global_route,
|
||||
[{ram_copies, [node()]},
|
||||
{type, set},
|
||||
{attributes,
|
||||
record_info(fields, global_route)}]),
|
||||
mnesia:add_table_copy(global_route, node(), ram_copies),
|
||||
{ok, #state{}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @spec
|
||||
%% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
%% {reply, Reply, State, Timeout} |
|
||||
%% {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, Reply, State} |
|
||||
%% {stop, Reason, State}
|
||||
%% @doc Call message handler callbacks
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call(Call, _From, State) ->
|
||||
?WARNING_MSG("Unexpected call ~p.", [Call]),
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @spec
|
||||
%% handle_cast(Msg, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% @doc Cast message handler callbacks
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(Msg, State) ->
|
||||
?WARNING_MSG("Unexpected cast ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @spec
|
||||
%% handle_info(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% @doc Non gen-server message handler callbacks
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info(Info, State) ->
|
||||
?WARNING_MSG("Unexpected info ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @spec terminate(Reason, State) -> void()
|
||||
%% @doc This function is called by a gen_server when it is about to
|
||||
%% terminate. It should be the opposite of Module:init/1 and do any necessary
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% @doc Convert process state when code is changed
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
pick_route(Pids) when is_list(Pids) ->
|
||||
pick_local_route(Pids).
|
||||
|
||||
pick_local_route(Pids) when is_list(Pids) ->
|
||||
case [Pid || Pid <- Pids,
|
||||
node(Pid) =:= node()] of
|
||||
[] -> pick_remote_route(Pids);
|
||||
LocalPids -> pick_one_route(LocalPids)
|
||||
end.
|
||||
|
||||
pick_remote_route(Pids) when is_list(Pids) -> pick_one_route(Pids).
|
||||
|
||||
pick_one_route(Pids) when is_list(Pids) ->
|
||||
hd(Pids). % should pick random pid?
|
||||
|
||||
|
||||
update_tables() ->
|
||||
CorrectAttributes = record_info(fields, global_route),
|
||||
case catch mnesia:table_info(global_route, attributes) of
|
||||
CorrectAttributes ->
|
||||
ok;
|
||||
IncorrectAttributes when is_list(IncorrectAttributes) ->
|
||||
mnesia:delete_table(global_route);
|
||||
{'EXIT', _} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
write_route(R = #global_route{prefix=Prefix}) ->
|
||||
NewRoute = case mnesia:read({global_route, Prefix}) of
|
||||
[] -> R;
|
||||
[OldRoute] ->
|
||||
combine_routes(OldRoute, R)
|
||||
end,
|
||||
ok = mnesia:write(NewRoute).
|
||||
|
||||
delete_route(Prefix, Pid) ->
|
||||
case mnesia:read({global_route, Prefix}, write) of
|
||||
[] -> ok;
|
||||
[#global_route{pids=[Pid]}] ->
|
||||
mnesia:delete({global_route, Prefix});
|
||||
[Route = #global_route{pids=Pids}] ->
|
||||
mnesia:write(Route#global_route{pids=Pids -- [Pid]})
|
||||
end.
|
||||
|
||||
combine_routes(R = #global_route{prefix=P, pids=APids},
|
||||
#global_route{prefix=P, pids=BPids}) ->
|
||||
R#global_route{pids=lists:usort(APids ++ BPids)}.
|
||||
|
||||
prefix_match(Domain, Prefixes) ->
|
||||
case find_prefix(Domain, Prefixes) of
|
||||
[] -> nomatch;
|
||||
[Prefix] -> Prefix;
|
||||
_Prefixes ->
|
||||
erlang:error(multiple_prefix_matches)
|
||||
end.
|
||||
|
||||
dirty_get_route(Prefix) ->
|
||||
case mnesia:dirty_read({global_route, Prefix}) of
|
||||
[Route = #global_route{}] ->
|
||||
Route;
|
||||
_ -> no_route
|
||||
end.
|
||||
|
||||
find_prefix(Domain, PossiblePrefixes) ->
|
||||
[Prefix
|
||||
|| Prefix <- PossiblePrefixes,
|
||||
lists:prefix(Prefix, Domain)].
|
||||
|
||||
remove_prefix(Prefix, Domain) ->
|
||||
[$. | BareDomain] = Domain -- Prefix,
|
||||
BareDomain.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Eunit Tests.
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -1,11 +1,11 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : stringprep_sup.erl
|
||||
%%% Author : Mickael Remond <mremond@process-one.net>
|
||||
%%% Description : Supervisor for the Stringprep worker.
|
||||
%%% Created : 29 Jun 2007 by Mickael Remond <mremond@process-one.net>
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_global_router.hrl
|
||||
%%% Author : Geoff Cant <gcant@process-one.net>
|
||||
%%% Purpose : Virtual global routes record definitions
|
||||
%%% Created : 08 Aug 2002 by Geoff Cant <gcant@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% 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
|
||||
@@ -22,24 +22,6 @@
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(stringprep_sup).
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
%% API
|
||||
-export([start_link/0]).
|
||||
|
||||
%% Supervisor callbacks
|
||||
-export([init/1]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
|
||||
|
||||
init([]) ->
|
||||
StringPrep = {stringprep, {stringprep, start_link, []},
|
||||
permanent, brutal_kill, worker, [stringprep]},
|
||||
{ok, {{one_for_all, 10, 1}, [StringPrep]}}.
|
||||
-record(global_route, {prefix, pids}).
|
||||
+56
-47
@@ -5,7 +5,7 @@
|
||||
%%% Created : 8 Aug 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -67,86 +67,69 @@
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ejabberd_hooks}, ejabberd_hooks, [], []).
|
||||
|
||||
-spec add(atom(), fun(), number()) -> any().
|
||||
|
||||
%% @spec (Hook::atom(), Function::function(), Seq::integer()) -> ok
|
||||
%% @doc See add/4.
|
||||
add(Hook, Function, Seq) when is_function(Function) ->
|
||||
add(Hook, global, undefined, Function, Seq).
|
||||
|
||||
-spec add(atom(), binary() | atom(), fun() | atom() , number()) -> any().
|
||||
add(Hook, Host, Function, Seq) when is_function(Function) ->
|
||||
add(Hook, Host, undefined, Function, Seq);
|
||||
|
||||
%% @spec (Hook::atom(), Module::atom(), Function::atom(), Seq::integer()) -> ok
|
||||
%% @doc Add a module and function to this hook.
|
||||
%% The integer sequence is used to sort the calls: low number is called before high number.
|
||||
add(Hook, Module, Function, Seq) ->
|
||||
add(Hook, global, Module, Function, Seq).
|
||||
|
||||
-spec add(atom(), binary() | global, atom(), atom() | fun(), number()) -> any().
|
||||
|
||||
add(Hook, Host, Module, Function, Seq) ->
|
||||
add(Hook, Host, Module, Function, Seq)
|
||||
when is_binary(Host) orelse is_atom(Host) ->
|
||||
gen_server:call(ejabberd_hooks, {add, Hook, Host, Module, Function, Seq}).
|
||||
|
||||
-spec add_dist(atom(), atom(), atom(), atom() | fun(), number()) -> any().
|
||||
|
||||
add_dist(Hook, Node, Module, Function, Seq) ->
|
||||
gen_server:call(ejabberd_hooks, {add, Hook, global, Node, Module, Function, Seq}).
|
||||
|
||||
-spec add_dist(atom(), binary() | global, atom(), atom(), atom() | fun(), number()) -> any().
|
||||
|
||||
add_dist(Hook, Host, Node, Module, Function, Seq) ->
|
||||
gen_server:call(ejabberd_hooks, {add, Hook, Host, Node, Module, Function, Seq}).
|
||||
|
||||
-spec delete(atom(), fun(), number()) -> ok.
|
||||
|
||||
%% @spec (Hook::atom(), Function::function(), Seq::integer()) -> ok
|
||||
%% @doc See del/4.
|
||||
delete(Hook, Function, Seq) when is_function(Function) ->
|
||||
delete(Hook, global, undefined, Function, Seq).
|
||||
|
||||
-spec delete(atom(), binary() | atom(), atom() | fun(), number()) -> ok.
|
||||
|
||||
delete(Hook, Host, Function, Seq) when is_function(Function) ->
|
||||
delete(Hook, Host, undefined, Function, Seq);
|
||||
|
||||
%% @spec (Hook::atom(), Module::atom(), Function::atom(), Seq::integer()) -> ok
|
||||
%% @doc Delete a module and function from this hook.
|
||||
%% It is important to indicate exactly the same information than when the call was added.
|
||||
delete(Hook, Module, Function, Seq) ->
|
||||
delete(Hook, global, Module, Function, Seq).
|
||||
|
||||
-spec delete(atom(), binary() | global, atom(), atom() | fun(), number()) -> ok.
|
||||
|
||||
delete(Hook, Host, Module, Function, Seq) ->
|
||||
delete(Hook, Host, Module, Function, Seq)
|
||||
when is_binary(Host) orelse is_atom(Host) ->
|
||||
gen_server:call(ejabberd_hooks, {delete, Hook, Host, Module, Function, Seq}).
|
||||
|
||||
-spec delete_dist(atom(), atom(), atom(), atom() | fun(), number()) -> ok.
|
||||
|
||||
delete_dist(Hook, Node, Module, Function, Seq) ->
|
||||
delete_dist(Hook, global, Node, Module, Function, Seq).
|
||||
|
||||
-spec delete_dist(atom(), binary() | global, atom(), atom(), atom() | fun(), number()) -> ok.
|
||||
|
||||
delete_dist(Hook, Host, Node, Module, Function, Seq) ->
|
||||
gen_server:call(ejabberd_hooks, {delete, Hook, Host, Node, Module, Function, Seq}).
|
||||
|
||||
-spec run(atom(), list()) -> ok.
|
||||
|
||||
%% @spec (Hook::atom(), Args) -> ok
|
||||
%% @doc Run the calls of this hook in order, don't care about function results.
|
||||
%% If a call returns stop, no more calls are performed.
|
||||
run(Hook, Args) ->
|
||||
run(Hook, global, Args).
|
||||
|
||||
-spec run(atom(), binary() | global, list()) -> ok.
|
||||
|
||||
run(Hook, Host, Args) ->
|
||||
case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
run1(Ls, Hook, Args);
|
||||
[] ->
|
||||
ok
|
||||
case get_registered_hooks_for(Hook, Host) of
|
||||
[] ->
|
||||
ok;
|
||||
Ls ->
|
||||
run1(Ls, Hook, Args)
|
||||
end.
|
||||
|
||||
-spec run_fold(atom(), any(), list()) -> any().
|
||||
|
||||
%% @spec (Hook::atom(), Val, Args) -> Val | stopped | NewVal
|
||||
%% @doc Run the calls of this hook in order.
|
||||
%% The arguments passed to the function are: [Val | Args].
|
||||
%% The result of a call is used as Val for the next call.
|
||||
@@ -155,14 +138,14 @@ run(Hook, Host, Args) ->
|
||||
run_fold(Hook, Val, Args) ->
|
||||
run_fold(Hook, global, Val, Args).
|
||||
|
||||
-spec run_fold(atom(), binary() | global, any(), list()) -> any().
|
||||
|
||||
%% @spec (Hook::atom(), Host, Val, Args) -> Val | stopped | NewVal
|
||||
%% Host = global | binary()
|
||||
run_fold(Hook, Host, Val, Args) ->
|
||||
case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
run_fold1(Ls, Hook, Val, Args);
|
||||
[] ->
|
||||
Val
|
||||
case get_registered_hooks_for(Hook, Host) of
|
||||
[] ->
|
||||
Val;
|
||||
Ls ->
|
||||
run_fold1(Ls, Hook, Val, Args)
|
||||
end.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -190,7 +173,8 @@ init([]) ->
|
||||
%% {stop, Reason, State} (terminate/2 is called)
|
||||
%%----------------------------------------------------------------------
|
||||
handle_call({add, Hook, Host, Module, Function, Seq}, _From, State) ->
|
||||
Reply = case ets:lookup(hooks, {Hook, Host}) of
|
||||
NHost = ejabberd:normalize_host(Host),
|
||||
Reply = case ets:lookup(hooks, {Hook, NHost}) of
|
||||
[{_, Ls}] ->
|
||||
El = {Seq, Module, Function},
|
||||
case lists:member(El, Ls) of
|
||||
@@ -198,12 +182,12 @@ handle_call({add, Hook, Host, Module, Function, Seq}, _From, State) ->
|
||||
ok;
|
||||
false ->
|
||||
NewLs = lists:merge(Ls, [El]),
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs}),
|
||||
ets:insert(hooks, {{Hook, NHost}, NewLs}),
|
||||
ok
|
||||
end;
|
||||
[] ->
|
||||
NewLs = [{Seq, Module, Function}],
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs}),
|
||||
ets:insert(hooks, {{Hook, NHost}, NewLs}),
|
||||
ok
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
@@ -226,10 +210,14 @@ handle_call({add, Hook, Host, Node, Module, Function, Seq}, _From, State) ->
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
handle_call({delete, Hook, Host, Module, Function, Seq}, _From, State) ->
|
||||
Reply = case ets:lookup(hooks, {Hook, Host}) of
|
||||
NHost = ejabberd:normalize_host(Host),
|
||||
Reply = case ets:lookup(hooks, {Hook, NHost}) of
|
||||
[{_, Ls}] ->
|
||||
NewLs = lists:delete({Seq, Module, Function}, Ls),
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs}),
|
||||
case NewLs of
|
||||
[] -> ets:delete(hooks, {Hook, NHost});
|
||||
_ -> ets:insert(hooks, {{Hook, NHost}, NewLs})
|
||||
end,
|
||||
ok;
|
||||
[] ->
|
||||
ok
|
||||
@@ -239,7 +227,10 @@ handle_call({delete, Hook, Host, Node, Module, Function, Seq}, _From, State) ->
|
||||
Reply = case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
NewLs = lists:delete({Seq, Node, Module, Function}, Ls),
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs}),
|
||||
case NewLs of
|
||||
[] -> ets:delete(hooks, {Hook, Host});
|
||||
_ -> ets:insert(hooks, {{Hook, Host}, NewLs})
|
||||
end,
|
||||
ok;
|
||||
[] ->
|
||||
ok
|
||||
@@ -316,7 +307,7 @@ run1([{_Seq, Module, Function} | Ls], Hook, Args) ->
|
||||
[Reason, {Hook, Args}]),
|
||||
run1(Ls, Hook, Args);
|
||||
stop ->
|
||||
ok;
|
||||
stop;
|
||||
_ ->
|
||||
run1(Ls, Hook, Args)
|
||||
end.
|
||||
@@ -363,3 +354,21 @@ run_fold1([{_Seq, Module, Function} | Ls], Hook, Val, Args) ->
|
||||
NewVal ->
|
||||
run_fold1(Ls, Hook, NewVal, Args)
|
||||
end.
|
||||
|
||||
get_registered_hooks_for(Hook, global) ->
|
||||
case ets:lookup(hooks, {Hook, global}) of
|
||||
[{_, Ls}] ->
|
||||
Ls;
|
||||
[] ->
|
||||
[]
|
||||
end;
|
||||
|
||||
get_registered_hooks_for(Hook, Host) ->
|
||||
GlobalHooks = get_registered_hooks_for(Hook, global),
|
||||
HostHooks = case ets:lookup(hooks, {Hook, ejabberd:normalize_host(Host)}) of
|
||||
[{_, Ls}] ->
|
||||
Ls;
|
||||
[] ->
|
||||
[]
|
||||
end,
|
||||
lists:umerge(GlobalHooks, HostHooks).
|
||||
|
||||
@@ -0,0 +1,489 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : ejabberd_hosts.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Description : Synchronises running VHosts with the hosts table in the Mnesia database.
|
||||
%%% Created : 16 Nov 2007 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
%%% Database schema (version / storage / table)
|
||||
%%%
|
||||
%%% The table 'hosts' keeps the dynamic hosts (not defined in ejabberd.cfg)
|
||||
%%%
|
||||
%%% 3.0.0-alpha-x / mnesia / hosts
|
||||
%%% host = string()
|
||||
%%% clusterid = integer()
|
||||
%%% config = string()
|
||||
%%%
|
||||
%%% 3.0.0-alpha-x / odbc / hosts
|
||||
%%% host = varchar150
|
||||
%%% clusterid = integer
|
||||
%%% config = text
|
||||
|
||||
-module(ejabberd_hosts).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% External API
|
||||
-export([start_link/0
|
||||
,reload/0
|
||||
]).
|
||||
|
||||
%% Host Registration API
|
||||
-export([register/1
|
||||
,register/2
|
||||
,registered/1
|
||||
,running/1
|
||||
,registered/0
|
||||
,remove/1
|
||||
,update_host_conf/2
|
||||
]).
|
||||
|
||||
%% Host control API
|
||||
-export([start_host/1
|
||||
,start_hosts/1
|
||||
,stop_host/1
|
||||
,stop_hosts/1
|
||||
,load_host_cert/2
|
||||
]).
|
||||
|
||||
%% Private utility functions
|
||||
-export([get_hosts/1
|
||||
,config_from_string/2
|
||||
,get_host_config/2
|
||||
,diff_hosts/0
|
||||
,diff_hosts/1
|
||||
,diff_hosts/2
|
||||
,reload_hosts/0
|
||||
,delete_host_config/1
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("ejabberd_config.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
-record(state, {state=wait_odbc,
|
||||
backend=mnesia,
|
||||
odbc_wait_time=120}).
|
||||
-record(hosts, {host, clusterid, config}).
|
||||
|
||||
-define(RELOAD_INTERVAL, timer:seconds(60)).
|
||||
-define(ODBC_STARTUP_TIME, 120). % 2minute limit for ODBC startup.
|
||||
|
||||
%% The vhost where the table 'hosts' is stored
|
||||
%% The table 'hosts' is defined in gen_storage as being for the "localhost" vhost
|
||||
-define(HOSTS_HOST, list_to_binary(?MYNAME)).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
|
||||
reload() ->
|
||||
?MODULE ! reload.
|
||||
|
||||
%% Creates a vhost in the system.
|
||||
register(Host) when is_list(Host) -> ?MODULE:register(Host, "").
|
||||
register(Host, Config) when is_list(Host), is_list(Config) ->
|
||||
true = exmpp_stringprep:is_node(Host),
|
||||
ID = get_clusterid(),
|
||||
H = #hosts{host = Host, clusterid = ID, config = Config},
|
||||
ok = gen_storage:dirty_write(?HOSTS_HOST, H),
|
||||
reload(),
|
||||
ok.
|
||||
|
||||
%% Updates host configuration
|
||||
update_host_conf(Host, Config) when is_list(Host), is_list(Config) ->
|
||||
true = exmpp_stringprep:is_node(Host),
|
||||
case registered(Host) of
|
||||
false -> {error, host_process_not_registered};
|
||||
true ->
|
||||
remove(Host),
|
||||
?MODULE:register(Host, Config)
|
||||
end.
|
||||
|
||||
%% Removes a vhost from the system,
|
||||
%% XXX deleting all ODBC data.
|
||||
remove(Host) when is_list(Host) ->
|
||||
true = exmpp_stringprep:is_node(Host),
|
||||
ID = get_clusterid(),
|
||||
gen_storage:dirty_delete_where(
|
||||
?HOSTS_HOST, hosts,
|
||||
[{'andalso',
|
||||
{'==', clusterid, ID},
|
||||
{'==', host, Host}}]),
|
||||
reload(),
|
||||
ok.
|
||||
|
||||
registered() ->
|
||||
mnesia:dirty_select(local_config,
|
||||
ets:fun2ms(fun (#local_config{key={Host, host}}) ->
|
||||
Host
|
||||
end)).
|
||||
|
||||
registered(Host) when is_list(Host) ->
|
||||
case mnesia:dirty_read({local_config, {Host, host}}) of
|
||||
[{local_config, {Host, host}, _}] -> true;
|
||||
[] -> false
|
||||
end.
|
||||
|
||||
running(global) -> true;
|
||||
running(HostString) when is_list(HostString) ->
|
||||
Host = list_to_binary(HostString),
|
||||
Routes = [H
|
||||
|| {H, _, {apply, ejabberd_local, route}} <- ejabberd_router:read_route(Host),
|
||||
H =:= Host],
|
||||
Routes =/= [].
|
||||
|
||||
|
||||
load_host_cert(Host, PemData) ->
|
||||
File = cert_filename(Host),
|
||||
ok = file:write_file(File, PemData),
|
||||
configure_host_cert(Host, File).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: init(Args) -> {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
Backend = mnesia, %%+++ TODO: allow to configure this in ejabberd.cfg
|
||||
configure_static_hosts(),
|
||||
get_clusterid(), %% this is to report an error if the option wasn't configured
|
||||
ejabberd_commands:register_commands(commands()),
|
||||
%% Wait up to 120 seconds for odbc to start
|
||||
{ok, #state{state=wait_odbc,backend=Backend,odbc_wait_time=?ODBC_STARTUP_TIME}, timer:seconds(1)}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
%% {reply, Reply, State, Timeout} |
|
||||
%% {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, Reply, State} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
%% Wait for odbc to start.
|
||||
handle_info(timeout, State = #state{state=wait_odbc,backend=Backend,odbc_wait_time=N}) when N > 0 ->
|
||||
case (Backend /= odbc) orelse ejabberd_odbc:running(?MYNAME) of
|
||||
true ->
|
||||
?DEBUG("ejabberd_hosts: odbc now running.",[]),
|
||||
|
||||
%% The table 'hosts' is defined in gen_storage as being for the "localhost" vhost
|
||||
Host = ?MYNAME,
|
||||
HostB = list_to_binary(Host),
|
||||
gen_storage:create_table(Backend, HostB, hosts,
|
||||
[{disc_only_copies, [node()]},
|
||||
{odbc_host, Host},
|
||||
{attributes, record_info(fields, hosts)},
|
||||
{types, [{host, text},
|
||||
{clusterid, int},
|
||||
{config, text}]}]),
|
||||
|
||||
self() ! reload,
|
||||
timer:send_interval(?RELOAD_INTERVAL, reload),
|
||||
{noreply, State#state{state=running,odbc_wait_time=0}};
|
||||
false ->
|
||||
{noreply,State#state{odbc_wait_time=N-1},timer:seconds(1)}
|
||||
end;
|
||||
handle_info(timeout, State=#state{state=running}) ->
|
||||
?WARNING_MSG("Spurious timeout message when odbc is already running.", []),
|
||||
{noreply, State};
|
||||
|
||||
handle_info(reload, State = #state{state=running}) ->
|
||||
try reload_hosts()
|
||||
catch
|
||||
Class:Error ->
|
||||
StackTrace = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("~p while synchonising running vhosts with database: ~p~n~p", [Class, Error, StackTrace])
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info(reload, State = #state{state=wait_odbc}) ->
|
||||
?ERROR_MSG("Tried to reload vhosts while waiting for odbc startup.", []),
|
||||
handle_info(timeout, State);
|
||||
|
||||
handle_info({reload, Host}, State = #state{state=running}) ->
|
||||
try reload_host(Host)
|
||||
catch
|
||||
Class:Error ->
|
||||
StackTrace = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("~p while synchonising running ~p with database: ~p~n~p", [Host, Class, Error, StackTrace])
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info({reload, Host}, State = #state{state=wait_odbc}) ->
|
||||
?ERROR_MSG("Tried to reload ~p while waiting for odbc startup.", [Host]),
|
||||
handle_info(timeout, State);
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
%% Description: This function is called by a gen_server when it is about to
|
||||
%% terminate. It should be the opposite of Module:init/1 and do any necessary
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ejabberd_commands:unregister_commands(commands()),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
reload_hosts() ->
|
||||
reload_hosts(get_hosts(odbc)).
|
||||
|
||||
reload_hosts(NewHosts) ->
|
||||
{AddedHosts,RemovedHosts} = diff_hosts(NewHosts),
|
||||
%% Avoid removing permanent hosts (staticly configured hosts)
|
||||
DeletedHosts = [H || H <- RemovedHosts,
|
||||
ejabberd_config:get_host_option(H, permanent) =/= true],
|
||||
AddHostConfig = lists:map(fun (Host) ->
|
||||
{Host, get_host_config(odbc,Host)}
|
||||
end, AddedHosts),
|
||||
update_config(AddHostConfig,DeletedHosts),
|
||||
RemovedNotDelete = RemovedHosts -- DeletedHosts,
|
||||
ejabberd_config:add_global_option(hosts, NewHosts++RemovedNotDelete), % overwrite hosts list
|
||||
stop_hosts(DeletedHosts),
|
||||
start_hosts(AddedHosts),
|
||||
ejabberd_local:refresh_iq_handlers(),
|
||||
{DeletedHosts, AddedHosts}.
|
||||
|
||||
%% updates the configuration of an existing virtual host
|
||||
reload_host(Host) ->
|
||||
Config = get_host_config(odbc,Host),
|
||||
F = fun() ->
|
||||
mnesia:write_lock_table(local_config),
|
||||
ejabberd_config:configure_host(Host, Config),
|
||||
ok
|
||||
end,
|
||||
{atomic, ok} = mnesia:transaction(F),
|
||||
% restart host
|
||||
stop_host(Host),
|
||||
start_host(Host),
|
||||
ejabberd_local:refresh_iq_handlers(),
|
||||
ok.
|
||||
|
||||
%% Apply the vhost changes (new, removed vhosts, configuration) to mnesia
|
||||
update_config(AddHostConfig, RemoveHosts) ->
|
||||
F = fun() ->
|
||||
mnesia:write_lock_table(local_config),
|
||||
lists:foreach(fun configure_new_host/1, AddHostConfig),
|
||||
lists:foreach(fun delete_host_config/1, RemoveHosts),
|
||||
ok
|
||||
end,
|
||||
{atomic, ok} = mnesia:transaction(F).
|
||||
|
||||
%% Write the configuration data for a new vhost to mnesia (currently only modules)
|
||||
configure_new_host({H, Config}) when is_list(Config) ->
|
||||
reconfigure_host_cert(H),
|
||||
ejabberd_config:configure_host(H, Config).
|
||||
|
||||
%% Delete the mnesia config data for a vhost.
|
||||
%% Needs to mirror any data we insert in configure_new_host (currently only modules)
|
||||
delete_host_config(Host) ->
|
||||
ejabberd_config:delete_host(Host).
|
||||
|
||||
%% Startup a list of vhosts
|
||||
start_hosts([]) -> ok;
|
||||
start_hosts(AddHosts) when is_list(AddHosts) ->
|
||||
?DEBUG("ejabberd_hosts adding hosts: ~P", [AddHosts, 10]),
|
||||
lists:foreach(fun start_host/1, AddHosts).
|
||||
|
||||
%% Start a single vhost (route, modules)
|
||||
start_host(Host) when is_list(Host) ->
|
||||
?DEBUG("Starting host ~p", [Host]),
|
||||
ejabberd_router:register_route(Host, {apply, ejabberd_local, route}),
|
||||
case ejabberd_config:get_local_option({modules, Host}) of
|
||||
undefined -> ok;
|
||||
Modules when is_list(Modules) ->
|
||||
lists:foreach(
|
||||
fun({Module, Args}) ->
|
||||
gen_mod:start_module(Host, Module, Args)
|
||||
end, Modules)
|
||||
end,
|
||||
case auth_method(Host) of
|
||||
{host_method, HostMethod} ->
|
||||
ejabberd_auth:start_methods(Host, HostMethod)
|
||||
end,
|
||||
ok.
|
||||
|
||||
|
||||
%% Shut down a list of vhosts.
|
||||
stop_hosts([]) -> ok;
|
||||
stop_hosts(RemoveHosts) when is_list(RemoveHosts)->
|
||||
?DEBUG("ejabberd_hosts removing hosts: ~p", [RemoveHosts]),
|
||||
lists:foreach(fun stop_host/1, RemoveHosts).
|
||||
|
||||
%% Shut down a single vhost. (Routes, modules)
|
||||
stop_host(Host) when is_list(Host) ->
|
||||
?DEBUG("Stopping host ~p", [Host]),
|
||||
ejabberd_router:force_unregister_route(list_to_binary(Host)),
|
||||
lists:foreach(fun(Module) ->
|
||||
gen_mod:stop_module_keep_config(Host, Module)
|
||||
end, gen_mod:loaded_modules(Host)),
|
||||
case auth_method(Host) of
|
||||
{host_method, Method} ->
|
||||
ejabberd_auth:stop_methods(Host, Method)
|
||||
end.
|
||||
|
||||
%% Get the current vhost list from a variety of sources (ODBC, internal)
|
||||
get_hosts(ejabberd) -> ?MYHOSTS;
|
||||
get_hosts(odbc) ->
|
||||
ClusterID = get_clusterid(),
|
||||
Hosts = gen_storage:dirty_select(?HOSTS_HOST, hosts, [{'=', clusterid, ClusterID}]),
|
||||
lists:map(fun (#hosts{host = Host}) ->
|
||||
exmpp_stringprep:nameprep(Host)
|
||||
end, Hosts).
|
||||
|
||||
%% Retreive the text format config for host Host from ODBC and covert
|
||||
%% it into a {host, Host, Config} tuple.
|
||||
get_host_config(odbc, Host) ->
|
||||
case gen_storage:dirty_read(?HOSTS_HOST, hosts, Host) of
|
||||
[] ->
|
||||
erlang:error({no_such_host, Host});
|
||||
[H] ->
|
||||
config_from_string(Host, H#hosts.config);
|
||||
E ->
|
||||
erlang:error({host_config_error, E})
|
||||
end.
|
||||
|
||||
%% Convert a plaintext string into a host config tuple.
|
||||
config_from_string(_Host, "") -> [];
|
||||
config_from_string(_Host, Config) ->
|
||||
{ok, Tokens, _} = erl_scan:string(Config),
|
||||
case erl_parse:parse_term(Tokens) of
|
||||
{ok, List} when is_list(List) ->
|
||||
List;
|
||||
E ->
|
||||
erlang:error({bad_host_config, Config, E})
|
||||
end.
|
||||
|
||||
diff_hosts() ->
|
||||
diff_hosts(get_hosts(odbc)).
|
||||
|
||||
diff_hosts(NewHosts) ->
|
||||
diff_hosts(NewHosts, get_hosts(ejabberd)).
|
||||
|
||||
%% Given the new list of vhosts and the old list, return the list of
|
||||
%% hosts added since last time and the list of hosts that have been
|
||||
%% removed.
|
||||
diff_hosts(NewHosts, OldHosts) ->
|
||||
RemoveHosts = OldHosts -- NewHosts,
|
||||
AddHosts = NewHosts -- OldHosts,
|
||||
{AddHosts,RemoveHosts}.
|
||||
|
||||
%% XXX - this should be part of auth, not hosts.
|
||||
auth_method(Host) ->
|
||||
case ejabberd_config:get_host_option(Host, auth_method) of
|
||||
undefined ->
|
||||
[Default] = ejabberd_config:get_host_option(global, auth_method),
|
||||
{host_method, Default};
|
||||
Other ->
|
||||
{host_method, Other}
|
||||
end.
|
||||
|
||||
configure_static_hosts() ->
|
||||
?DEBUG("Node startup - configuring hosts: ~p", [?MYHOSTS]),
|
||||
%% Add a null configuration for all MYHOSTS - this ensures
|
||||
%% the 'I'm a host' term gets written to the config table.
|
||||
%% We don't need any configuration options because these are
|
||||
%% statically configured hosts already configured by ejabberd_config.
|
||||
F = fun () ->
|
||||
lists:foreach(fun (H) -> ejabberd_config:configure_host(H, [{permanent, true}]) end,
|
||||
?MYHOSTS)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
cert_filename(Host) ->
|
||||
Dir = ejabberd_config:get_local_option({domain_certdir, global}),
|
||||
filename:join(Dir, Host ++ ".pem").
|
||||
|
||||
configure_host_cert(Host, File) ->
|
||||
ejabberd_config:add_local_option({domain_certfile, Host}, File),
|
||||
ok.
|
||||
|
||||
reconfigure_host_cert(Host) ->
|
||||
File = cert_filename(Host),
|
||||
case ejabberd_config:is_file_readable(File) of
|
||||
true ->
|
||||
ejabberd_config:mne_add_local_option({domain_certfile, Host}, File),
|
||||
ok;
|
||||
false ->
|
||||
no_cert
|
||||
end.
|
||||
|
||||
get_clusterid() ->
|
||||
case ejabberd_config:get_local_option(clusterid) of
|
||||
ID when is_integer(ID) ->
|
||||
ID;
|
||||
undefined ->
|
||||
?ERROR_MSG("Please add to your ejabberd.cfg the line: ~n {clusterid, 1}.", []),
|
||||
1;
|
||||
Other ->
|
||||
?ERROR_MSG("Change your misconfigured {clusterid, ~p} to the value: ~p", [Other, 1]),
|
||||
1
|
||||
end.
|
||||
|
||||
commands() ->
|
||||
[
|
||||
%% The commands status, stop and restart are implemented also in ejabberd_ctl
|
||||
%% They are defined here so that other interfaces can use them too
|
||||
#ejabberd_commands{name = host_list, tags = [hosts],
|
||||
desc = "Get a list of registered virtual hosts",
|
||||
module = ?MODULE, function = registered,
|
||||
args = [],
|
||||
result = {hosts, {list, {host, string}}}},
|
||||
#ejabberd_commands{name = host_register, tags = [hosts],
|
||||
desc = "Register and start a virtual host",
|
||||
module = ?MODULE, function = register,
|
||||
args = [{host, string}], result = {res, rescode}},
|
||||
#ejabberd_commands{name = host_remove, tags = [hosts],
|
||||
desc = "Stop and remove a virtual host",
|
||||
module = ?MODULE, function = remove,
|
||||
args = [{host, string}], result = {res, rescode}}
|
||||
].
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user