Compare commits
1436 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c942e31e0 | |||
| 9912e1b548 | |||
| 09a5fafc8a | |||
| 64036bc121 | |||
| 33973677f2 | |||
| c4c0b45ced | |||
| 4e9c81b66a | |||
| 7a53b66e46 | |||
| d5ebef1362 | |||
| 26b814e367 | |||
| f675ae1e8c | |||
| b03828d2f7 | |||
| df2e050780 | |||
| 089331caf0 | |||
| db834ca421 | |||
| 5594e1470f | |||
| f7076f49b6 | |||
| 5c40d38b69 | |||
| e2763b7d11 | |||
| 80188d3a52 | |||
| 6575f899b6 | |||
| 3cd9cd0e36 | |||
| 576dda5c87 | |||
| 35a71fc493 | |||
| 1a3abf15c0 | |||
| c7c59766b2 | |||
| e681f4d4cf | |||
| b2a6eccded | |||
| 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):
|
||||
|
||||
|
||||
+1
-2
@@ -1,5 +1,3 @@
|
||||
# $Id$
|
||||
|
||||
SHELL = /bin/bash
|
||||
|
||||
CONTRIBUTED_MODULES = ""
|
||||
@@ -37,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
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
@author Mickael Remond <mickael.remond@process-one.net>
|
||||
[http://www.process-one.net/]
|
||||
@copyright 2013 ProcessOne
|
||||
@version {@vsn}, {@date} {@time}
|
||||
@title ejabberd Development API Documentation
|
||||
|
||||
@doc
|
||||
== Introduction ==
|
||||
|
||||
TODO: Insert content from Jerome documentation.
|
||||
-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.12 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.12 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.12 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.12 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>
|
||||
-4523
File diff suppressed because it is too large
Load Diff
+431
-347
File diff suppressed because it is too large
Load Diff
@@ -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,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/
|
||||
@@ -1,67 +0,0 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.12
|
||||
|
||||
ejabberd 2.1.12 includes a many bugfixes and a few improvements.
|
||||
|
||||
Read more details about the changes in:
|
||||
http://redir.process-one.net/ejabberd-2.1.12
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The changes are:
|
||||
|
||||
* Core ejabberd
|
||||
- Make terms serialization faster
|
||||
- Reduce size of XML stream state
|
||||
|
||||
* Administration
|
||||
- Add SCRAM and remove MD5 support to ejabberd commands auth verification
|
||||
- Added command to list all the vhosts registered in an ejabberd node
|
||||
- Added export2odbc command, copied from mod_admin_extra.erl
|
||||
- Fix ejabberdctl number of arguments error report with R15
|
||||
- Check node name is available before starting ejabberd (EJAB-1572)
|
||||
- Fix ejabberd_xmlrpc commands authentication with SCRAM
|
||||
- Fix mod_offline:store_offline_msg argument (EJAB-1581)
|
||||
- Log IP address when auth attempt fails
|
||||
- Make sure update_info returns atoms only (EJAB-1595)
|
||||
- On shutdown, first stop listeners, then modules
|
||||
|
||||
* Encryption
|
||||
- Detect OpenSSL version at runtime, not at compile time
|
||||
- Fixed signedness issue in tls_drv GET_DESCRYPTED_INPUT (EJAB-1591)
|
||||
- Enable DHE key exchange in TLS driver
|
||||
- Enable ECDHE key exchange in TSL driver
|
||||
- Disable old and unsecure ciphers in TLS driver
|
||||
- Disable SSL 2.0 in TLS driver
|
||||
|
||||
* HTTP-Bind
|
||||
- Do not trigger item-not-found errors in mod_http_bind
|
||||
- Repeated http-bind request should abort only requests with same rid
|
||||
- Receiving missing request shouldn't close waiting out-ouf-order request
|
||||
|
||||
* XMPP
|
||||
- Allow multiple fqdn values in configuration (EJAB-1578)
|
||||
- Fix get_subscription_lists/4
|
||||
- Fix account registration
|
||||
- Send announce Message stanzas as Headline type instead of Normal
|
||||
|
||||
* Other
|
||||
- Guide: Fix file name of Name Service Switch
|
||||
- Guide: Document the db_type modules option (EJAB-1560)
|
||||
- LDAP: Fix broken JPEG photo (EJAB-1526)
|
||||
- LDAP: Fix compatibility with Erlang R16A (EJAB-1612)
|
||||
- MUC: Fix angle brackets handle in plaintext log (EJAB-1610)
|
||||
- MUC: Fix MUC start when Mnesia tables don't exist yet
|
||||
- MUC: New mod_muc_log option file_permissions (EJAB-1588)
|
||||
- ODBC: Merge SQL and Mnesia code into one module (EJAB-1560)
|
||||
- Translation: New Hebrew
|
||||
- Translation: Update Slovak
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -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}{2.1.12}
|
||||
@@ -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
|
||||
+104
-23
@@ -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 +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
|
||||
@@ -74,11 +64,16 @@ ifeq (@pam@, pam)
|
||||
INSTALL_EPAM=install -m 750 $(O_USER) epam $(PBINDIR)
|
||||
endif
|
||||
|
||||
ifeq (@flash_hack@, true)
|
||||
ERLC_FLAGS+=-DENABLE_FLASH_HACK
|
||||
CPPFLAGS+=-DENABLE_FLASH_HACK
|
||||
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
|
||||
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)
|
||||
@@ -154,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,,'`; \
|
||||
@@ -168,17 +163,15 @@ mostlyclean-recursive maintainer-clean-recursive:
|
||||
@ERLC@ -W $(EFLAGS) $*.erl
|
||||
|
||||
$(ERLSHLIBS): %.so: %.c
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) \
|
||||
$(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
|
||||
#
|
||||
@@ -261,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) \
|
||||
@@ -295,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
|
||||
@@ -305,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
|
||||
@@ -314,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
-23
@@ -52,9 +52,6 @@ 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
|
||||
@@ -72,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
|
||||
@@ -85,17 +79,15 @@ 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
|
||||
@@ -108,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
|
||||
@@ -127,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
|
||||
@@ -137,8 +125,6 @@ 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
|
||||
@@ -151,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
|
||||
@@ -164,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,
|
||||
|
||||
+121
-31
@@ -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
|
||||
@@ -32,13 +32,45 @@
|
||||
add/3,
|
||||
add_list/3,
|
||||
match_rule/3,
|
||||
for_host/1,
|
||||
% for debugging only
|
||||
match_acl/3]).
|
||||
|
||||
-include("ejabberd.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}).
|
||||
|
||||
%% @spec () -> ok
|
||||
|
||||
start() ->
|
||||
mnesia:create_table(acl,
|
||||
[{disc_copies, [node()]},
|
||||
@@ -47,9 +79,20 @@ start() ->
|
||||
mnesia:add_table_copy(acl, node(), ram_copies),
|
||||
ok.
|
||||
|
||||
%% @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)}.
|
||||
|
||||
%% @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() ->
|
||||
mnesia:write(#acl{aclname = {ACLName, Host},
|
||||
@@ -57,6 +100,11 @@ add(Host, ACLName, ACLSpec) ->
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
%% @spec (Host, ACLs, Clear) -> ok | false
|
||||
%% Host = global | string()
|
||||
%% ACLs = [acl()]
|
||||
%% Clear = bool()
|
||||
|
||||
add_list(Host, ACLs, Clear) ->
|
||||
F = fun() ->
|
||||
if
|
||||
@@ -86,8 +134,17 @@ add_list(Host, ACLs, Clear) ->
|
||||
false
|
||||
end.
|
||||
|
||||
normalize(A) ->
|
||||
jlib:nodeprep(A).
|
||||
%% @spec (String) -> Prepd_String
|
||||
%% String = string()
|
||||
%% Prepd_String = string()
|
||||
|
||||
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}) ->
|
||||
@@ -99,6 +156,12 @@ normalize_spec(none) ->
|
||||
|
||||
|
||||
|
||||
%% @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;
|
||||
@@ -143,23 +206,37 @@ match_rule(Host, Rule, JID) ->
|
||||
end
|
||||
end.
|
||||
|
||||
%% @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, ACL} | ACLs], JID, Host) ->
|
||||
case match_acl(ACL, JID, Host) of
|
||||
match_acls([{Access, ACLName} | ACLs], JID, Host) ->
|
||||
case match_acl(ACLName, JID, Host) of
|
||||
true ->
|
||||
Access;
|
||||
_ ->
|
||||
match_acls(ACLs, JID, Host)
|
||||
end.
|
||||
|
||||
match_acl(ACL, JID, Host) ->
|
||||
case ACL of
|
||||
%% @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, Server, Resource} = jlib:jid_tolower(JID),
|
||||
lists:any(fun(#acl{aclspec = Spec}) ->
|
||||
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;
|
||||
@@ -168,24 +245,26 @@ match_acl(ACL, JID, Host) ->
|
||||
andalso
|
||||
((Host == Server) orelse
|
||||
((Host == global) andalso
|
||||
lists:member(Server, ?MYHOSTS)));
|
||||
?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
|
||||
lists:member(Server, ?MYHOSTS)))
|
||||
?IS_MY_HOST(Server)))
|
||||
andalso is_regexp_match(User, UR);
|
||||
{shared_group, G} ->
|
||||
Mod = loaded_shared_roster_module(Host),
|
||||
Mod:is_user_in_group({User, Server}, G, Host);
|
||||
mod_shared_roster: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);
|
||||
mod_shared_roster:is_user_in_group({User, Server}, G, H);
|
||||
{user_regexp, UR, S} ->
|
||||
(S == Server) andalso
|
||||
is_regexp_match(User, UR);
|
||||
@@ -199,7 +278,7 @@ match_acl(ACL, JID, Host) ->
|
||||
{user_glob, UR} ->
|
||||
((Host == Server) orelse
|
||||
((Host == global) andalso
|
||||
lists:member(Server, ?MYHOSTS)))
|
||||
?IS_MY_HOST(Server)))
|
||||
andalso
|
||||
is_glob_match(User, UR);
|
||||
{user_glob, UR, S} ->
|
||||
@@ -220,30 +299,41 @@ match_acl(ACL, JID, Host) ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
ets:lookup(acl, {ACL, global}) ++
|
||||
ets:lookup(acl, {ACL, Host}))
|
||||
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) ->
|
||||
case ejabberd_regexp:run(String, RegExp) of
|
||||
try re:run(String, RegExp, [{capture, none}]) of
|
||||
nomatch ->
|
||||
false;
|
||||
match ->
|
||||
true;
|
||||
{error, ErrDesc} ->
|
||||
true
|
||||
catch
|
||||
_:ErrDesc ->
|
||||
?ERROR_MSG(
|
||||
"Wrong regexp ~p in ACL: ~p",
|
||||
"Wrong regexp ~p in ACL:~n~p",
|
||||
[RegExp, ErrDesc]),
|
||||
false
|
||||
end.
|
||||
|
||||
is_glob_match(String, Glob) ->
|
||||
is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)).
|
||||
%% @spec (String, Glob) -> bool()
|
||||
%% String = string() | undefined
|
||||
%% Glob = string()
|
||||
|
||||
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.
|
||||
is_glob_match(String, Glob) ->
|
||||
is_regexp_match(String, xmerl_regexp:sh_to_awk(Glob)).
|
||||
|
||||
|
||||
for_host(Host) ->
|
||||
mnesia:select(acl,
|
||||
ets:fun2ms(fun (#acl{aclname = {_ACLName, H}})
|
||||
when H =:= Host ->
|
||||
object()
|
||||
end)).
|
||||
|
||||
+46
-43
@@ -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
|
||||
@@ -31,48 +31,52 @@
|
||||
produce_response/2,
|
||||
produce_response/1]).
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("adhoc.hrl").
|
||||
|
||||
%% Parse an ad-hoc request. Return either an adhoc_request record or
|
||||
%% an {error, ErrorType} tuple.
|
||||
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),
|
||||
{xmlelement, _, _, AllEls} = SubEl,
|
||||
Others = case XData of
|
||||
false ->
|
||||
AllEls;
|
||||
_ ->
|
||||
lists:delete(XData, AllEls)
|
||||
end,
|
||||
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,
|
||||
|
||||
#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({xmlelement, _Name, _Attrs, SubEls}) ->
|
||||
find_xdata_el(#xmlel{children = SubEls}) ->
|
||||
find_xdata_el1(SubEls).
|
||||
|
||||
find_xdata_el1([]) ->
|
||||
false;
|
||||
find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_XDATA ->
|
||||
{xmlelement, Name, Attrs, SubEls};
|
||||
_ ->
|
||||
find_xdata_el1(Els)
|
||||
end;
|
||||
find_xdata_el1([#xmlel{ns = ?NS_DATA_FORMS} = El | _Els]) ->
|
||||
El;
|
||||
find_xdata_el1([_ | Els]) ->
|
||||
find_xdata_el1(Els).
|
||||
|
||||
@@ -110,20 +114,19 @@ produce_response(#adhoc_response{lang = _Lang,
|
||||
"" ->
|
||||
ActionsElAttrs = [];
|
||||
_ ->
|
||||
ActionsElAttrs = [{"execute", DefaultAction}]
|
||||
ActionsElAttrs = [?XMLATTR(<<"execute">>, DefaultAction)]
|
||||
end,
|
||||
ActionsEls = [{xmlelement, "actions",
|
||||
ActionsElAttrs,
|
||||
[{xmlelement, Action, [], []} || Action <- Actions]}]
|
||||
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}) ->
|
||||
{xmlelement, "note",
|
||||
[{"type", Type}],
|
||||
[{xmlcdata, Text}]}
|
||||
#xmlel{ns = ?NS_ADHOC, name = 'note', attrs =
|
||||
[?XMLATTR(<<"type">>, Type)],
|
||||
children = [#xmlcdata{cdata = list_to_binary(Text)}]}
|
||||
end, Notes),
|
||||
{xmlelement, "command",
|
||||
[{"xmlns", ?NS_COMMANDS},
|
||||
{"sessionid", SessionID},
|
||||
{"node", Node},
|
||||
{"status", atom_to_list(Status)}],
|
||||
#xmlel{ns = ?NS_ADHOC, name = 'command', attrs =
|
||||
[?XMLATTR(<<"sessionid">>, SessionID),
|
||||
?XMLATTR(<<"node">>, Node),
|
||||
?XMLATTR(<<"status">>, Status)], children =
|
||||
ActionsEls ++ NotesEls ++ Elements}.
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+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 erlang:system_info(logical_processors) of
|
||||
unknown ->
|
||||
1;
|
||||
Num ->
|
||||
Num
|
||||
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
-6317
File diff suppressed because it is too large
Load Diff
+19
-22
@@ -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,7 +28,6 @@ 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)
|
||||
@@ -41,10 +36,6 @@ 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
|
||||
@@ -67,6 +58,15 @@ AC_ARG_ENABLE(roster_gateway_workaround,
|
||||
esac],[roster_gateway_workaround=false])
|
||||
AC_SUBST(roster_gateway_workaround)
|
||||
|
||||
AC_ARG_ENABLE(flash_hack,
|
||||
[AC_HELP_STRING([--enable-flash-hack], [support Adobe Flash client XML (default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) flash_hack=true ;;
|
||||
no) flash_hack=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-flash-hack) ;;
|
||||
esac],[flash_hack=false])
|
||||
AC_SUBST(flash_hack)
|
||||
|
||||
AC_ARG_ENABLE(mssql,
|
||||
[AC_HELP_STRING([--enable-mssql], [use Microsoft SQL Server database (default: no, requires --enable-odbc)])],
|
||||
[case "${enableval}" in
|
||||
@@ -94,28 +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_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
|
||||
@@ -179,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
|
||||
|
||||
+1
-15
@@ -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,
|
||||
@@ -63,7 +55,6 @@ start() ->
|
||||
%% Load the ejabberd application description so that ?VERSION can read the vsn key
|
||||
application:load(ejabberd),
|
||||
Version = "EJABBERD_VERSION = " ++ ?VERSION ++ "\n",
|
||||
ExpatDir = "EXPAT_DIR = c:\\sdk\\Expat-2.0.0\n",
|
||||
OpenSSLDir = "OPENSSL_DIR = c:\\sdk\\OpenSSL\n",
|
||||
DBType = "DBTYPE = generic\n", %% 'generic' or 'mssql'
|
||||
|
||||
@@ -79,11 +70,6 @@ start() ->
|
||||
StdLibDir ++
|
||||
OpenSSLDir ++
|
||||
DBType ++
|
||||
ExpatDir ++
|
||||
ExpatLib ++
|
||||
ExpatFlag ++
|
||||
IconvDir ++
|
||||
IconvLib ++
|
||||
ZlibDir ++
|
||||
ZlibLib)),
|
||||
halt().
|
||||
|
||||
+158
-53
@@ -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
|
||||
@@ -30,24 +30,46 @@
|
||||
-export([start/0,
|
||||
register_mechanism/3,
|
||||
listmech/1,
|
||||
server_new/7,
|
||||
server_new/8,
|
||||
server_start/3,
|
||||
server_step/2]).
|
||||
|
||||
-include("cyrsasl.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
%% @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, module, password_type}).
|
||||
-record(sasl_state, {service, myname, realm,
|
||||
get_password, check_password, check_password_digest,
|
||||
mech_mod, mech_state}).
|
||||
|
||||
%% @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.
|
||||
|
||||
-record(sasl_state, {service, myname,
|
||||
mech_mod, mech_state, params}).
|
||||
|
||||
-export([behaviour_info/1]).
|
||||
|
||||
%% @hidden
|
||||
|
||||
behaviour_info(callbacks) ->
|
||||
[{mech_new, 4}, {mech_step, 2}];
|
||||
[{mech_new, 1}, {mech_step, 2}];
|
||||
behaviour_info(_Other) ->
|
||||
undefined.
|
||||
|
||||
%% @spec () -> ok
|
||||
|
||||
start() ->
|
||||
ets:new(sasl_mechanism, [named_table,
|
||||
public,
|
||||
@@ -56,49 +78,81 @@ start() ->
|
||||
cyrsasl_digest:start([]),
|
||||
cyrsasl_scram:start([]),
|
||||
cyrsasl_anonymous:start([]),
|
||||
maybe_try_start_gssapi(),
|
||||
ok.
|
||||
|
||||
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,
|
||||
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 = xml:get_attr_s(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 (Host) -> [Mechanism]
|
||||
%% Host = string()
|
||||
%% Mechanism = string()
|
||||
|
||||
listmech(Host) ->
|
||||
Mechs = ets:select(sasl_mechanism,
|
||||
[{#sasl_mechanism{mechanism = '$1',
|
||||
@@ -118,35 +172,79 @@ listmech(Host) ->
|
||||
['$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) ->
|
||||
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,
|
||||
realm = UserRealm,
|
||||
get_password = GetPassword,
|
||||
check_password = CheckPassword,
|
||||
check_password_digest= CheckPasswordDigest}.
|
||||
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),
|
||||
{ok, MechState} =
|
||||
Module:mech_new(State#sasl_state.params),
|
||||
server_step(State#sasl_state{mech_mod = Module,
|
||||
mech_state = MechState},
|
||||
ClientIn);
|
||||
_ ->
|
||||
{error, "no-mechanism"}
|
||||
{error, 'invalid-mechanism'}
|
||||
end;
|
||||
false ->
|
||||
{error, "no-mechanism"}
|
||||
{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,
|
||||
@@ -168,14 +266,21 @@ server_step(State, ClientIn) ->
|
||||
{continue, ServerOut, NewMechState} ->
|
||||
{continue, ServerOut,
|
||||
State#sasl_state{mech_state = NewMechState}};
|
||||
{error, Error, Username} ->
|
||||
{error, Error, Username};
|
||||
{error, Error, Text, Username} ->
|
||||
{error, Error, Text, Username};
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
%% Remove the anonymous mechanism from the list if not enabled for the given
|
||||
%% host
|
||||
%% @spec (Host, Mechs) -> [Filtered_Mechs]
|
||||
%% Host = string()
|
||||
%% Mechs = [Mech]
|
||||
%% Mech = string()
|
||||
%% Filtered_Mechs = [Mech]
|
||||
%%
|
||||
%% @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;
|
||||
|
||||
@@ -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}).
|
||||
@@ -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,22 +27,42 @@
|
||||
|
||||
-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).
|
||||
|
||||
%% @type mechstate() = {state, Server}
|
||||
%% Server = string().
|
||||
|
||||
-record(state, {server}).
|
||||
|
||||
%% @spec (Opts) -> true
|
||||
%% Opts = term()
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("ANONYMOUS", ?MODULE, plain),
|
||||
ok.
|
||||
|
||||
%% @spec () -> ok
|
||||
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
|
||||
mech_new(#sasl_params{host=Host}) ->
|
||||
{ok, #state{server = Host}}.
|
||||
|
||||
%% @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())]),
|
||||
@@ -50,7 +70,7 @@ mech_step(State, _ClientIn) ->
|
||||
|
||||
%% Checks that the username is available
|
||||
case ejabberd_auth:is_user_exists(User, Server) of
|
||||
true -> {error, "not-authorized"};
|
||||
true -> {error, 'not-authorized'};
|
||||
false -> {ok, [{username, User},
|
||||
{auth_module, ejabberd_auth_anonymous}]}
|
||||
end.
|
||||
|
||||
+177
-61
@@ -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
|
||||
@@ -29,32 +29,60 @@
|
||||
|
||||
-export([start/1,
|
||||
stop/0,
|
||||
mech_new/4,
|
||||
mech_new/1,
|
||||
mech_step/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("cyrsasl.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
%% @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().
|
||||
|
||||
-record(state, {step, nonce, username, authzid, get_password, check_password, auth_module,
|
||||
host, hostfqdn}).
|
||||
host}).
|
||||
|
||||
%% @spec (Opts) -> true
|
||||
%% Opts = term()
|
||||
|
||||
start(_Opts) ->
|
||||
Fqdn = get_local_fqdn(),
|
||||
?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~p", [Fqdn]),
|
||||
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, digest).
|
||||
|
||||
%% @spec () -> ok
|
||||
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
mech_new(Host, GetPassword, _CheckPassword, CheckPasswordDigest) ->
|
||||
mech_new(#sasl_params{host=Host, get_password=GetPassword,
|
||||
check_password_digest=CheckPasswordDigest}) ->
|
||||
{ok, #state{step = 1,
|
||||
nonce = randoms:get_string(),
|
||||
host = Host,
|
||||
hostfqdn = get_local_fqdn(),
|
||||
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 ++
|
||||
@@ -63,24 +91,23 @@ mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
|
||||
mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
bad ->
|
||||
{error, "bad-protocol"};
|
||||
{error, 'malformed-request'};
|
||||
KeyVals ->
|
||||
DigestURI = xml:get_attr_s("digest-uri", KeyVals),
|
||||
UserName = xml:get_attr_s("username", KeyVals),
|
||||
case is_digesturi_valid(DigestURI, State#state.host, State#state.hostfqdn) of
|
||||
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 (checking for Host ~p, FQDN ~p)", [DigestURI,
|
||||
State#state.host, State#state.hostfqdn]),
|
||||
{error, "not-authorized", UserName};
|
||||
"seems invalid: ~p", [DigestURI]),
|
||||
{error, 'not-authorized', "", UserName};
|
||||
true ->
|
||||
AuthzId = xml:get_attr_s("authzid", KeyVals),
|
||||
AuthzId = proplists:get_value("authzid", KeyVals, ""),
|
||||
case (State#state.get_password)(UserName) of
|
||||
{false, _} ->
|
||||
{error, "not-authorized", UserName};
|
||||
{error, 'not-authorized', "", UserName};
|
||||
{Passwd, AuthModule} ->
|
||||
case (State#state.check_password)(UserName, "",
|
||||
xml:get_attr_s("response", KeyVals),
|
||||
proplists:get_value("response", KeyVals, ""),
|
||||
fun(PW) -> response(KeyVals, UserName, PW, Nonce, AuthzId,
|
||||
"AUTHENTICATE") end) of
|
||||
{true, _} ->
|
||||
@@ -94,9 +121,9 @@ mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
|
||||
username = UserName,
|
||||
authzid = AuthzId}};
|
||||
false ->
|
||||
{error, "not-authorized", UserName};
|
||||
{error, 'not-authorized', "", UserName};
|
||||
{false, _} ->
|
||||
{error, "not-authorized", UserName}
|
||||
{error, 'not-authorized', "", UserName}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -109,11 +136,18 @@ mech_step(#state{step = 5,
|
||||
{auth_module, AuthModule}]};
|
||||
mech_step(A, B) ->
|
||||
?DEBUG("SASL DIGEST: A ~p B ~p", [A,B]),
|
||||
{error, "bad-protocol"}.
|
||||
{error, 'malformed-request'}.
|
||||
|
||||
%% @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) ->
|
||||
@@ -127,6 +161,8 @@ parse1([], [], T) ->
|
||||
parse1([], _S, _T) ->
|
||||
bad.
|
||||
|
||||
%% @hidden
|
||||
|
||||
parse2([$\" | Cs], Key, Val, Ts) ->
|
||||
parse3(Cs, Key, Val, Ts);
|
||||
parse2([C | Cs], Key, Val, Ts) ->
|
||||
@@ -134,6 +170,8 @@ parse2([C | Cs], Key, Val, Ts) ->
|
||||
parse2([], _, _, _) ->
|
||||
bad.
|
||||
|
||||
%% @hidden
|
||||
|
||||
parse3([$\" | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, Val, Ts);
|
||||
parse3([$\\, C | Cs], Key, Val, Ts) ->
|
||||
@@ -143,6 +181,8 @@ parse3([C | Cs], Key, Val, Ts) ->
|
||||
parse3([], _, _, _) ->
|
||||
bad.
|
||||
|
||||
%% @hidden
|
||||
|
||||
parse4([$, | Cs], Key, Val, Ts) ->
|
||||
parse1(Cs, "", [{Key, lists:reverse(Val)} | Ts]);
|
||||
parse4([$\s | Cs], Key, Val, Ts) ->
|
||||
@@ -153,60 +193,46 @@ parse4([], Key, Val, Ts) ->
|
||||
parse1([], "", [{Key, lists:reverse(Val)} | Ts]).
|
||||
|
||||
|
||||
%% @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 acceptable digest-uris would be:
|
||||
%% xmpp/server3.example.org/jabber.example.org, xmpp/server3.example.org and
|
||||
%% xmpp/jabber.example.org
|
||||
%% The last version is not actually allowed by the RFC, but implemented by popular clients
|
||||
is_digesturi_valid(DigestURICase, JabberDomain, JabberFQDN) ->
|
||||
DigestURI = stringprep:tolower(DigestURICase),
|
||||
%% 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] ->
|
||||
IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
|
||||
(Host == JabberDomain) or IsHostFqdn;
|
||||
["xmpp", Host, ServName] ->
|
||||
IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
|
||||
(ServName == JabberDomain) and IsHostFqdn;
|
||||
["xmpp", Host] when Host == JabberHost ->
|
||||
true;
|
||||
["xmpp", _Host, ServName] when ServName == JabberHost ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
is_host_fqdn(Host, [Letter | _Tail] = Fqdn) when not is_list(Letter) ->
|
||||
Host == Fqdn;
|
||||
is_host_fqdn(_Host, []) ->
|
||||
false;
|
||||
is_host_fqdn(Host, [Fqdn | _FqdnTail]) when Host == Fqdn ->
|
||||
true;
|
||||
is_host_fqdn(Host, [Fqdn | FqdnTail]) when Host /= Fqdn ->
|
||||
is_host_fqdn(Host, FqdnTail).
|
||||
|
||||
get_local_fqdn() ->
|
||||
case (catch get_local_fqdn2()) of
|
||||
Str when is_list(Str) -> Str;
|
||||
_ -> "unknown-fqdn, please configure fqdn option in ejabberd.cfg!"
|
||||
end.
|
||||
get_local_fqdn2() ->
|
||||
case ejabberd_config:get_local_option(fqdn) of
|
||||
ConfiguredFqdn when is_list(ConfiguredFqdn) ->
|
||||
ConfiguredFqdn;
|
||||
_undefined ->
|
||||
{ok, Hostname} = inet:gethostname(),
|
||||
{ok, {hostent, Fqdn, _, _, _, _}} = inet:gethostbyname(Hostname),
|
||||
Fqdn
|
||||
end.
|
||||
|
||||
|
||||
%% @hidden
|
||||
|
||||
digit_to_xchar(D) when (D >= 0) and (D < 10) ->
|
||||
D + 48;
|
||||
digit_to_xchar(D) ->
|
||||
D + 87.
|
||||
|
||||
%% @hidden
|
||||
|
||||
hex(S) ->
|
||||
hex(S, []).
|
||||
|
||||
%% @hidden
|
||||
|
||||
hex([], Res) ->
|
||||
lists:reverse(Res);
|
||||
hex([N | Ns], Res) ->
|
||||
@@ -214,20 +240,35 @@ hex([N | Ns], Res) ->
|
||||
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 = xml:get_attr_s("realm", KeyVals),
|
||||
CNonce = xml:get_attr_s("cnonce", KeyVals),
|
||||
DigestURI = xml:get_attr_s("digest-uri", KeyVals),
|
||||
NC = xml:get_attr_s("nc", KeyVals),
|
||||
QOP = xml:get_attr_s("qop", KeyVals),
|
||||
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
|
||||
"" ->
|
||||
binary_to_list(
|
||||
crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
|
||||
crypto:md5(SUser ++ ":" ++ SRealm ++ ":" ++ SPasswd)) ++
|
||||
":" ++ Nonce ++ ":" ++ CNonce;
|
||||
_ ->
|
||||
binary_to_list(
|
||||
crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
|
||||
crypto:md5(SUser ++ ":" ++ SRealm ++ ":" ++ SPasswd)) ++
|
||||
":" ++ Nonce ++ ":" ++ CNonce ++ ":" ++ AuthzId
|
||||
end,
|
||||
A2 = case QOP of
|
||||
@@ -243,4 +284,79 @@ response(KeyVals, User, Passwd, Nonce, AuthzId, A2Prefix) ->
|
||||
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}]}.
|
||||
+32
-5
@@ -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
|
||||
@@ -27,22 +27,43 @@
|
||||
-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),
|
||||
ok.
|
||||
|
||||
%% @spec () -> ok
|
||||
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) ->
|
||||
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] ->
|
||||
@@ -50,11 +71,13 @@ mech_step(State, ClientIn) ->
|
||||
{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}
|
||||
{error, 'not-authorized', "", User}
|
||||
end;
|
||||
_ ->
|
||||
{error, "bad-protocol"}
|
||||
{error, 'malformed-request'}
|
||||
end.
|
||||
|
||||
prepare(ClientIn) ->
|
||||
@@ -76,9 +99,13 @@ prepare(ClientIn) ->
|
||||
end.
|
||||
|
||||
|
||||
%% @hidden
|
||||
|
||||
parse(S) ->
|
||||
parse1(S, "", []).
|
||||
|
||||
%% @hidden
|
||||
|
||||
parse1([0 | Cs], S, T) ->
|
||||
parse1(Cs, "", [lists:reverse(S) | T]);
|
||||
parse1([C | Cs], S, T) ->
|
||||
|
||||
+19
-18
@@ -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
|
||||
@@ -29,10 +29,11 @@
|
||||
|
||||
-export([start/1,
|
||||
stop/0,
|
||||
mech_new/4,
|
||||
mech_new/1,
|
||||
mech_step/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("cyrsasl.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
@@ -48,25 +49,25 @@ start(_Opts) ->
|
||||
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 string:tokens(ClientIn, ",") of
|
||||
[CBind, UserNameAttribute, ClientNonceAttribute] when (CBind == "y") or (CBind == "n") ->
|
||||
case parse_attribute(UserNameAttribute) of
|
||||
{error, Reason} ->
|
||||
{error, Reason} ->
|
||||
{error, Reason};
|
||||
{_, EscapedUserName} ->
|
||||
case unescape_username(EscapedUserName) of
|
||||
error ->
|
||||
{error, "protocol-error-bad-username"};
|
||||
{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};
|
||||
{error, 'not-authorized', "", UserName};
|
||||
{Ret, _AuthModule} ->
|
||||
{StoredKey, ServerKey, Salt, IterationCount} = if
|
||||
is_tuple(Ret) ->
|
||||
@@ -89,12 +90,12 @@ mech_step(#state{step = 2} = State, ClientIn) ->
|
||||
client_nonce = ClientNonce, server_nonce = ServerNonce, username = UserName}}
|
||||
end;
|
||||
_Else ->
|
||||
{error, "not-supported"}
|
||||
{error, 'malformed-request'}
|
||||
end
|
||||
end
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
{error, 'malformed-request'}
|
||||
end;
|
||||
mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
case string:tokens(ClientIn, ",") of
|
||||
@@ -117,21 +118,21 @@ mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
ServerSignature = scram:server_signature(State#state.server_key, AuthMessage),
|
||||
{ok, [{username, State#state.username}], "v=" ++ base64:encode_to_string(ServerSignature)};
|
||||
true ->
|
||||
{error, "bad-auth"}
|
||||
{error, 'not-authorized', "", State#state.username}
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
{error, 'malformed-request', "Bad protocol", State#state.username}
|
||||
end;
|
||||
{$r, _} ->
|
||||
{error, "bad-nonce"};
|
||||
{error, 'malformed-request', "Bad nonce", State#state.username};
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
{error, 'malformed-request', "Bad protocol", State#state.username}
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
{error, 'malformed-request', "Bad protocol", State#state.username}
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
{error, 'malformed-request', "Bad protocol", State#state.username}
|
||||
end.
|
||||
|
||||
parse_attribute(Attribute) ->
|
||||
@@ -144,15 +145,15 @@ parse_attribute(Attribute) ->
|
||||
if
|
||||
SecondChar == $= ->
|
||||
String = string:substr(Attribute, 3),
|
||||
{lists:nth(1, Attribute), String};
|
||||
{lists:nth(1, Attribute), String};
|
||||
true ->
|
||||
{error, "bad-format second char not equal sign"}
|
||||
{error, 'malformed-request', "Second char not equal sign", ""}
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-format first char not a letter"}
|
||||
{error, 'malformed-request', "First char not a letter", ""}
|
||||
end;
|
||||
true ->
|
||||
{error, "bad-format attribute too short"}
|
||||
{error, 'malformed-request', "Attribute too short", ""}
|
||||
end.
|
||||
|
||||
unescape_username("") ->
|
||||
|
||||
+4
-11
@@ -1,8 +1,6 @@
|
||||
%% $Id$
|
||||
|
||||
{application, ejabberd,
|
||||
[{description, "ejabberd"},
|
||||
{vsn, "2.1.12"},
|
||||
{vsn, "3.0.0-alpha-5"},
|
||||
{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,
|
||||
@@ -38,6 +35,7 @@
|
||||
ejabberd_rdbms,
|
||||
ejabberd_receiver,
|
||||
ejabberd_router,
|
||||
ejabberd_router_multicast,
|
||||
ejabberd_s2s,
|
||||
ejabberd_s2s_in,
|
||||
ejabberd_s2s_out,
|
||||
@@ -50,7 +48,6 @@
|
||||
ejabberd_update,
|
||||
ejabberd_web_admin,
|
||||
ejabberd_web,
|
||||
ejabberd_zlib,
|
||||
ejd2odbc,
|
||||
eldap,
|
||||
eldap_filter,
|
||||
@@ -62,7 +59,6 @@
|
||||
gen_mod,
|
||||
gen_pubsub_node,
|
||||
gen_pubsub_nodetree,
|
||||
iconv,
|
||||
idna,
|
||||
jd2ejd,
|
||||
jlib,
|
||||
@@ -75,8 +71,6 @@
|
||||
mod_echo,
|
||||
mod_http_bind,
|
||||
mod_http_fileserver,
|
||||
mod_irc,
|
||||
mod_irc_connection,
|
||||
mod_last,
|
||||
mod_muc,
|
||||
mod_muc_log,
|
||||
@@ -119,13 +113,13 @@
|
||||
tls,
|
||||
translate,
|
||||
xml,
|
||||
xml_stream,
|
||||
'XmppAddr'
|
||||
]},
|
||||
{registered, [ejabberd,
|
||||
ejabberd_sup,
|
||||
ejabberd_auth,
|
||||
ejabberd_router,
|
||||
ejabberd_router_multicast,
|
||||
ejabberd_sm,
|
||||
ejabberd_s2s,
|
||||
ejabberd_local,
|
||||
@@ -138,7 +132,6 @@
|
||||
ejabberd_mod_roster,
|
||||
ejabberd_mod_echo,
|
||||
ejabberd_mod_pubsub,
|
||||
ejabberd_mod_irc,
|
||||
ejabberd_mod_muc,
|
||||
ejabberd_offline,
|
||||
random_generator
|
||||
|
||||
+101
-58
@@ -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"]}.
|
||||
|
||||
%%
|
||||
@@ -213,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
|
||||
@@ -233,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
|
||||
%%
|
||||
@@ -290,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
|
||||
@@ -300,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"
|
||||
%%
|
||||
@@ -457,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
|
||||
|
||||
@@ -473,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.
|
||||
@@ -583,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
|
||||
%%
|
||||
@@ -602,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
|
||||
|
||||
+12
-3
@@ -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
|
||||
@@ -23,7 +23,8 @@
|
||||
%% 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_global_option(hosts)).
|
||||
-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)).
|
||||
|
||||
@@ -31,10 +32,19 @@
|
||||
-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(S2STIMEOUT, 600000).
|
||||
|
||||
-define(PRIVACY_SUPPORT, true).
|
||||
|
||||
%%-define(DBGFSM, true).
|
||||
|
||||
-record(scram, {storedkey, serverkey, salt, iterationcount}).
|
||||
@@ -61,4 +71,3 @@
|
||||
|
||||
-define(CRITICAL_MSG(Format, Args),
|
||||
ejabberd_logger:critical_msg(?MODULE,?LINE,Format, Args)).
|
||||
|
||||
|
||||
+33
-52
@@ -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
|
||||
@@ -31,18 +31,19 @@
|
||||
%% Server
|
||||
status/0, reopen_log/0,
|
||||
stop_kindly/2, send_service_message_all_mucs/2,
|
||||
registered_vhosts/0,
|
||||
%% Erlang
|
||||
update_list/0, update/1,
|
||||
%% Accounts
|
||||
register/3, unregister/2,
|
||||
registered_users/1,
|
||||
%% For debugging gen_storage
|
||||
get_last_info/0,
|
||||
get_last_info/2,
|
||||
%% Migration jabberd1.4
|
||||
import_file/1, import_dir/1,
|
||||
%% Purge DB
|
||||
delete_expired_messages/0, delete_old_messages/1,
|
||||
%% Mnesia
|
||||
export2odbc/2,
|
||||
set_master/1,
|
||||
backup_mnesia/1, restore_mnesia/1,
|
||||
dump_mnesia/1, dump_table/2, load_mnesia/1,
|
||||
@@ -87,10 +88,6 @@ 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}},
|
||||
@@ -129,11 +126,21 @@ commands() ->
|
||||
module = ?MODULE, function = registered_users,
|
||||
args = [{host, string}],
|
||||
result = {users, {list, {username, string}}}},
|
||||
#ejabberd_commands{name = registered_vhosts, tags = [server],
|
||||
desc = "List all registered vhosts in SERVER",
|
||||
module = ?MODULE, function = registered_vhosts,
|
||||
|
||||
#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",
|
||||
@@ -166,17 +173,7 @@ 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 = export2odbc, tags = [mnesia],
|
||||
desc = "Export virtual host information from Mnesia tables to SQL files",
|
||||
module = ?MODULE, function = export2odbc,
|
||||
args = [{host, string}, {directory, string}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = set_master, tags = [mnesia],
|
||||
desc = "Set master node of the clustered Mnesia tables",
|
||||
longdesc = "If you provide as nodename \"self\", this "
|
||||
@@ -264,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,
|
||||
@@ -287,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,
|
||||
@@ -301,7 +297,8 @@ send_service_message_all_mucs(Subject, AnnouncementText) ->
|
||||
fun(ServerHost) ->
|
||||
MUCHost = gen_mod:get_module_opt_host(
|
||||
ServerHost, mod_muc, "conference.@HOST@"),
|
||||
mod_muc:broadcast_service_message(MUCHost, Message)
|
||||
MUCHostB = list_to_binary(MUCHost),
|
||||
mod_muc:broadcast_service_message(MUCHostB, Message)
|
||||
end,
|
||||
?MYHOSTS).
|
||||
|
||||
@@ -354,8 +351,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
|
||||
@@ -387,38 +388,18 @@ 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
|
||||
%%%
|
||||
|
||||
export2odbc(Host, Directory) ->
|
||||
Tables = [{export_last, last},
|
||||
{export_offline, offline},
|
||||
{export_private_storage, private_storage},
|
||||
{export_roster, roster},
|
||||
{export_vcard, vcard},
|
||||
{export_vcard_search, vcard_search},
|
||||
{export_passwd, passwd}],
|
||||
Export = fun({TableFun, Table}) ->
|
||||
Filename = filename:join([Directory, atom_to_list(Table)++".txt"]),
|
||||
io:format("Trying to export Mnesia table '~p' on Host '~s' to file '~s'~n", [Table, Host, Filename]),
|
||||
Res = (catch ejd2odbc:TableFun(Host, Filename)),
|
||||
io:format(" Result: ~p~n", [Res])
|
||||
end,
|
||||
lists:foreach(Export, Tables),
|
||||
ok.
|
||||
|
||||
set_master("self") ->
|
||||
set_master(node());
|
||||
set_master(NodeString) when is_list(NodeString) ->
|
||||
|
||||
+23
-17
@@ -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(),
|
||||
@@ -67,7 +66,10 @@ start(normal, _Args) ->
|
||||
%ejabberd_debug:eprof_start(),
|
||||
%ejabberd_debug:fprof_start(),
|
||||
maybe_add_nameservers(),
|
||||
print_start_debug_info(),
|
||||
start_modules(),
|
||||
ejabberd_cluster:announce(),
|
||||
ejabberd_node_groups:start(),
|
||||
ejabberd_listener:start_listeners(),
|
||||
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
|
||||
Sup;
|
||||
@@ -78,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_listener:stop_listeners(),
|
||||
stop_modules(),
|
||||
ejabberd_admin:stop(),
|
||||
broadcast_c2s_shutdown(),
|
||||
@@ -107,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
|
||||
@@ -133,6 +122,15 @@ 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) ->
|
||||
case ejabberd_config:get_local_option({modules, Host}) of
|
||||
@@ -240,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.
|
||||
|
||||
+235
-87
@@ -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
|
||||
@@ -49,34 +49,74 @@
|
||||
is_user_exists_in_other_modules/3,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
remove_host/1,
|
||||
plain_password_required/1,
|
||||
store_type/1,
|
||||
entropy/1
|
||||
]).
|
||||
|
||||
-export([start/1
|
||||
,stop/1
|
||||
,start_modules/2
|
||||
,stop_modules/2
|
||||
]).
|
||||
|
||||
-export([auth_modules/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
%% @type authmodule() = ejabberd_auth_anonymous | ejabberd_auth_external |
|
||||
%% ejabberd_auth_ldap | ejabberd_auth_pam |
|
||||
%% ejabberd_auth_storage | atom().
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%% @spec () -> term()
|
||||
|
||||
start() ->
|
||||
?DEBUG("About to start auth modules. Hosts: ~p", [?MYHOSTS]),
|
||||
lists:foreach(fun start/1, ?MYHOSTS).
|
||||
|
||||
start(Host) ->
|
||||
ejabberd_hooks:add(remove_host, list_to_binary(Host), ?MODULE, remove_host, 150),
|
||||
start_modules(Host, auth_modules(Host)).
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(remove_host, list_to_binary(Host), ?MODULE, remove_host, 150),
|
||||
stop_modules(Host, auth_modules(Host)).
|
||||
|
||||
remove_host(HostB) when is_binary(HostB) ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
lists:foreach(
|
||||
fun(M) ->
|
||||
M:start(Host)
|
||||
end, auth_modules(Host))
|
||||
end, ?MYHOSTS).
|
||||
fun({Username, Host}) ->
|
||||
ejabberd_auth:remove_user(Username, Host)
|
||||
end,
|
||||
ejabberd_auth:get_vh_registered_users(binary_to_list(HostB))).
|
||||
|
||||
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).
|
||||
|
||||
%% @spec (Server) -> bool()
|
||||
%% Server = string()
|
||||
|
||||
%% This is only executed by ejabberd_c2s for non-SASL auth client
|
||||
plain_password_required(Server) ->
|
||||
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) ->
|
||||
@@ -92,61 +132,92 @@ store_type(Server) ->
|
||||
M:store_type()
|
||||
end, plain, auth_modules(Server)).
|
||||
|
||||
%% @spec (User, Server, Password) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% @doc Check if the user and password can login in server.
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% true | false
|
||||
check_password(User, Server, Password) ->
|
||||
|
||||
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 -> false
|
||||
{false, _Reason} -> false
|
||||
end.
|
||||
|
||||
%% @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.
|
||||
%% @spec (User::string(), Server::string(), Password::string(),
|
||||
%% Digest::string(), DigestGen::function()) ->
|
||||
%% true | false
|
||||
check_password(User, Server, Password, Digest, DigestGen) ->
|
||||
|
||||
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 -> false
|
||||
{false, _Reason} -> false
|
||||
end.
|
||||
|
||||
%% @doc Check if the user and password can login in server.
|
||||
%% The user can login if at least an authentication method accepts the user
|
||||
%% and the password.
|
||||
%% The first authentication method that accepts the credentials is returned.
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% {true, AuthModule} | false
|
||||
%% {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
|
||||
check_password_with_authmodule(User, Server, Password) ->
|
||||
check_password_loop(auth_modules(Server), [User, Server, Password]).
|
||||
%% @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, Digest, DigestGen) ->
|
||||
%% @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
|
||||
|
||||
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]).
|
||||
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, Reason} when Reason /= "" ->
|
||||
check_password_loop(AuthModules, Args, Reason);
|
||||
{false, ""} ->
|
||||
check_password_loop(AuthModules, Args, PreviousReason);
|
||||
false ->
|
||||
check_password_loop(AuthModules, Args)
|
||||
check_password_loop(AuthModules, Args, PreviousReason)
|
||||
end.
|
||||
|
||||
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% ok | {error, ErrorType}
|
||||
%% where ErrorType = empty_password | not_allowed | invalid_jid
|
||||
%% @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) ->
|
||||
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);
|
||||
@@ -155,49 +226,71 @@ set_password(User, Server, Password) ->
|
||||
end, {error, not_allowed}, auth_modules(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) ->
|
||||
case is_user_exists(User,Server) of
|
||||
{error, not_allowed};
|
||||
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 ->
|
||||
case lists:member(jlib:nameprep(Server), ?MYHOSTS) of
|
||||
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)),
|
||||
case Res of
|
||||
{atomic, ok} ->
|
||||
ejabberd_hooks:run(register_user, Server,
|
||||
[User, Server]),
|
||||
{atomic, ok};
|
||||
_ -> Res
|
||||
end;
|
||||
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.
|
||||
|
||||
%% Registered users list do not include anonymous users logged
|
||||
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()).
|
||||
|
||||
%% Registered users list do not include anonymous users logged
|
||||
get_vh_registered_users(Server) ->
|
||||
%% @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) when is_list(Server) ->
|
||||
lists:flatmap(
|
||||
fun(M) ->
|
||||
M:get_vh_registered_users(Server)
|
||||
end, auth_modules(Server)).
|
||||
|
||||
get_vh_registered_users(Server, Opts) ->
|
||||
%% @spec (Server, Opts) -> [{LUser, LServer}]
|
||||
%% Server = string()
|
||||
%% Opts = [{Opt, Val}]
|
||||
%% Opt = atom()
|
||||
%% Val = term()
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
|
||||
get_vh_registered_users(Server, Opts) when is_list(Server) ->
|
||||
lists:flatmap(
|
||||
fun(M) ->
|
||||
case erlang:function_exported(
|
||||
@@ -209,7 +302,11 @@ get_vh_registered_users(Server, Opts) ->
|
||||
end
|
||||
end, auth_modules(Server)).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
%% @spec (Server) -> Users_Number
|
||||
%% Server = string()
|
||||
%% Users_Number = integer()
|
||||
|
||||
get_vh_registered_users_number(Server) when is_list(Server) ->
|
||||
lists:sum(
|
||||
lists:map(
|
||||
fun(M) ->
|
||||
@@ -222,7 +319,14 @@ get_vh_registered_users_number(Server) ->
|
||||
end
|
||||
end, auth_modules(Server))).
|
||||
|
||||
get_vh_registered_users_number(Server, Opts) ->
|
||||
%% @spec (Server, Opts) -> Users_Number
|
||||
%% Server = string()
|
||||
%% Opts = [{Opt, Val}]
|
||||
%% Opt = atom()
|
||||
%% Val = term()
|
||||
%% Users_Number = integer()
|
||||
|
||||
get_vh_registered_users_number(Server, Opts) when is_list(Server) ->
|
||||
lists:sum(
|
||||
lists:map(
|
||||
fun(M) ->
|
||||
@@ -235,9 +339,13 @@ get_vh_registered_users_number(Server, Opts) ->
|
||||
end
|
||||
end, auth_modules(Server))).
|
||||
|
||||
%% @spec (User, Server) -> Password | false
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
%% @doc Get the password of the user.
|
||||
%% @spec (User::string(), Server::string()) -> Password::string()
|
||||
get_password(User, Server) ->
|
||||
|
||||
get_password(User, Server) when is_list(User), is_list(Server) ->
|
||||
lists:foldl(
|
||||
fun(M, false) ->
|
||||
M:get_password(User, Server);
|
||||
@@ -245,7 +353,13 @@ get_password(User, Server) ->
|
||||
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 ->
|
||||
"";
|
||||
@@ -255,10 +369,15 @@ get_password_s(User, Server) ->
|
||||
""
|
||||
end.
|
||||
|
||||
%% @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.
|
||||
%% @spec (User::string(), Server::string()) ->
|
||||
%% {Password::string(), AuthModule::atom()} | {false, none}
|
||||
get_password_with_authmodule(User, 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};
|
||||
@@ -266,9 +385,13 @@ get_password_with_authmodule(User, Server) ->
|
||||
{Password, AuthModule}
|
||||
end, {false, none}, auth_modules(Server)).
|
||||
|
||||
%% Returns true if the user exists in the DB or if an anonymous user is logged
|
||||
%% under the given name
|
||||
is_user_exists(User, Server) ->
|
||||
%% @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) when is_list(User), is_list(Server) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
case M:is_user_exists(User, Server) of
|
||||
@@ -283,10 +406,15 @@ is_user_exists(User, Server) ->
|
||||
end
|
||||
end, auth_modules(Server)).
|
||||
|
||||
%% Check if the user exists in all authentications module except the module
|
||||
%% passed as parameter
|
||||
%% @spec (Module::atom(), User, Server) -> true | false | maybe
|
||||
is_user_exists_in_other_modules(Module, User, Server) ->
|
||||
%% @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)
|
||||
when is_list(User), is_list(Server) ->
|
||||
is_user_exists_in_other_modules_loop(
|
||||
auth_modules(Server)--[Module],
|
||||
User, Server).
|
||||
@@ -306,23 +434,34 @@ is_user_exists_in_other_modules_loop([AuthModule|AuthModules], User, Server) ->
|
||||
end.
|
||||
|
||||
|
||||
%% @spec (User, Server) -> ok
|
||||
%% @spec (User, Server) -> ok | error | {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% @doc Remove user.
|
||||
%% Note: it may return ok even if there was some problem removing the user.
|
||||
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.
|
||||
%% 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'
|
||||
|
||||
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.
|
||||
|
||||
%% @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) ->
|
||||
|
||||
remove_user(User, Server, Password)
|
||||
when is_list(User), is_list(Server), is_list(Password) ->
|
||||
R = lists:foldl(
|
||||
fun(_M, ok = Res) ->
|
||||
Res;
|
||||
@@ -330,7 +469,8 @@ remove_user(User, Server, Password) ->
|
||||
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]);
|
||||
ok -> ejabberd_hooks:run(remove_user, list_to_binary(exmpp_stringprep:nameprep(Server)),
|
||||
[list_to_binary(User), list_to_binary(Server)]);
|
||||
_ -> none
|
||||
end,
|
||||
R.
|
||||
@@ -362,8 +502,11 @@ entropy(IOList) ->
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
%% Return the lists of all the auth modules actually used in the
|
||||
%% configuration
|
||||
|
||||
%% @spec () -> [authmodule()]
|
||||
%% @doc Return the lists of all the auth modules actually used in the
|
||||
%% configuration.
|
||||
|
||||
auth_modules() ->
|
||||
lists:usort(
|
||||
lists:flatmap(
|
||||
@@ -371,13 +514,18 @@ auth_modules() ->
|
||||
auth_modules(Server)
|
||||
end, ?MYHOSTS)).
|
||||
|
||||
%% Return the list of authenticated modules for a given host
|
||||
%% @spec (Server) -> [authmodule()]
|
||||
%% Server = string()
|
||||
%% @doc Return the list of authenticated modules for a given host.
|
||||
|
||||
auth_modules(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
Method = ejabberd_config:get_local_option({auth_method, LServer}),
|
||||
Method = ejabberd_config:get_local_option({auth_method, Server}),
|
||||
Methods = if
|
||||
Method == undefined -> [];
|
||||
is_list(Method) -> Method;
|
||||
is_atom(Method) -> [Method]
|
||||
end,
|
||||
[list_to_atom("ejabberd_auth_" ++ atom_to_list(M)) || M <- Methods].
|
||||
[module_name(M) || M <- Methods].
|
||||
|
||||
module_name(Method) when is_atom(Method) ->
|
||||
list_to_atom("ejabberd_auth_" ++ atom_to_list(Method)).
|
||||
|
||||
+171
-51
@@ -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
|
||||
@@ -28,6 +28,7 @@
|
||||
-author('mickael.remond@process-one.net').
|
||||
|
||||
-export([start/1,
|
||||
stop/1,
|
||||
allow_anonymous/1,
|
||||
is_sasl_anonymous_enabled/1,
|
||||
is_login_anonymous_enabled/1,
|
||||
@@ -54,31 +55,54 @@
|
||||
store_type/0,
|
||||
plain_password_required/0]).
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-record(anonymous, {us, sid}).
|
||||
|
||||
%% Create the anonymous table if at least one virtual host has anonymous features enabled
|
||||
%% Register to login / logout events
|
||||
start(Host) ->
|
||||
%% @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.
|
||||
|
||||
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},
|
||||
{attributes, record_info(fields, anonymous)}]),
|
||||
{type, bag}, {local_content, true},
|
||||
{attributes, record_info(fields, anonymous)}]),
|
||||
mnesia:add_table_copy(anonymous, node(), ram_copies),
|
||||
%% The hooks are needed to add / remove users from the anonymous tables
|
||||
ejabberd_hooks:add(sm_register_connection_hook, Host,
|
||||
ejabberd_hooks:add(sm_register_connection_hook, HostB,
|
||||
?MODULE, register_connection, 100),
|
||||
ejabberd_hooks:add(sm_remove_connection_hook, Host,
|
||||
ejabberd_hooks:add(sm_remove_connection_hook, HostB,
|
||||
?MODULE, unregister_connection, 100),
|
||||
ok.
|
||||
|
||||
%% Return true if anonymous is allowed for host or false otherwise
|
||||
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)).
|
||||
|
||||
%% 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) ->
|
||||
%% @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 ->
|
||||
@@ -89,10 +113,13 @@ is_sasl_anonymous_enabled(Host) ->
|
||||
end
|
||||
end.
|
||||
|
||||
%% Return true if anonymous login is enabled on the server
|
||||
%% @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) ->
|
||||
|
||||
is_login_anonymous_enabled(Host) when is_list(Host) ->
|
||||
case allow_anonymous(Host) of
|
||||
false -> false;
|
||||
true ->
|
||||
@@ -103,9 +130,12 @@ is_login_anonymous_enabled(Host) ->
|
||||
end
|
||||
end.
|
||||
|
||||
%% Return the anonymous protocol to use: sasl_anon|login_anon|both
|
||||
%% @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
|
||||
anonymous_protocol(Host) ->
|
||||
|
||||
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;
|
||||
@@ -113,16 +143,26 @@ anonymous_protocol(Host) ->
|
||||
_Other -> sasl_anon
|
||||
end.
|
||||
|
||||
%% Return true if multiple connections have been allowed in the config file
|
||||
%% @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) ->
|
||||
ejabberd_config:get_local_option(
|
||||
{allow_multiple_connections, Host}) =:= true.
|
||||
|
||||
%% Check if user exist in the anonymus database
|
||||
anonymous_user_exist(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
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},
|
||||
case catch mnesia:dirty_read({anonymous, US}) of
|
||||
[] ->
|
||||
@@ -131,46 +171,75 @@ anonymous_user_exist(User, Server) ->
|
||||
true
|
||||
end.
|
||||
|
||||
%% Remove connection from Mnesia tables
|
||||
remove_connection(SID, LUser, LServer) ->
|
||||
%% @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:transaction(F).
|
||||
mnesia:async_dirty(F).
|
||||
|
||||
%% Register connection
|
||||
register_connection(SID, #jid{luser = LUser, lserver = LServer}, Info) ->
|
||||
AuthModule = xml:get_attr_s(auth_module, Info),
|
||||
case AuthModule == ?MODULE of
|
||||
true ->
|
||||
%% @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:sync_dirty(
|
||||
mnesia:async_dirty(
|
||||
fun() -> mnesia:write(#anonymous{us = US, sid=SID})
|
||||
end);
|
||||
false ->
|
||||
ok
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%% Remove an anonymous user from the anonymous users table
|
||||
unregister_connection(SID, #jid{luser = LUser, lserver = 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).
|
||||
|
||||
%% Launch the hook to purge user data only for anonymous users
|
||||
%% @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) ->
|
||||
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
|
||||
%% ---------------------------------
|
||||
|
||||
%% When anonymous login is enabled, check the password for permenant users
|
||||
%% before allowing access
|
||||
%% @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) ->
|
||||
check_password(User, Server, Password, undefined, undefined).
|
||||
check_password(User, Server, _Password, _Digest, _DigestGen) ->
|
||||
@@ -185,6 +254,10 @@ check_password(User, Server, _Password, _Digest, _DigestGen) ->
|
||||
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;
|
||||
@@ -199,8 +272,13 @@ login(User, Server) ->
|
||||
end
|
||||
end.
|
||||
|
||||
%% When anonymous login is enabled, check that the user is permanent before
|
||||
%% changing its password
|
||||
%% @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 ->
|
||||
@@ -209,22 +287,41 @@ set_password(User, Server, _Password) ->
|
||||
{error, not_allowed}
|
||||
end.
|
||||
|
||||
%% When anonymous login is enabled, check if permanent users are allowed on
|
||||
%% the server:
|
||||
%% @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}.
|
||||
|
||||
%% @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))].
|
||||
|
||||
%% @spec (User, Server) -> Password | false
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = nil()
|
||||
%% @doc Return password of permanent user or false for anonymous users.
|
||||
|
||||
%% Return password of permanent user or false for anonymous users
|
||||
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
|
||||
@@ -235,19 +332,42 @@ get_password(User, Server, DefaultValue) ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% Returns true if the user exists in the DB or if an anonymous user is logged
|
||||
%% under the given name
|
||||
%% @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).
|
||||
|
||||
%% @spec (User, Server) -> {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
remove_user(_User, _Server) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
%% @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
|
||||
end.
|
||||
|
||||
+121
-30
@@ -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
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
%% External exports
|
||||
-export([start/1,
|
||||
stop/1,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
@@ -52,12 +53,25 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%% @spec (Host) -> ok
|
||||
%% Host = string()
|
||||
|
||||
start(Host) ->
|
||||
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);
|
||||
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.
|
||||
@@ -76,56 +90,116 @@ check_cache_last_options(Server) ->
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec () -> bool()
|
||||
|
||||
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)
|
||||
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) ->
|
||||
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),
|
||||
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)
|
||||
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).
|
||||
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)
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> nil()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case get_password(User, Server) of
|
||||
false -> [];
|
||||
@@ -133,6 +207,9 @@ get_password_s(User, Server) ->
|
||||
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
|
||||
@@ -140,6 +217,10 @@ is_user_exists(User, Server) ->
|
||||
_: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;
|
||||
@@ -147,10 +228,15 @@ remove_user(User, Server) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_internal:remove_user(User, Server)
|
||||
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;
|
||||
@@ -158,7 +244,7 @@ remove_user(User, Server, Password) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_internal:remove_user(User, Server, Password)
|
||||
ejabberd_auth_storage:remove_user(User, Server, Password)
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -184,7 +270,7 @@ try_register_extauth(User, Server, Password) ->
|
||||
check_password_cache(User, Server, Password, CacheTime) ->
|
||||
case get_last_access(User, Server) of
|
||||
online ->
|
||||
check_password_internal(User, Server, Password);
|
||||
check_password_storage(User, Server, Password);
|
||||
never ->
|
||||
check_password_external_cache(User, Server, Password);
|
||||
mod_last_required ->
|
||||
@@ -195,7 +281,7 @@ check_password_cache(User, Server, Password, CacheTime) ->
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
%% If no need to refresh, check password against Mnesia
|
||||
true ->
|
||||
case check_password_internal(User, Server, Password) of
|
||||
case check_password_storage(User, Server, Password) of
|
||||
%% If password valid in Mnesia, accept it
|
||||
true ->
|
||||
true;
|
||||
@@ -209,14 +295,14 @@ check_password_cache(User, Server, Password, CacheTime) ->
|
||||
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) -> false | Password::string()
|
||||
%% @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);
|
||||
get_password_storage(User, Server);
|
||||
never ->
|
||||
false;
|
||||
mod_last_required ->
|
||||
@@ -225,7 +311,7 @@ get_password_cache(User, Server, CacheTime) ->
|
||||
TimeStamp ->
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
true ->
|
||||
get_password_internal(User, Server);
|
||||
get_password_storage(User, Server);
|
||||
false ->
|
||||
false
|
||||
end
|
||||
@@ -236,7 +322,7 @@ get_password_cache(User, Server, CacheTime) ->
|
||||
check_password_external_cache(User, Server, Password) ->
|
||||
case check_password_extauth(User, Server, Password) of
|
||||
true ->
|
||||
set_password_internal(User, Server, Password), true;
|
||||
set_password_storage(User, Server, Password), true;
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
@@ -245,18 +331,18 @@ check_password_external_cache(User, Server, Password) ->
|
||||
try_register_external_cache(User, Server, Password) ->
|
||||
case try_register_extauth(User, Server, Password) of
|
||||
{atomic, ok} = R ->
|
||||
set_password_internal(User, Server, Password),
|
||||
set_password_storage(User, Server, Password),
|
||||
R;
|
||||
_ -> {error, not_allowed}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false
|
||||
check_password_internal(User, Server, Password) ->
|
||||
ejabberd_auth_internal:check_password(User, Server, Password).
|
||||
check_password_storage(User, Server, Password) ->
|
||||
ejabberd_auth_storage:check_password(User, Server, Password).
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
|
||||
set_password_internal(User, Server, Password) ->
|
||||
ejabberd_auth_internal:set_password(User, Server, Password).
|
||||
set_password_storage(User, Server, Password) ->
|
||||
ejabberd_auth_storage:set_password(User, Server, Password).
|
||||
|
||||
%% @spec (TimeLast, CacheTime) -> true | false
|
||||
%% TimeLast = online | never | integer()
|
||||
@@ -270,11 +356,13 @@ is_fresh_enough(TimeStampLast, CacheTime) ->
|
||||
Now = MegaSecs * 1000000 + Secs,
|
||||
(TimeStampLast + CacheTime > Now).
|
||||
|
||||
%% @spec (User, Server) -> online | never | mod_last_required | TimeStamp::integer()
|
||||
%% Code copied from mod_configure.erl
|
||||
%% Code copied from web/ejabberd_web_admin.erl
|
||||
%% TODO: Update time format to XEP-0202: Entity Time
|
||||
get_last_access(User, Server) ->
|
||||
%% @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},
|
||||
@@ -297,15 +385,18 @@ get_last_info(User, Server) ->
|
||||
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;
|
||||
true -> mod_last;
|
||||
false -> no_mod_last
|
||||
end.
|
||||
|
||||
get_mod_last_configured(Server) ->
|
||||
case is_configured(Server, mod_last) of
|
||||
true -> mod_last;
|
||||
true -> mod_last;
|
||||
false -> no_mod_last
|
||||
end.
|
||||
|
||||
|
||||
@@ -1,470 +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').
|
||||
|
||||
%% 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").
|
||||
|
||||
-record(passwd, {us, password}).
|
||||
-record(reg_users_counter, {vhost, count}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
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_list(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_list(Passwd) ->
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true ->
|
||||
false
|
||||
end,
|
||||
if DigRes ->
|
||||
true;
|
||||
true ->
|
||||
(Passwd == Password) and (Password /= "")
|
||||
end;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
Passwd = base64:decode(Scram#scram.storedkey),
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true ->
|
||||
false
|
||||
end,
|
||||
if DigRes ->
|
||||
true;
|
||||
true ->
|
||||
(Passwd == Password) and (Password /= "")
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% ok | {error, invalid_jid}
|
||||
set_password(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
if
|
||||
(LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
true ->
|
||||
F = fun() ->
|
||||
Password2 = case is_scrammed() and is_list(Password) of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
mnesia:write(#passwd{us = US,
|
||||
password = Password2})
|
||||
end,
|
||||
{atomic, ok} = mnesia:transaction(F),
|
||||
ok
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {aborted, Reason}
|
||||
try_register(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
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_list(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.
|
||||
|
||||
%% Get all registered users in Mnesia
|
||||
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_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).
|
||||
|
||||
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_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).
|
||||
|
||||
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_list(Password) ->
|
||||
Password;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
{base64:decode(Scram#scram.storedkey),
|
||||
base64:decode(Scram#scram.serverkey),
|
||||
base64:decode(Scram#scram.salt),
|
||||
Scram#scram.iterationcount};
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
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_list(Password) ->
|
||||
Password;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
[];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
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.
|
||||
|
||||
%% @spec (User, Server) -> ok
|
||||
%% @doc Remove user.
|
||||
%% Note: it returns ok even if there was some problem removing the user.
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, -1)
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
ok.
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request
|
||||
%% @doc Remove user if the provided password is correct.
|
||||
remove_user(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
case mnesia:read({passwd, US}) of
|
||||
[#passwd{password = Password}] when is_list(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 ->
|
||||
maybe_scram_passwords(),
|
||||
ok;
|
||||
[user, password] ->
|
||||
?INFO_MSG("Converting passwd table from "
|
||||
"{user, password} format", []),
|
||||
Host = ?MYNAME,
|
||||
{atomic, ok} = mnesia:create_table(
|
||||
ejabberd_auth_internal_tmp_table,
|
||||
[{disc_only_copies, [node()]},
|
||||
{type, bag},
|
||||
{local_content, true},
|
||||
{record_name, passwd},
|
||||
{attributes, record_info(fields, passwd)}]),
|
||||
mnesia:transform_table(passwd, ignore, Fields),
|
||||
F1 = fun() ->
|
||||
mnesia:write_lock_table(ejabberd_auth_internal_tmp_table),
|
||||
mnesia:foldl(
|
||||
fun(#passwd{us = U} = R, _) ->
|
||||
mnesia:dirty_write(
|
||||
ejabberd_auth_internal_tmp_table,
|
||||
R#passwd{us = {U, Host}})
|
||||
end, ok, passwd)
|
||||
end,
|
||||
mnesia:transaction(F1),
|
||||
mnesia:clear_table(passwd),
|
||||
F2 = fun() ->
|
||||
mnesia:write_lock_table(passwd),
|
||||
mnesia:foldl(
|
||||
fun(R, _) ->
|
||||
mnesia:dirty_write(R)
|
||||
end, ok, ejabberd_auth_internal_tmp_table)
|
||||
end,
|
||||
mnesia:transaction(F2),
|
||||
mnesia:delete_table(ejabberd_auth_internal_tmp_table);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating passwd table", []),
|
||||
mnesia:transform_table(passwd, ignore, Fields)
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% SCRAM
|
||||
%%%
|
||||
|
||||
%% The passwords are stored scrammed in the table either if the option says so,
|
||||
%% or if at least the first password is scrammed.
|
||||
is_scrammed() ->
|
||||
OptionScram = is_option_scram(),
|
||||
FirstElement = mnesia:dirty_read(passwd, mnesia:dirty_first(passwd)),
|
||||
case {OptionScram, FirstElement} of
|
||||
{true, _} ->
|
||||
true;
|
||||
{false, [#passwd{password = Scram}]} when is_record(Scram, scram) ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
is_option_scram() ->
|
||||
scram == ejabberd_config:get_local_option({auth_password_format, ?MYNAME}).
|
||||
|
||||
maybe_alert_password_scrammed_without_option() ->
|
||||
case is_scrammed() andalso not is_option_scram() of
|
||||
true ->
|
||||
?ERROR_MSG("Some passwords were stored in the database as SCRAM, "
|
||||
"but 'auth_password_format' is not configured 'scram'. "
|
||||
"The option will now be considered to be 'scram'.", []);
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
maybe_scram_passwords() ->
|
||||
case is_scrammed() of
|
||||
true -> scram_passwords();
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
scram_passwords() ->
|
||||
?INFO_MSG("Converting the stored passwords into SCRAM bits", []),
|
||||
Fun = fun(#passwd{password = Password} = P) ->
|
||||
Scram = password_to_scram(Password),
|
||||
P#passwd{password = Scram}
|
||||
end,
|
||||
Fields = record_info(fields, passwd),
|
||||
mnesia:transform_table(passwd, Fun, Fields).
|
||||
|
||||
password_to_scram(Password) ->
|
||||
password_to_scram(Password, ?SCRAM_DEFAULT_ITERATION_COUNT).
|
||||
|
||||
password_to_scram(Password, IterationCount) ->
|
||||
Salt = crypto:rand_bytes(?SALT_LENGTH),
|
||||
SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
|
||||
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
ServerKey = scram:server_key(SaltedPassword),
|
||||
#scram{storedkey = base64:encode(StoredKey),
|
||||
serverkey = base64:encode(ServerKey),
|
||||
salt = base64:encode(Salt),
|
||||
iterationcount = IterationCount}.
|
||||
|
||||
is_password_scram_valid(Password, Scram) ->
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
Salt = base64:decode(Scram#scram.salt),
|
||||
SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
|
||||
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
(base64:decode(Scram#scram.storedkey) == StoredKey).
|
||||
+191
-48
@@ -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
|
||||
@@ -73,7 +73,6 @@
|
||||
base,
|
||||
uids,
|
||||
ufilter,
|
||||
sfilter,
|
||||
lfilter, %% Local filter (performed by ejabberd, not LDAP)
|
||||
deref_aliases,
|
||||
dn_filter,
|
||||
@@ -97,51 +96,99 @@ handle_info(_Info, State) ->
|
||||
%%% 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, []).
|
||||
|
||||
%% @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}.
|
||||
|
||||
%% @spec () -> true
|
||||
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
store_type() ->
|
||||
external.
|
||||
|
||||
%% @spec (User, Server, Password) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
%% In LDAP spec: empty password means anonymous authentication.
|
||||
%% As ejabberd is providing other anonymous authentication mechanisms
|
||||
@@ -155,12 +202,25 @@ check_password(User, Server, Password) ->
|
||||
end
|
||||
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) ->
|
||||
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
|
||||
case find_user_dn(User, Server, State) of
|
||||
false ->
|
||||
{error, user_not_found};
|
||||
DN ->
|
||||
@@ -168,9 +228,17 @@ set_password(User, Server, 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(
|
||||
@@ -178,22 +246,42 @@ dirty_get_registered_users() ->
|
||||
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.
|
||||
|
||||
%% @spec (Server) -> Users_Number
|
||||
%% Server = string()
|
||||
%% Users_Number = integer()
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
length(get_vh_registered_users(Server)).
|
||||
|
||||
%% @spec (User, Server) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
get_password(_User, _Server) ->
|
||||
false.
|
||||
|
||||
%% @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} ->
|
||||
@@ -202,18 +290,33 @@ is_user_exists(User, Server) ->
|
||||
Result
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
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
|
||||
{ok, State} = get_state(Server),
|
||||
case find_user_dn(User, Server, State) of
|
||||
false ->
|
||||
false;
|
||||
DN ->
|
||||
@@ -221,15 +324,34 @@ check_password_ldap(User, Server, Password) ->
|
||||
ok -> true;
|
||||
_ -> false
|
||||
end
|
||||
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
|
||||
case eldap_filter:parse(SearchFilter) of
|
||||
{ok, EldapFilter} ->
|
||||
case eldap_pool:search(Eldap_ID,
|
||||
[{base, State#state.base},
|
||||
@@ -241,7 +363,7 @@ get_vh_registered_users_ldap(Server) ->
|
||||
lists:flatmap(
|
||||
fun(#eldap_entry{attributes = Attrs,
|
||||
object_name = DN}) ->
|
||||
case is_valid_dn(DN, Attrs, State) of
|
||||
case is_valid_dn(DN, Server, Attrs, State) of
|
||||
false -> [];
|
||||
_ ->
|
||||
case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
|
||||
@@ -249,9 +371,11 @@ get_vh_registered_users_ldap(Server) ->
|
||||
{User, UIDFormat} ->
|
||||
case eldap_utils:get_user_part(User, UIDFormat) of
|
||||
{ok, U} ->
|
||||
case jlib:nodeprep(U) of
|
||||
error -> [];
|
||||
LU -> [{LU, jlib:nameprep(Server)}]
|
||||
try
|
||||
[{exmpp_stringprep:nodeprep(U), exmpp_stringprep:nameprep(Server)}]
|
||||
catch
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
_ -> []
|
||||
end
|
||||
@@ -265,9 +389,13 @@ get_vh_registered_users_ldap(Server) ->
|
||||
[]
|
||||
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
|
||||
case find_user_dn(User, Server, State) of
|
||||
false -> false;
|
||||
_DN -> true
|
||||
end.
|
||||
@@ -281,9 +409,10 @@ handle_call(stop, _From, 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
|
||||
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},
|
||||
@@ -292,7 +421,7 @@ find_user_dn(User, State) ->
|
||||
{attributes, ResAttrs}]) of
|
||||
#eldap_search_result{entries = [#eldap_entry{attributes = Attrs,
|
||||
object_name = DN} | _]} ->
|
||||
dn_filter(DN, Attrs, State);
|
||||
dn_filter(DN, Server, Attrs, State);
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
@@ -301,20 +430,20 @@ find_user_dn(User, State) ->
|
||||
end.
|
||||
|
||||
%% apply the dn filter and the local filter:
|
||||
dn_filter(DN, Attrs, State) ->
|
||||
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)
|
||||
true -> is_valid_dn(DN, Server, Attrs, State)
|
||||
end.
|
||||
|
||||
%% Check that the DN is valid, based on the dn filter
|
||||
is_valid_dn(DN, _, #state{dn_filter = undefined}) ->
|
||||
is_valid_dn(DN, _, _, #state{dn_filter = undefined}) ->
|
||||
DN;
|
||||
|
||||
is_valid_dn(DN, Attrs, State) ->
|
||||
is_valid_dn(DN, Server, Attrs, State) ->
|
||||
DNAttrs = State#state.dn_filter_attrs,
|
||||
UIDs = State#state.uids,
|
||||
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;
|
||||
@@ -370,6 +499,22 @@ result_attrs(#state{uids = UIDs, dn_filter_attrs = DNFilterAttrs}) ->
|
||||
[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
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -407,13 +552,12 @@ parse_options(Host) ->
|
||||
end,
|
||||
SubFilter = lists:flatten(eldap_utils:generate_subfilter(UIDs)),
|
||||
UserFilter = case ejabberd_config:get_local_option({ldap_filter, Host}) of
|
||||
undefined -> SubFilter;
|
||||
"" -> SubFilter;
|
||||
undefined -> "";
|
||||
"" -> "";
|
||||
F ->
|
||||
eldap_utils:check_filter(F),
|
||||
"(&" ++ SubFilter ++ F ++ ")"
|
||||
end,
|
||||
SearchFilter = eldap_filter:do_sub(UserFilter, [{"%u", "*"}]),
|
||||
LDAPBase = ejabberd_config:get_local_option({ldap_base, Host}),
|
||||
{DNFilter, DNFilterAttrs} =
|
||||
case ejabberd_config:get_local_option({ldap_dn_filter, Host}) of
|
||||
@@ -446,7 +590,6 @@ parse_options(Host) ->
|
||||
base = LDAPBase,
|
||||
uids = UIDs,
|
||||
ufilter = UserFilter,
|
||||
sfilter = SearchFilter,
|
||||
lfilter = LocalFilter,
|
||||
deref_aliases = DerefAliases,
|
||||
dn_filter = DNFilter,
|
||||
|
||||
@@ -1,285 +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').
|
||||
|
||||
%% 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").
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(_Host) ->
|
||||
ok.
|
||||
|
||||
plain_password_required() ->
|
||||
false.
|
||||
|
||||
store_type() ->
|
||||
plain.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false | {error, Error}
|
||||
check_password(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
false;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
try odbc_queries:get_password(LServer, Username) of
|
||||
{selected, ["password"], [{Password}]} ->
|
||||
Password /= ""; %% Password is correct, and not empty
|
||||
{selected, ["password"], [{_Password2}]} ->
|
||||
false; %% Password is not correct
|
||||
{selected, ["password"], []} ->
|
||||
false; %% Account does not exist
|
||||
{error, _Error} ->
|
||||
false %% Typical error is that table doesn't exist
|
||||
catch
|
||||
_:_ ->
|
||||
false %% Typical error is database not accessible
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
|
||||
check_password(User, Server, Password, Digest, DigestGen) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
false;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
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.
|
||||
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% ok | {error, invalid_jid}
|
||||
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.
|
||||
|
||||
|
||||
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid}
|
||||
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}]} ->
|
||||
list_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}]} ->
|
||||
list_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.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
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} %% Typical error is that table doesn't exist
|
||||
catch
|
||||
_:B ->
|
||||
{error, B} %% Typical error is database not accessible
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> ok | error
|
||||
%% @doc Remove user.
|
||||
%% Note: it may return ok even if there was some problem removing the user.
|
||||
remove_user(User, Server) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
error;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
catch odbc_queries:del_user(LServer, Username),
|
||||
ok
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed
|
||||
%% @doc Remove user if the provided password is correct.
|
||||
remove_user(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
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.
|
||||
+83
-14
@@ -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
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
%% External exports
|
||||
-export([start/1,
|
||||
stop/1,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
@@ -46,6 +47,10 @@
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
|
||||
%% @spec (Host) -> ok | term()
|
||||
%% Host = string()
|
||||
|
||||
start(_Host) ->
|
||||
case epam:start() of
|
||||
{ok, _} -> ok;
|
||||
@@ -53,57 +58,117 @@ start(_Host) ->
|
||||
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
|
||||
%% @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++"@"++Host
|
||||
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}.
|
||||
|
||||
%% @spec () -> [{LUser, LServer}]
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
[].
|
||||
|
||||
get_vh_registered_users(_Host) ->
|
||||
%% @spec (Server) -> [{LUser, LServer}]
|
||||
%% Server = string()
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
|
||||
get_vh_registered_users(_Server) ->
|
||||
[].
|
||||
|
||||
%% @spec (User, Server) -> Password | false
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
get_password(_User, _Server) ->
|
||||
false.
|
||||
|
||||
%% @spec (User, Server) -> Password | nil()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
get_password_s(_User, _Server) ->
|
||||
"".
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
%% TODO: Improve this function to return an error instead of 'false' when connection to PAM failed
|
||||
is_user_exists(User, Host) ->
|
||||
Service = get_pam_service(Host),
|
||||
UserInfo = case get_pam_userinfotype(Host) of
|
||||
%% 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++"@"++Host
|
||||
jid -> User++"@"++Server
|
||||
end,
|
||||
case catch epam:acct_mgmt(Service, UserInfo) of
|
||||
true -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
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.
|
||||
|
||||
%% @spec () -> bool()
|
||||
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
@@ -113,8 +178,12 @@ store_type() ->
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
get_pam_service(Host) ->
|
||||
case ejabberd_config:get_local_option({pam_service, Host}) of
|
||||
|
||||
%% @spec (Server) -> string()
|
||||
%% Server = string()
|
||||
|
||||
get_pam_service(Server) ->
|
||||
case ejabberd_config:get_local_option({pam_service, Server}) of
|
||||
undefined -> "ejabberd";
|
||||
Service -> Service
|
||||
end.
|
||||
|
||||
@@ -0,0 +1,645 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% 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),
|
||||
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_option_scram(?MYNAME) of
|
||||
false -> false;
|
||||
true -> true
|
||||
end.
|
||||
|
||||
store_type() ->
|
||||
case is_option_scram(?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_option_scram(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_option_scram(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.
|
||||
|
||||
action_password_format(Host) ->
|
||||
OptionScram = is_option_scram(Host),
|
||||
case {OptionScram, get_format_first_element(Host)} of
|
||||
{true, scram} -> scram;
|
||||
{true, any} -> scram;
|
||||
{true, plain} -> must_scram;
|
||||
{false, plain} -> plain;
|
||||
{false, any} -> plain;
|
||||
{false, scram} ->
|
||||
set_option_password_format(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(Host) when is_list(Host) ->
|
||||
scram == ejabberd_config:get_local_option({auth_password_format, Host}).
|
||||
|
||||
set_option_password_format(Value) ->
|
||||
?ERROR_MSG("Some passwords are stored in the database as SCRAM bits, "
|
||||
"but the option 'auth_password_format' is not configured 'scram'. "
|
||||
"The option will now be considered to be 'scram'.", []),
|
||||
ejabberd_config:add_local_option({auth_password_format, ?MYNAME}, Value).
|
||||
|
||||
maybe_scram_passwords(HostB) ->
|
||||
case action_password_format(binary_to_list(HostB)) of
|
||||
must_scram -> scram_passwords(HostB);
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
scram_passwords(HostB) ->
|
||||
Backend =
|
||||
case ejabberd_config:get_local_option({auth_storage, binary_to_list(HostB)}) of
|
||||
undefined -> mnesia;
|
||||
B -> B
|
||||
end,
|
||||
scram_passwords(HostB, Backend).
|
||||
scram_passwords(HostB, mnesia) ->
|
||||
?INFO_MSG("Converting the passwords stored in mnesia for host ~p into SCRAM bits", [HostB]),
|
||||
gen_storage_migration:migrate_mnesia(
|
||||
HostB, passwd,
|
||||
[{passwd, [user_host, password, storedkey, serverkey, salt, iterationcount],
|
||||
fun(#passwd{password = Password} = Passwd) ->
|
||||
password_to_scram(Password, Passwd)
|
||||
end}]);
|
||||
scram_passwords(HostB, odbc) ->
|
||||
?INFO_MSG("Converting the passwords stored in odbc for host ~p into SCRAM bits", [HostB]),
|
||||
gen_storage_migration:migrate_odbc(
|
||||
HostB, [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}]).
|
||||
+1045
-959
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
||||
%%% Created : 2 Nov 2007 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-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
|
||||
|
||||
+482
-276
@@ -5,7 +5,7 @@
|
||||
%%% Created : 26 Apr 2008 by Evgeniy Khramtsov <xramtsov@gmail.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
|
||||
@@ -24,6 +24,22 @@
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
%%% Database schema (version / storage / table)
|
||||
%%%
|
||||
%%% 2.1.x / mnesia / captcha
|
||||
%%% id = string()
|
||||
%%% pid = pid()
|
||||
%%% key = string()
|
||||
%%% tref = any()
|
||||
%%% args = any()
|
||||
%%%
|
||||
%%% 3.0.0-alpha / ets / captcha
|
||||
%%% id = string()
|
||||
%%% pid = pid()
|
||||
%%% key = string()
|
||||
%%% tref = any()
|
||||
%%% args = any()
|
||||
|
||||
-module(ejabberd_captcha).
|
||||
|
||||
-behaviour(gen_server).
|
||||
@@ -35,34 +51,39 @@
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-export([create_captcha/6, build_captcha_html/2, check_captcha/2,
|
||||
-export([create_captcha/5, build_captcha_html/2, check_captcha/2,
|
||||
process_reply/1, process/2, is_feature_available/0,
|
||||
create_captcha_x/5, create_captcha_x/6]).
|
||||
create_captcha_x/4, create_captcha_x/5]).
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
-include("web/ejabberd_http.hrl").
|
||||
|
||||
-define(VFIELD(Type, Var, Value),
|
||||
{xmlelement, "field", [{"type", Type}, {"var", Var}],
|
||||
[{xmlelement, "value", [], [Value]}]}).
|
||||
#xmlel{name = 'field',
|
||||
attrs = [
|
||||
#xmlattr{name = <<"type">>,
|
||||
value = Type
|
||||
},
|
||||
#xmlattr{name = <<"var">>,
|
||||
value = Var
|
||||
}
|
||||
],
|
||||
children = [
|
||||
#xmlel{name = 'value',
|
||||
children = [Value]
|
||||
}
|
||||
]}).
|
||||
|
||||
-define(CAPTCHA_TEXT(Lang), translate:translate(Lang, "Enter the text you see")).
|
||||
-define(CAPTCHA_TEXT(Lang), list_to_binary(translate:translate(Lang, "Enter the text you see"))).
|
||||
-define(CAPTCHA_LIFETIME, 120000). % two minutes
|
||||
-define(LIMIT_PERIOD, 60*1000*1000). % one minute
|
||||
-define(RPC_TIMEOUT, 5000).
|
||||
|
||||
-record(state, {limits = treap:empty()}).
|
||||
-record(state, {}).
|
||||
-record(captcha, {id, pid, key, tref, args}).
|
||||
|
||||
-define(T(S),
|
||||
case catch mnesia:transaction(fun() -> S end) of
|
||||
{atomic, Res} ->
|
||||
Res;
|
||||
{_, Reason} ->
|
||||
?ERROR_MSG("mnesia transaction failed: ~p", [Reason]),
|
||||
{error, Reason}
|
||||
end).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@@ -73,96 +94,226 @@
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
create_captcha(SID, From, To, Lang, Limiter, Args)
|
||||
when is_list(Lang), is_list(SID),
|
||||
is_record(From, jid), is_record(To, jid) ->
|
||||
case create_image(Limiter) of
|
||||
create_captcha(SID, From, To, Lang, Args)
|
||||
when is_binary(Lang), is_binary(SID) ->
|
||||
case create_image() of
|
||||
{ok, Type, Key, Image} ->
|
||||
Id = randoms:get_string(),
|
||||
B64Image = jlib:encode_base64(binary_to_list(Image)),
|
||||
JID = jlib:jid_to_string(From),
|
||||
CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
|
||||
Data = {xmlelement, "data",
|
||||
[{"xmlns", ?NS_BOB}, {"cid", CID},
|
||||
{"max-age", "0"}, {"type", Type}],
|
||||
[{xmlcdata, B64Image}]},
|
||||
Id = randoms:get_string() ++ "-" ++ ejabberd_cluster:node_id(),
|
||||
B64Image = list_to_binary(jlib:encode_base64(binary_to_list(Image))),
|
||||
JID = exmpp_jid:to_list(From),
|
||||
CID = list_to_binary(["sha1+", sha:sha(Image), "@bob.xmpp.org"]),
|
||||
% Data = {xmlelement, "data",
|
||||
% [{"xmlns", ?NS_BOB}, {"cid", CID},
|
||||
% {"max-age", "0"}, {"type", Type}],
|
||||
% [{xmlcdata, B64Image}]},
|
||||
Data =
|
||||
#xmlel{
|
||||
name = 'data',
|
||||
ns = ?NS_BOB,
|
||||
attrs = [
|
||||
#xmlattr{name = <<"cid">>,
|
||||
value = CID
|
||||
},
|
||||
#xmlattr{name = <<"max-age">>,
|
||||
value = <<"0">>
|
||||
},
|
||||
#xmlattr{name = <<"type">>,
|
||||
value = Type
|
||||
}
|
||||
],
|
||||
children = [#xmlcdata{cdata = B64Image}]},
|
||||
% {xmlelement, "captcha", [{"xmlns", ?NS_CAPTCHA}],
|
||||
% %% ?NS_DATA_FORMS is 'jabber:x:data'
|
||||
% [{xmlelement, "x", [{"xmlns", "jabber:x:data"}, {"type", "form"}],
|
||||
% [?VFIELD("hidden", "FORM_TYPE", {xmlcdata, ?NS_CAPTCHA}),
|
||||
% ?VFIELD("hidden", "from", {xmlcdata, exmpp_jid:to_list(To)}),
|
||||
% ?VFIELD("hidden", "challenge", {xmlcdata, Id}),
|
||||
% ?VFIELD("hidden", "sid", {xmlcdata, SID}),
|
||||
% {xmlelement, "field", [{"var", "ocr"}, {"label", ?CAPTCHA_TEXT(Lang)}],
|
||||
% [{xmlelement, "media", [{"xmlns", ?NS_DATA_FORMS_MEDIA_s}],
|
||||
% [{xmlelement, "uri", [{"type", Type}],
|
||||
% [{xmlcdata, "cid:" ++ CID}]}]}]}]}]},
|
||||
%% TODO : kael : write exmpp_captcha.erl
|
||||
Captcha =
|
||||
{xmlelement, "captcha", [{"xmlns", ?NS_CAPTCHA}],
|
||||
[{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
|
||||
[?VFIELD("hidden", "FORM_TYPE", {xmlcdata, ?NS_CAPTCHA}),
|
||||
?VFIELD("hidden", "from", {xmlcdata, jlib:jid_to_string(To)}),
|
||||
?VFIELD("hidden", "challenge", {xmlcdata, Id}),
|
||||
?VFIELD("hidden", "sid", {xmlcdata, SID}),
|
||||
{xmlelement, "field", [{"var", "ocr"}, {"label", ?CAPTCHA_TEXT(Lang)}],
|
||||
[{xmlelement, "required", [], []},
|
||||
{xmlelement, "media", [{"xmlns", ?NS_MEDIA}],
|
||||
[{xmlelement, "uri", [{"type", Type}],
|
||||
[{xmlcdata, "cid:" ++ CID}]}]}]}]}]},
|
||||
#xmlel{
|
||||
name = 'captcha',
|
||||
ns = ?NS_CAPTCHA,
|
||||
children = [
|
||||
#xmlel{name = 'x',
|
||||
ns = ?NS_DATA_FORMS_s,
|
||||
attrs = [
|
||||
#xmlattr{name = <<"type">>,
|
||||
value = <<"form">>
|
||||
}
|
||||
],
|
||||
children = [
|
||||
?VFIELD(<<"hidden">>, <<"FORM_TYPE">>, #xmlcdata{cdata = ?NS_CAPTCHA_b}),
|
||||
?VFIELD(<<"hidden">>, <<"from">>, #xmlcdata{cdata = exmpp_jid:to_binary(To)}),
|
||||
?VFIELD(<<"hidden">>, <<"challenge">>, #xmlcdata{cdata = list_to_binary(Id)}),
|
||||
?VFIELD(<<"hidden">>, <<"sid">>, #xmlcdata{cdata = SID}),
|
||||
#xmlel{name = 'field',
|
||||
attrs = [
|
||||
#xmlattr{name = <<"var">>,
|
||||
value = <<"ocr">>
|
||||
},
|
||||
#xmlattr{name = <<"label">>,
|
||||
value = ?CAPTCHA_TEXT(Lang)
|
||||
}
|
||||
],
|
||||
children = [
|
||||
#xmlel{ns = ?NS_DATA_FORMS, name = 'required'},
|
||||
#xmlel{name = 'media',
|
||||
ns = ?NS_DATA_FORMS_MEDIA_s,
|
||||
children = [
|
||||
#xmlel{name = 'uri',
|
||||
attrs = [
|
||||
#xmlattr{name = <<"type">>,
|
||||
value = Type
|
||||
}
|
||||
],
|
||||
children = [
|
||||
#xmlcdata{cdata = list_to_binary(["cid:", CID])}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
BodyString1 = translate:translate(Lang, "Your messages to ~s are being blocked. To unblock them, visit ~s"),
|
||||
BodyString = io_lib:format(BodyString1, [JID, get_url(Id)]),
|
||||
Body = {xmlelement, "body", [],
|
||||
[{xmlcdata, BodyString}]},
|
||||
OOB = {xmlelement, "x", [{"xmlns", ?NS_OOB}],
|
||||
[{xmlelement, "url", [], [{xmlcdata, get_url(Id)}]}]},
|
||||
Body =
|
||||
#xmlel{name = 'body',
|
||||
children = [
|
||||
#xmlcdata{cdata = list_to_binary(BodyString)
|
||||
}
|
||||
]
|
||||
},
|
||||
OOB =
|
||||
#xmlel{name = 'x',
|
||||
ns = ?NS_OOBD_X_s,
|
||||
children = [
|
||||
#xmlel{name = 'url',
|
||||
children = [
|
||||
#xmlcdata{cdata = list_to_binary(get_url(Id))}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
%Body = {xmlelement, "body", [],
|
||||
% [{xmlcdata, BodyString}]},
|
||||
%OOB = {xmlelement, "x", [{"xmlns", ?NS_OOBD_X_s}],
|
||||
% [{xmlelement, "url", [], [{xmlcdata, get_url(Id)}]}]},
|
||||
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
|
||||
case ?T(mnesia:write(#captcha{id=Id, pid=self(), key=Key,
|
||||
tref=Tref, args=Args})) of
|
||||
ok ->
|
||||
{ok, Id, [Body, OOB, Captcha, Data]};
|
||||
Err ->
|
||||
{error, Err}
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
ets:insert(captcha, #captcha{id=Id, pid=self(), key=Key,
|
||||
tref=Tref, args=Args}),
|
||||
{ok, Id, [Body, OOB, Captcha, Data]};
|
||||
_Err ->
|
||||
error
|
||||
end.
|
||||
|
||||
create_captcha_x(SID, To, Lang, Limiter, HeadEls) ->
|
||||
create_captcha_x(SID, To, Lang, Limiter, HeadEls, []).
|
||||
create_captcha_x(SID, To, Lang, HeadEls) ->
|
||||
create_captcha_x(SID, To, Lang, HeadEls, []).
|
||||
|
||||
create_captcha_x(SID, To, Lang, Limiter, HeadEls, TailEls) ->
|
||||
case create_image(Limiter) of
|
||||
create_captcha_x(SID, To, Lang, HeadEls, TailEls) ->
|
||||
case create_image() of
|
||||
{ok, Type, Key, Image} ->
|
||||
Id = randoms:get_string(),
|
||||
B64Image = jlib:encode_base64(binary_to_list(Image)),
|
||||
CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
|
||||
Data = {xmlelement, "data",
|
||||
[{"xmlns", ?NS_BOB}, {"cid", CID},
|
||||
{"max-age", "0"}, {"type", Type}],
|
||||
[{xmlcdata, B64Image}]},
|
||||
Id = randoms:get_string() ++ "-" ++ ejabberd_cluster:node_id(),
|
||||
B64Image = list_to_binary(jlib:encode_base64(binary_to_list(Image))),
|
||||
CID = list_to_binary(["sha1+", sha:sha(Image), "@bob.xmpp.org"]),
|
||||
Data =
|
||||
#xmlel{
|
||||
name = 'data',
|
||||
ns = ?NS_BOB,
|
||||
attrs = [
|
||||
#xmlattr{name = <<"cid">>,
|
||||
value = CID
|
||||
},
|
||||
#xmlattr{name = <<"max-age">>,
|
||||
value = <<"0">>
|
||||
},
|
||||
#xmlattr{name = <<"type">>,
|
||||
value = Type
|
||||
}
|
||||
],
|
||||
children = [#xmlcdata{cdata = B64Image}]},
|
||||
HelpTxt = translate:translate(
|
||||
Lang,
|
||||
"If you don't see the CAPTCHA image here, "
|
||||
"visit the web page."),
|
||||
Imageurl = get_url(Id ++ "/image"),
|
||||
Imageurl = list_to_binary(get_url(Id ++ "/image")),
|
||||
Captcha =
|
||||
{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
|
||||
[?VFIELD("hidden", "FORM_TYPE", {xmlcdata, ?NS_CAPTCHA}) | HeadEls] ++
|
||||
[{xmlelement, "field", [{"type", "fixed"}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, HelpTxt}]}]},
|
||||
{xmlelement, "field", [{"type", "hidden"}, {"var", "captchahidden"}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, "workaround-for-psi"}]}]},
|
||||
{xmlelement, "field",
|
||||
[{"type", "text-single"},
|
||||
{"label", translate:translate(Lang, "CAPTCHA web page")},
|
||||
{"var", "url"}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, Imageurl}]}]},
|
||||
?VFIELD("hidden", "from", {xmlcdata, jlib:jid_to_string(To)}),
|
||||
?VFIELD("hidden", "challenge", {xmlcdata, Id}),
|
||||
?VFIELD("hidden", "sid", {xmlcdata, SID}),
|
||||
{xmlelement, "field", [{"var", "ocr"}, {"label", ?CAPTCHA_TEXT(Lang)}],
|
||||
[{xmlelement, "required", [], []},
|
||||
{xmlelement, "media", [{"xmlns", ?NS_MEDIA}],
|
||||
[{xmlelement, "uri", [{"type", Type}],
|
||||
[{xmlcdata, "cid:" ++ CID}]}]}]}] ++ TailEls},
|
||||
#xmlel{name = 'x',
|
||||
ns = ?NS_DATA_FORMS_s,
|
||||
attrs = [
|
||||
#xmlattr{name = <<"type">>,
|
||||
value = <<"form">>
|
||||
}
|
||||
],
|
||||
children = [
|
||||
?VFIELD(<<"hidden">>, <<"FORM_TYPE">>, #xmlcdata{cdata = ?NS_CAPTCHA_b}) | HeadEls] ++ [
|
||||
#xmlel{ns = ?NS_DATA_FORMS, name = 'field', attrs =
|
||||
[?XMLATTR(<<"type">>, <<"fixed">>), ?XMLATTR(<<"label">>, HelpTxt)]},
|
||||
?VFIELD(<<"hidden">>, <<"captchahidden">>, #xmlcdata{cdata = <<"workaround-for-psi">>}),
|
||||
|
||||
#xmlel{name = 'field',
|
||||
attrs = [
|
||||
#xmlattr{name = <<"type">>,
|
||||
value = <<"text-single">>
|
||||
},
|
||||
#xmlattr{name = <<"label">>,
|
||||
value = list_to_binary(translate:translate(Lang, "CAPTCHA web page"))
|
||||
},
|
||||
#xmlattr{name = <<"var">>,
|
||||
value = <<"url">>
|
||||
}
|
||||
],
|
||||
children = [
|
||||
#xmlel{name = 'value',
|
||||
children = [#xmlcdata{cdata = Imageurl}]
|
||||
}
|
||||
]},
|
||||
?VFIELD(<<"hidden">>, <<"from">>, #xmlcdata{cdata = exmpp_jid:to_binary(To)}),
|
||||
?VFIELD(<<"hidden">>, <<"challenge">>, #xmlcdata{cdata = list_to_binary(Id)}),
|
||||
?VFIELD(<<"hidden">>, <<"sid">>, #xmlcdata{cdata = SID}),
|
||||
#xmlel{name = 'field',
|
||||
attrs = [
|
||||
#xmlattr{name = <<"var">>,
|
||||
value = <<"ocr">>
|
||||
},
|
||||
#xmlattr{name = <<"label">>,
|
||||
value = ?CAPTCHA_TEXT(Lang)
|
||||
}
|
||||
],
|
||||
children = [
|
||||
#xmlel{ns = ?NS_DATA_FORMS, name = 'required'},
|
||||
#xmlel{name = 'media',
|
||||
ns = ?NS_DATA_FORMS_MEDIA_s,
|
||||
children = [
|
||||
#xmlel{name = 'uri',
|
||||
attrs = [
|
||||
#xmlattr{name = <<"type">>,
|
||||
value = Type
|
||||
}
|
||||
],
|
||||
children = [
|
||||
#xmlcdata{cdata = list_to_binary(["cid:", CID])}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
] ++ TailEls
|
||||
},
|
||||
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
|
||||
case ?T(mnesia:write(#captcha{id=Id, key=Key, tref=Tref})) of
|
||||
ok ->
|
||||
{ok, [Captcha, Data]};
|
||||
Err ->
|
||||
{error, Err}
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
ets:insert(captcha, #captcha{id=Id, key=Key, tref=Tref}),
|
||||
{ok, [Captcha, Data]};
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
%% @spec (Id::string(), Lang::string()) -> {FormEl, {ImgEl, TextEl, IdEl, KeyEl}} | captcha_not_found
|
||||
@@ -172,30 +323,106 @@ create_captcha_x(SID, To, Lang, Limiter, HeadEls, TailEls) ->
|
||||
%% IdEl = xmlelement()
|
||||
%% KeyEl = xmlelement()
|
||||
build_captcha_html(Id, Lang) ->
|
||||
case mnesia:dirty_read(captcha, Id) of
|
||||
[#captcha{}] ->
|
||||
ImgEl = {xmlelement, "img", [{"src", get_url(Id ++ "/image")}], []},
|
||||
TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
|
||||
IdEl = {xmlelement, "input", [{"type", "hidden"},
|
||||
{"name", "id"},
|
||||
{"value", Id}], []},
|
||||
KeyEl = {xmlelement, "input", [{"type", "text"},
|
||||
{"name", "key"},
|
||||
{"size", "10"}], []},
|
||||
FormEl = {xmlelement, "form", [{"action", get_url(Id)},
|
||||
{"name", "captcha"},
|
||||
{"method", "POST"}],
|
||||
[ImgEl,
|
||||
{xmlelement, "br", [], []},
|
||||
TextEl,
|
||||
{xmlelement, "br", [], []},
|
||||
IdEl,
|
||||
KeyEl,
|
||||
{xmlelement, "br", [], []},
|
||||
{xmlelement, "input", [{"type", "submit"},
|
||||
{"name", "enter"},
|
||||
{"value", "OK"}], []}
|
||||
]},
|
||||
case lookup_captcha(Id) of
|
||||
{ok, _} ->
|
||||
%ImgEl = {xmlelement, "img", [{"src", get_url(Id ++ "/image")}], []},
|
||||
ImgEl =
|
||||
#xmlel{name = 'img',
|
||||
attrs = [
|
||||
#xmlattr{name = <<"src">>,
|
||||
value = list_to_binary(get_url(Id ++ "/image"))
|
||||
}
|
||||
]
|
||||
},
|
||||
%TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
|
||||
TextEl = #xmlcdata{cdata = ?CAPTCHA_TEXT(Lang)},
|
||||
%IdEl = {xmlelement, "input", [{"type", "hidden"},
|
||||
% {"name", "id"},
|
||||
% {"value", Id}], []},
|
||||
IdEl =
|
||||
#xmlel{name = 'input',
|
||||
attrs = [
|
||||
#xmlattr{name = <<"type">>,
|
||||
value = <<"hidden">>
|
||||
},
|
||||
#xmlattr{name = <<"name">>,
|
||||
value = <<"id">>
|
||||
},
|
||||
#xmlattr{name = <<"value">>,
|
||||
value = list_to_binary(Id)
|
||||
}
|
||||
]
|
||||
},
|
||||
%KeyEl = {xmlelement, "input", [{"type", "text"},
|
||||
% {"name", "key"},
|
||||
% {"size", "10"}], []},
|
||||
KeyEl =
|
||||
#xmlel{name = 'input',
|
||||
attrs = [
|
||||
#xmlattr{name = <<"type">>,
|
||||
value = <<"text">>
|
||||
},
|
||||
#xmlattr{name = <<"name">>,
|
||||
value = <<"key">>
|
||||
},
|
||||
#xmlattr{name = <<"size">>,
|
||||
value = <<"10">>
|
||||
}
|
||||
]
|
||||
},
|
||||
%FormEl = {xmlelement, "form", [{"action", get_url(Id)},
|
||||
% {"name", "captcha"},
|
||||
% {"method", "POST"}],
|
||||
% [ImgEl,
|
||||
% {xmlelement, "br", [], []},
|
||||
% TextEl,
|
||||
% {xmlelement, "br", [], []},
|
||||
% IdEl,
|
||||
% KeyEl,
|
||||
% {xmlelement, "br", [], []},
|
||||
% {xmlelement, "input", [{"type", "submit"},
|
||||
% {"name", "enter"},
|
||||
% {"value", "OK"}], []}
|
||||
% ]},
|
||||
FormEl =
|
||||
#xmlel{name = 'form',
|
||||
attrs = [
|
||||
#xmlattr{name = <<"action">>,
|
||||
value = list_to_binary(get_url(Id))
|
||||
},
|
||||
#xmlattr{name = <<"name">>,
|
||||
value = <<"captcha">>
|
||||
},
|
||||
#xmlattr{name = <<"method">>,
|
||||
value = <<"POST">>
|
||||
}
|
||||
],
|
||||
children = [
|
||||
ImgEl,
|
||||
#xmlel{name = 'br'
|
||||
},
|
||||
TextEl,
|
||||
#xmlel{name = 'br'
|
||||
},
|
||||
IdEl,
|
||||
KeyEl,
|
||||
#xmlel{name = 'br'
|
||||
},
|
||||
#xmlel{name = 'input',
|
||||
attrs = [
|
||||
#xmlattr{name = <<"type">>,
|
||||
value = <<"submit">>
|
||||
},
|
||||
#xmlattr{name = <<"name">>,
|
||||
value = <<"enter">>
|
||||
},
|
||||
#xmlattr{name = <<"value">>,
|
||||
value = <<"OK">>
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{FormEl, {ImgEl, TextEl, IdEl, KeyEl}};
|
||||
_ ->
|
||||
captcha_not_found
|
||||
@@ -203,93 +430,78 @@ build_captcha_html(Id, Lang) ->
|
||||
|
||||
%% @spec (Id::string(), ProvidedKey::string()) -> captcha_valid | captcha_non_valid | captcha_not_found
|
||||
check_captcha(Id, ProvidedKey) ->
|
||||
?T(case mnesia:read(captcha, Id, write) of
|
||||
[#captcha{pid=Pid, args=Args, key=StoredKey, tref=Tref}] ->
|
||||
mnesia:delete({captcha, Id}),
|
||||
erlang:cancel_timer(Tref),
|
||||
if StoredKey == ProvidedKey ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_succeed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
captcha_valid;
|
||||
true ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
captcha_non_valid
|
||||
end;
|
||||
_ ->
|
||||
captcha_not_found
|
||||
end).
|
||||
case string:tokens(Id, "-") of
|
||||
[_, NodeID] ->
|
||||
case ejabberd_cluster:get_node_by_id(NodeID) of
|
||||
Node when Node == node() ->
|
||||
do_check_captcha(Id, ProvidedKey);
|
||||
Node ->
|
||||
case catch rpc:call(Node, ?MODULE, check_captcha,
|
||||
[Id, ProvidedKey], ?RPC_TIMEOUT) of
|
||||
{'EXIT', _} ->
|
||||
captcha_not_found;
|
||||
{badrpc, _} ->
|
||||
captcha_not_found;
|
||||
Res ->
|
||||
Res
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
captcha_not_found
|
||||
end.
|
||||
|
||||
|
||||
process_reply({xmlelement, _, _, _} = El) ->
|
||||
case xml:get_subtag(El, "x") of
|
||||
false ->
|
||||
process_reply(El) ->
|
||||
case exmpp_xml:get_element(El, x) of
|
||||
undefined ->
|
||||
{error, malformed};
|
||||
Xdata ->
|
||||
Fields = jlib:parse_xdata_submit(Xdata),
|
||||
case catch {proplists:get_value("challenge", Fields),
|
||||
proplists:get_value("ocr", Fields)} of
|
||||
{[Id|_], [OCR|_]} ->
|
||||
?T(case mnesia:read(captcha, Id, write) of
|
||||
[#captcha{pid=Pid, args=Args, key=Key, tref=Tref}] ->
|
||||
mnesia:delete({captcha, Id}),
|
||||
erlang:cancel_timer(Tref),
|
||||
if OCR == Key ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_succeed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
ok;
|
||||
true ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{error, bad_match}
|
||||
end;
|
||||
_ ->
|
||||
{error, not_found}
|
||||
end);
|
||||
case check_captcha(Id, OCR) of
|
||||
captcha_valid ->
|
||||
ok;
|
||||
captcha_non_valid ->
|
||||
{error, bad_match};
|
||||
captcha_not_found ->
|
||||
{error, not_found}
|
||||
end;
|
||||
_ ->
|
||||
{error, malformed}
|
||||
end
|
||||
end;
|
||||
process_reply(_) ->
|
||||
{error, malformed}.
|
||||
end.
|
||||
|
||||
|
||||
process(_Handlers, #request{method='GET', lang=Lang, path=[_, Id]}) ->
|
||||
case build_captcha_html(Id, Lang) of
|
||||
{FormEl, _} when is_tuple(FormEl) ->
|
||||
{FormEl, CaptchaTuple} when is_tuple(CaptchaTuple) ->
|
||||
Form =
|
||||
{xmlelement, "div", [{"align", "center"}],
|
||||
[FormEl]},
|
||||
%{xmlelement, "div", [{"align", "center"}],
|
||||
%[FormEl]},
|
||||
#xmlel{name = 'div',
|
||||
attrs = [
|
||||
#xmlattr{name = <<"align">>,
|
||||
value = <<"center">>
|
||||
}
|
||||
],
|
||||
children = [FormEl]
|
||||
},
|
||||
ejabberd_web:make_xhtml([Form]);
|
||||
captcha_not_found ->
|
||||
ejabberd_web:error(not_found)
|
||||
end;
|
||||
|
||||
process(_Handlers, #request{method='GET', path=[_, Id, "image"], ip = IP}) ->
|
||||
{Addr, _Port} = IP,
|
||||
case mnesia:dirty_read(captcha, Id) of
|
||||
[#captcha{key=Key}] ->
|
||||
case create_image(Addr, Key) of
|
||||
process(_Handlers, #request{method='GET', path=[_, Id, "image"]}) ->
|
||||
case lookup_captcha(Id) of
|
||||
{ok, #captcha{key=Key}} ->
|
||||
case create_image(Key) of
|
||||
{ok, Type, _, Img} ->
|
||||
{200,
|
||||
[{"Content-Type", Type},
|
||||
{"Cache-Control", "no-cache"},
|
||||
{"Last-Modified", httpd_util:rfc1123_date()}],
|
||||
Img};
|
||||
{error, limit} ->
|
||||
ejabberd_web:error(not_allowed);
|
||||
_ ->
|
||||
ejabberd_web:error(not_found)
|
||||
end;
|
||||
@@ -302,10 +514,15 @@ process(_Handlers, #request{method='POST', q=Q, lang=Lang, path=[_, Id]}) ->
|
||||
case check_captcha(Id, ProvidedKey) of
|
||||
captcha_valid ->
|
||||
Form =
|
||||
{xmlelement, "p", [],
|
||||
[{xmlcdata,
|
||||
translate:translate(Lang, "The CAPTCHA is valid.")
|
||||
}]},
|
||||
%{xmlelement, "p", [],
|
||||
% [{xmlcdata,
|
||||
% translate:translate(Lang, "The captcha is valid.")
|
||||
% }]},
|
||||
#xmlel{name = 'p',
|
||||
children = [
|
||||
#xmlcdata{cdata = list_to_binary(translate:translate(Lang, "The captcha is valid."))}
|
||||
]
|
||||
},
|
||||
ejabberd_web:make_xhtml([Form]);
|
||||
captcha_non_valid ->
|
||||
ejabberd_web:error(not_allowed);
|
||||
@@ -316,32 +533,15 @@ process(_Handlers, #request{method='POST', q=Q, lang=Lang, path=[_, Id]}) ->
|
||||
process(_Handlers, _Request) ->
|
||||
ejabberd_web:error(not_found).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([]) ->
|
||||
mnesia:create_table(captcha,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, captcha)}]),
|
||||
mnesia:add_table_copy(captcha, node(), ram_copies),
|
||||
mnesia:delete_table(captcha),
|
||||
ets:new(captcha, [named_table, public, {keypos, #captcha.id}]),
|
||||
check_captcha_setup(),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call({is_limited, Limiter, RateLimit}, _From, State) ->
|
||||
NowPriority = now_priority(),
|
||||
CleanPriority = NowPriority + ?LIMIT_PERIOD,
|
||||
Limits = clean_treap(State#state.limits, CleanPriority),
|
||||
case treap:lookup(Limiter, Limits) of
|
||||
{ok, _, Rate} when Rate >= RateLimit ->
|
||||
{reply, true, State#state{limits = Limits}};
|
||||
{ok, Priority, Rate} ->
|
||||
NewLimits = treap:insert(Limiter, Priority, Rate+1, Limits),
|
||||
{reply, false, State#state{limits = NewLimits}};
|
||||
_ ->
|
||||
NewLimits = treap:insert(Limiter, NowPriority, 1, Limits),
|
||||
{reply, false, State#state{limits = NewLimits}}
|
||||
end;
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, bad_request, State}.
|
||||
|
||||
@@ -350,17 +550,17 @@ handle_cast(_Msg, State) ->
|
||||
|
||||
handle_info({remove_id, Id}, State) ->
|
||||
?DEBUG("captcha ~p timed out", [Id]),
|
||||
_ = ?T(case mnesia:read(captcha, Id, write) of
|
||||
[#captcha{args=Args, pid=Pid}] ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
mnesia:delete({captcha, Id});
|
||||
_ ->
|
||||
ok
|
||||
end),
|
||||
case ets:lookup(captcha, Id) of
|
||||
[#captcha{args=Args, pid=Pid}] ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
ets:delete(captcha, Id);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State};
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
@@ -378,36 +578,25 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: create_image() -> {ok, Type, Key, Image} | {error, Reason}
|
||||
%% Type = "image/png" | "image/jpeg" | "image/gif"
|
||||
%% Key = string()
|
||||
%% Key = binary()
|
||||
%% Image = binary()
|
||||
%% Reason = atom()
|
||||
%%--------------------------------------------------------------------
|
||||
create_image() ->
|
||||
create_image(undefined).
|
||||
|
||||
create_image(Limiter) ->
|
||||
%% Six numbers from 1 to 9.
|
||||
Key = string:substr(randoms:get_string(), 1, 6),
|
||||
create_image(Limiter, Key).
|
||||
create_image(Key).
|
||||
|
||||
create_image(Limiter, Key) ->
|
||||
case is_limited(Limiter) of
|
||||
true ->
|
||||
{error, limit};
|
||||
false ->
|
||||
do_create_image(Key)
|
||||
end.
|
||||
|
||||
do_create_image(Key) ->
|
||||
create_image(Key) ->
|
||||
FileName = get_prog_name(),
|
||||
Cmd = lists:flatten(io_lib:format("~s ~s", [FileName, Key])),
|
||||
case cmd(Cmd) of
|
||||
{ok, <<16#89, $P, $N, $G, $\r, $\n, 16#1a, $\n, _/binary>> = Img} ->
|
||||
{ok, "image/png", Key, Img};
|
||||
{ok, <<"image/png">>, Key, Img};
|
||||
{ok, <<16#ff, 16#d8, _/binary>> = Img} ->
|
||||
{ok, "image/jpeg", Key, Img};
|
||||
{ok, <<"image/jpeg">>, Key, Img};
|
||||
{ok, <<$G, $I, $F, $8, X, $a, _/binary>> = Img} when X==$7; X==$9 ->
|
||||
{ok, "image/gif", Key, Img};
|
||||
{ok, <<"image/gif">>, Key, Img};
|
||||
{error, enodata = Reason} ->
|
||||
?ERROR_MSG("Failed to process output from \"~s\". "
|
||||
"Maybe ImageMagick's Convert program is not installed.",
|
||||
@@ -428,12 +617,13 @@ get_prog_name() ->
|
||||
case ejabberd_config:get_local_option(captcha_cmd) of
|
||||
FileName when is_list(FileName) ->
|
||||
FileName;
|
||||
Value when (Value == undefined) or (Value == "") ->
|
||||
_ ->
|
||||
?DEBUG("The option captcha_cmd is not configured, but some "
|
||||
"module wants to use the CAPTCHA feature.", []),
|
||||
false
|
||||
throw({error, option_not_configured_captcha_cmd})
|
||||
end.
|
||||
|
||||
%% @doc (Str::string()) -> string()
|
||||
get_url(Str) ->
|
||||
CaptchaHost = ejabberd_config:get_local_option(captcha_host),
|
||||
case string:tokens(CaptchaHost, ":") of
|
||||
@@ -485,25 +675,6 @@ get_captcha_transfer_protocol([{{_Port, _Ip, tcp}, ejabberd_http, Opts}
|
||||
get_captcha_transfer_protocol([_ | Listeners]) ->
|
||||
get_captcha_transfer_protocol(Listeners).
|
||||
|
||||
is_limited(undefined) ->
|
||||
false;
|
||||
is_limited(Limiter) ->
|
||||
case ejabberd_config:get_local_option(captcha_limit) of
|
||||
Int when is_integer(Int), Int > 0 ->
|
||||
case catch gen_server:call(?MODULE, {is_limited, Limiter, Int},
|
||||
5000) of
|
||||
true ->
|
||||
true;
|
||||
false ->
|
||||
false;
|
||||
Err ->
|
||||
?ERROR_MSG("Call failed: ~p", [Err]),
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: cmd(Cmd) -> Data | {error, Reason}
|
||||
%% Cmd = string()
|
||||
@@ -552,41 +723,76 @@ return(Port, TRef, Result) ->
|
||||
catch port_close(Port),
|
||||
Result.
|
||||
|
||||
is_feature_enabled() ->
|
||||
try get_prog_name() of
|
||||
Prog when is_list(Prog) -> true
|
||||
catch
|
||||
_:_ -> false
|
||||
end.
|
||||
|
||||
is_feature_available() ->
|
||||
case get_prog_name() of
|
||||
Prog when is_list(Prog) -> true;
|
||||
false -> false
|
||||
case is_feature_enabled() of
|
||||
false -> false;
|
||||
true ->
|
||||
case create_image() of
|
||||
{ok, _, _, _} -> true;
|
||||
_Error -> false
|
||||
end
|
||||
end.
|
||||
|
||||
check_captcha_setup() ->
|
||||
case is_feature_available() of
|
||||
case is_feature_enabled() andalso not is_feature_available() of
|
||||
true ->
|
||||
case create_image() of
|
||||
{ok, _, _, _} ->
|
||||
ok;
|
||||
_Err ->
|
||||
?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, "
|
||||
"but it can't generate images.", []),
|
||||
throw({error, captcha_cmd_enabled_but_fails})
|
||||
end;
|
||||
?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, "
|
||||
"but it can't generate images.", []);
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
clean_treap(Treap, CleanPriority) ->
|
||||
case treap:is_empty(Treap) of
|
||||
true ->
|
||||
Treap;
|
||||
false ->
|
||||
{_Key, Priority, _Value} = treap:get_root(Treap),
|
||||
if
|
||||
Priority > CleanPriority ->
|
||||
clean_treap(treap:delete_root(Treap), CleanPriority);
|
||||
true ->
|
||||
Treap
|
||||
end
|
||||
lookup_captcha(Id) ->
|
||||
case string:tokens(Id, "-") of
|
||||
[_, NodeID] ->
|
||||
case ejabberd_cluster:get_node_by_id(NodeID) of
|
||||
Node when Node == node() ->
|
||||
case ets:lookup(captcha, Id) of
|
||||
[C] ->
|
||||
{ok, C};
|
||||
_ ->
|
||||
{error, enoent}
|
||||
end;
|
||||
Node ->
|
||||
case catch rpc:call(Node, ets, lookup,
|
||||
[captcha, Id], ?RPC_TIMEOUT) of
|
||||
[C] ->
|
||||
{ok, C};
|
||||
_ ->
|
||||
{error, enoent}
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
{error, enoent}
|
||||
end.
|
||||
|
||||
now_priority() ->
|
||||
{MSec, Sec, USec} = now(),
|
||||
-((MSec*1000000 + Sec)*1000000 + USec).
|
||||
do_check_captcha(Id, ProvidedKey) ->
|
||||
case ets:lookup(captcha, Id) of
|
||||
[#captcha{pid = Pid, args = Args, key = ValidKey, tref = Tref}] ->
|
||||
ets:delete(captcha, Id),
|
||||
erlang:cancel_timer(Tref),
|
||||
if ValidKey == ProvidedKey ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_succeed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
captcha_valid;
|
||||
true ->
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
captcha_non_valid
|
||||
end;
|
||||
_ ->
|
||||
captcha_not_found
|
||||
end.
|
||||
|
||||
@@ -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
|
||||
@@ -46,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) ->
|
||||
@@ -86,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})) of
|
||||
ejabberd_config:get_local_option
|
||||
({auth_method, Domain})) of
|
||||
true -> [get_db_type(DB)|Acc];
|
||||
_ -> Acc
|
||||
end
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : ejabberd_cluster.erl
|
||||
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Description :
|
||||
%%%
|
||||
%%% Created : 2 Apr 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_cluster).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-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]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-define(HASHTBL, nodes_hash).
|
||||
-define(HASHTBL_NEW, nodes_hash_new).
|
||||
-define(POINTS, 16).
|
||||
-define(REHASH_TIMEOUT, 5000).
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
get_node(Key) ->
|
||||
Hash = erlang:phash2(Key),
|
||||
get_node_by_hash(?HASHTBL, Hash).
|
||||
|
||||
get_node_new(Key) ->
|
||||
Hash = erlang:phash2(Key),
|
||||
get_node_by_hash(?HASHTBL_NEW, Hash).
|
||||
|
||||
get_nodes() ->
|
||||
%% TODO
|
||||
mnesia:system_info(running_db_nodes).
|
||||
|
||||
announce() ->
|
||||
gen_server:call(?MODULE, announce, infinity).
|
||||
|
||||
node_id() ->
|
||||
integer_to_list(erlang:phash2(node())).
|
||||
|
||||
rehash_timeout() ->
|
||||
?REHASH_TIMEOUT.
|
||||
|
||||
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()
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([]) ->
|
||||
net_kernel:monitor_nodes(true, [{node_type, visible}]),
|
||||
ets:new(?HASHTBL, [named_table, public, ordered_set]),
|
||||
ets:new(?HASHTBL_NEW, [named_table, public, ordered_set]),
|
||||
register_node(),
|
||||
AllNodes = mnesia:system_info(running_db_nodes),
|
||||
OtherNodes = case AllNodes of
|
||||
[_] ->
|
||||
AllNodes;
|
||||
_ ->
|
||||
AllNodes -- [node()]
|
||||
end,
|
||||
append_nodes(?HASHTBL, OtherNodes),
|
||||
append_nodes(?HASHTBL_NEW, AllNodes),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call(announce, _From, State) ->
|
||||
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,
|
||||
{reply, ok, State};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
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),
|
||||
{noreply, State};
|
||||
handle_info({nodedown, Node, _}, State) ->
|
||||
?INFO_MSG("node ~p goes down", [Node]),
|
||||
delete_node(?HASHTBL, Node),
|
||||
delete_node(?HASHTBL_NEW, Node),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
append_nodes(Tab, Nodes) ->
|
||||
lists:foreach(
|
||||
fun(Node) ->
|
||||
append_node(Tab, Node)
|
||||
end, Nodes).
|
||||
|
||||
append_node(Tab, Node) ->
|
||||
lists:foreach(
|
||||
fun(I) ->
|
||||
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}),
|
||||
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,
|
||||
if NodeHash == '$end_of_table' ->
|
||||
erlang:error(no_running_nodes);
|
||||
true ->
|
||||
case ets:lookup(Tab, NodeHash) of
|
||||
[] ->
|
||||
get_node_by_hash(Tab, Hash);
|
||||
[{_, Node}] ->
|
||||
Node
|
||||
end
|
||||
end.
|
||||
|
||||
register_node() ->
|
||||
global:register_name(list_to_atom(node_id()), self()).
|
||||
@@ -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
|
||||
@@ -381,17 +381,24 @@ check_auth(noauth) ->
|
||||
no_auth_provided;
|
||||
check_auth({User, Server, Password}) ->
|
||||
%% Check the account exists and password is valid
|
||||
case ejabberd_auth:check_password(User, Server, Password) of
|
||||
true -> {ok, User, Server};
|
||||
AccountPass = ejabberd_auth:get_password_s(User, Server),
|
||||
AccountPassMD5 = get_md5(AccountPass),
|
||||
case Password of
|
||||
AccountPass -> {ok, User, Server};
|
||||
AccountPassMD5 -> {ok, User, Server};
|
||||
_ -> throw({error, invalid_account_data})
|
||||
end.
|
||||
|
||||
get_md5(AccountPass) ->
|
||||
lists:flatten([io_lib:format("~.16B", [X])
|
||||
|| X <- binary_to_list(crypto:md5(AccountPass))]).
|
||||
|
||||
check_access(all, _) ->
|
||||
true;
|
||||
check_access(Access, Auth) ->
|
||||
{ok, User, Server} = check_auth(Auth),
|
||||
%% Check this user has access permission
|
||||
case acl:match_rule(Server, Access, jlib:make_jid(User, Server, "")) of
|
||||
case acl:match_rule(Server, Access, exmpp_jid:make(User, Server, "")) of
|
||||
allow -> true;
|
||||
deny -> false
|
||||
end.
|
||||
|
||||
@@ -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
|
||||
|
||||
+193
-46
@@ -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,16 +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,
|
||||
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]).
|
||||
|
||||
-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()}
|
||||
|
||||
@@ -151,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) ->
|
||||
@@ -159,15 +184,38 @@ normalize_hosts(Hosts) ->
|
||||
normalize_hosts([], PrepHosts) ->
|
||||
lists:reverse(PrepHosts);
|
||||
normalize_hosts([Host|Hosts], PrepHosts) ->
|
||||
case jlib:nodeprep(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
|
||||
@@ -374,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(
|
||||
@@ -396,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} ->
|
||||
@@ -430,22 +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);
|
||||
{_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) ->
|
||||
@@ -467,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.
|
||||
@@ -572,20 +625,47 @@ 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).
|
||||
|
||||
del_local_option(Opt) ->
|
||||
mnesia:transaction(fun() ->
|
||||
mnesia:delete({local_config, Opt})
|
||||
end).
|
||||
|
||||
|
||||
get_global_option({Opt1, Host} = Opt) when is_list(Host) ->
|
||||
case ets:lookup(config, Opt) of
|
||||
[#config{value = Val}] ->
|
||||
Val;
|
||||
_ ->
|
||||
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.
|
||||
|
||||
get_local_option({Opt1, Host} = Opt) when is_list(Host) ->
|
||||
case ets:lookup(local_config, Opt) of
|
||||
[#local_config{value = Val}] ->
|
||||
Val;
|
||||
_ ->
|
||||
get_local_option({Opt1, global})
|
||||
end;
|
||||
get_local_option(Opt) ->
|
||||
case ets:lookup(local_config, Opt) of
|
||||
[#local_config{value = Val}] ->
|
||||
@@ -594,6 +674,17 @@ get_local_option(Opt) ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
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) ->
|
||||
mnesia:dirty_select(local_config,
|
||||
@@ -613,29 +704,85 @@ is_file_readable(Path) ->
|
||||
false
|
||||
end.
|
||||
|
||||
replace_module(mod_announce_odbc) -> {mod_announce, odbc};
|
||||
replace_module(mod_blocking_odbc) -> {mod_blocking, odbc};
|
||||
replace_module(mod_irc_odbc) -> {mod_irc, odbc};
|
||||
replace_module(mod_last_odbc) -> {mod_last, odbc};
|
||||
replace_module(mod_muc_odbc) -> {mod_muc, odbc};
|
||||
replace_module(mod_offline_odbc) -> {mod_offline, odbc};
|
||||
replace_module(mod_privacy_odbc) -> {mod_privacy, odbc};
|
||||
replace_module(mod_private_odbc) -> {mod_private, odbc};
|
||||
replace_module(mod_roster_odbc) -> {mod_roster, odbc};
|
||||
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, odbc};
|
||||
replace_module(mod_vcard_odbc) -> {mod_vcard, odbc};
|
||||
replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, odbc};
|
||||
replace_module(Module) -> Module.
|
||||
search(Pattern) ->
|
||||
{atomic, Res} = mnesia:transaction(fun mnesia:select/2, [local_config, Pattern]),
|
||||
Res.
|
||||
|
||||
replace_modules(Modules) ->
|
||||
lists:map(
|
||||
fun({Module, Opts}) ->
|
||||
case replace_module(Module) of
|
||||
{NewModule, DBType} ->
|
||||
NewOpts = [{db_type, DBType} |
|
||||
lists:keydelete(db_type, 1, Opts)],
|
||||
{NewModule, NewOpts};
|
||||
NewModule ->
|
||||
{NewModule, Opts}
|
||||
end
|
||||
end, Modules).
|
||||
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).
|
||||
|
||||
delete_host(Host) ->
|
||||
mnesia_delete_objects(for_host(Host)),
|
||||
ok.
|
||||
|
||||
configure_host(Host, Config) ->
|
||||
HostExistenceTerm = {{Host, host}, []},
|
||||
Records = host_terms_to_records(Host, [HostExistenceTerm | Config]),
|
||||
mnesia_write_objects(Records),
|
||||
ok.
|
||||
|
||||
host_terms_to_records(Host, Terms) ->
|
||||
lists:foldl(fun (Term, Acc) ->
|
||||
host_term_to_record(Term, Host, Acc)
|
||||
end, [], Terms).
|
||||
|
||||
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].
|
||||
|
||||
|
||||
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).
|
||||
|
||||
%% 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).
|
||||
|
||||
replace_storage_auth(_Host, [], Val2, Storage) ->
|
||||
{lists:reverse(Val2), Storage};
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
replace_storage_auth(Host, [Val | ValT], Val2, Storage)
|
||||
when (Val /= internal) and (Val /= odbc) ->
|
||||
replace_storage_auth(Host, ValT, [Val | Val2], Storage);
|
||||
|
||||
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}).
|
||||
|
||||
@@ -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
|
||||
@@ -21,8 +21,3 @@
|
||||
|
||||
-record(config, {key, value}).
|
||||
-record(local_config, {key, value}).
|
||||
-record(state, {opts = [],
|
||||
hosts = [],
|
||||
override_local = false,
|
||||
override_global = false,
|
||||
override_acls = false}).
|
||||
|
||||
+31
-45
@@ -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
|
||||
@@ -190,7 +190,7 @@ process(["help" | Mode]) ->
|
||||
print_usage_help(MaxC, ShCode),
|
||||
?STATUS_SUCCESS;
|
||||
[CmdString | _] ->
|
||||
CmdStringU = ejabberd_regexp:greplace(CmdString, "-", "_"),
|
||||
CmdStringU = re:replace(CmdString, "-", "_", [global, {return, list}]),
|
||||
print_usage_commands(CmdStringU, MaxC, ShCode),
|
||||
?STATUS_SUCCESS
|
||||
end;
|
||||
@@ -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,39 +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(CmdString, "-", "_"),
|
||||
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);
|
||||
%% This case clause is for Erlang R14 and older
|
||||
{'EXIT', {function_clause,[{lists,zip,[A1, A2]} | _]}} ->
|
||||
{NumCompa, TextCompa} =
|
||||
case {length(A1), length(A2)} of
|
||||
{L1, L2} when L1 < L2 -> {L2-L1, "less argument"};
|
||||
{L1, L2} when L1 > L2 -> {L1-L2, "more argument"}
|
||||
end,
|
||||
{io_lib:format("Error: the command ~p requires ~p ~s.",
|
||||
[CmdString, NumCompa, TextCompa]),
|
||||
wrong_command_arguments};
|
||||
%% This case clause is for Erlang R15 and newer
|
||||
{'EXIT', {function_clause,[{lists,zip,[A1, A2],_} | _]}} ->
|
||||
{NumCompa, TextCompa} =
|
||||
case {length(A1), length(A2)} of
|
||||
{L1, L2} when L1 < L2 -> {L2-L1, "less argument"};
|
||||
{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.
|
||||
|
||||
@@ -452,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)"},
|
||||
@@ -625,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).
|
||||
@@ -689,10 +674,10 @@ filter_commands(All, SubString) ->
|
||||
end.
|
||||
|
||||
filter_commands_regexp(All, Glob) ->
|
||||
RegExp = ejabberd_regexp:sh_to_awk(Glob),
|
||||
RegExp = xmerl_regexp:sh_to_awk(Glob),
|
||||
lists:filter(
|
||||
fun(Command) ->
|
||||
case ejabberd_regexp:run(Command, RegExp) of
|
||||
case re:run(Command, RegExp, [{capture, none}]) of
|
||||
match ->
|
||||
true;
|
||||
nomatch ->
|
||||
@@ -711,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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -45,6 +45,8 @@
|
||||
get_peer_certificate/1,
|
||||
get_verify_result/1,
|
||||
close/1,
|
||||
setopts/2,
|
||||
change_controller/2,
|
||||
sockname/1, peername/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
@@ -94,18 +96,15 @@ start(Module, SockMod, Socket, Opts) ->
|
||||
todo
|
||||
end.
|
||||
|
||||
starttls(FsmRef, _TLSOpts) ->
|
||||
%% TODO: Frontend improvements planned by Aleksey
|
||||
%%gen_server:call(FsmRef, {starttls, TLSOpts}),
|
||||
FsmRef.
|
||||
starttls(FsmRef, TLSOpts) ->
|
||||
starttls(FsmRef, TLSOpts, undefined).
|
||||
|
||||
starttls(FsmRef, TLSOpts, Data) ->
|
||||
gen_server:call(FsmRef, {starttls, TLSOpts, Data}),
|
||||
FsmRef.
|
||||
|
||||
compress(FsmRef) ->
|
||||
gen_server:call(FsmRef, compress),
|
||||
FsmRef.
|
||||
compress(FsmRef, undefined).
|
||||
|
||||
compress(FsmRef, Data) ->
|
||||
gen_server:call(FsmRef, {compress, Data}),
|
||||
@@ -138,11 +137,14 @@ close(FsmRef) ->
|
||||
sockname(FsmRef) ->
|
||||
gen_server:call(FsmRef, sockname).
|
||||
|
||||
peername(_FsmRef) ->
|
||||
%% TODO: Frontend improvements planned by Aleksey
|
||||
%%gen_server:call(FsmRef, peername).
|
||||
{ok, {{0, 0, 0, 0}, 0}}.
|
||||
setopts(FsmRef, Opts) ->
|
||||
gen_server:call(FsmRef, {setopts, Opts}).
|
||||
|
||||
change_controller(FsmRef, C2SPid) ->
|
||||
gen_server:call(FsmRef, {change_controller, C2SPid}).
|
||||
|
||||
peername(FsmRef) ->
|
||||
gen_server:call(FsmRef, peername).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
@@ -158,9 +160,16 @@ peername(_FsmRef) ->
|
||||
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
|
||||
end,
|
||||
{SockMod2, Socket2} = check_starttls(SockMod, Socket, Receiver, Opts),
|
||||
{ok, Pid} =
|
||||
rpc:call(Node, Module, start, [{?MODULE, self()}, Opts]),
|
||||
rpc:call(Node, Module, start,
|
||||
[{?MODULE, self()}, [{frontend_ip, IP} | Opts]]),
|
||||
ejabberd_receiver:become_controller(Receiver, Pid),
|
||||
{ok, #state{sockmod = SockMod2,
|
||||
socket = Socket2,
|
||||
@@ -175,38 +184,16 @@ init([Module, SockMod, Socket, Opts, Receiver]) ->
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call({starttls, TLSOpts}, _From, State) ->
|
||||
{ok, TLSSocket} = tls:tcp_to_tls(State#state.socket, TLSOpts),
|
||||
ejabberd_receiver:starttls(State#state.receiver, TLSSocket),
|
||||
Reply = ok,
|
||||
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call({starttls, TLSOpts, Data}, _From, State) ->
|
||||
{ok, TLSSocket} = tls:tcp_to_tls(State#state.socket, TLSOpts),
|
||||
ejabberd_receiver:starttls(State#state.receiver, TLSSocket),
|
||||
catch (State#state.sockmod):send(
|
||||
State#state.socket, Data),
|
||||
{ok, TLSSocket} = ejabberd_receiver:starttls(
|
||||
State#state.receiver, TLSOpts, Data),
|
||||
Reply = ok,
|
||||
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(compress, _From, State) ->
|
||||
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
|
||||
State#state.sockmod,
|
||||
State#state.socket),
|
||||
ejabberd_receiver:compress(State#state.receiver, ZlibSocket),
|
||||
Reply = ok,
|
||||
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call({compress, Data}, _From, State) ->
|
||||
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
|
||||
State#state.sockmod,
|
||||
State#state.socket),
|
||||
ejabberd_receiver:compress(State#state.receiver, ZlibSocket),
|
||||
catch (State#state.sockmod):send(
|
||||
State#state.socket, Data),
|
||||
{ok, ZlibSocket} = ejabberd_receiver:compress(
|
||||
State#state.receiver, Data),
|
||||
Reply = ok,
|
||||
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
@@ -246,13 +233,7 @@ handle_call(close, _From, State) ->
|
||||
|
||||
handle_call(sockname, _From, State) ->
|
||||
#state{sockmod = SockMod, socket = Socket} = State,
|
||||
Reply =
|
||||
case SockMod of
|
||||
gen_tcp ->
|
||||
inet:sockname(Socket);
|
||||
_ ->
|
||||
SockMod:sockname(Socket)
|
||||
end,
|
||||
Reply = peername(SockMod, Socket),
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
||||
handle_call(peername, _From, State) ->
|
||||
@@ -266,6 +247,14 @@ handle_call(peername, _From, State) ->
|
||||
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}.
|
||||
@@ -318,10 +307,16 @@ check_starttls(SockMod, Socket, Receiver, Opts) ->
|
||||
end, Opts),
|
||||
if
|
||||
TLSEnabled ->
|
||||
{ok, TLSSocket} = tls:tcp_to_tls(Socket, TLSOpts),
|
||||
ejabberd_receiver:starttls(Receiver, TLSSocket),
|
||||
{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)
|
||||
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.
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -0,0 +1,27 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% 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-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
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(global_route, {prefix, pids}).
|
||||
+50
-20
@@ -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
|
||||
@@ -81,7 +81,8 @@ add(Hook, Host, Function, Seq) when is_function(Function) ->
|
||||
add(Hook, Module, Function, Seq) ->
|
||||
add(Hook, global, Module, Function, Seq).
|
||||
|
||||
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}).
|
||||
|
||||
add_dist(Hook, Node, Module, Function, Seq) ->
|
||||
@@ -104,7 +105,8 @@ delete(Hook, Host, Function, Seq) when is_function(Function) ->
|
||||
delete(Hook, Module, Function, Seq) ->
|
||||
delete(Hook, global, Module, Function, Seq).
|
||||
|
||||
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}).
|
||||
|
||||
delete_dist(Hook, Node, Module, Function, Seq) ->
|
||||
@@ -120,11 +122,11 @@ run(Hook, Args) ->
|
||||
run(Hook, global, Args).
|
||||
|
||||
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 (Hook::atom(), Val, Args) -> Val | stopped | NewVal
|
||||
@@ -136,12 +138,14 @@ run(Hook, Host, Args) ->
|
||||
run_fold(Hook, Val, Args) ->
|
||||
run_fold(Hook, global, Val, Args).
|
||||
|
||||
%% @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.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -169,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
|
||||
@@ -177,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};
|
||||
@@ -205,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
|
||||
@@ -218,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
|
||||
@@ -295,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.
|
||||
@@ -342,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,478 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% 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_info(Host),
|
||||
?MODULE:register(Host, Config)
|
||||
end.
|
||||
|
||||
%% Removes a vhost from the system,
|
||||
%% XXX deleting all ODBC data.
|
||||
remove(Host) when is_list(Host) ->
|
||||
HostB = list_to_binary(Host),
|
||||
ejabberd_hooks:run(remove_host, HostB, [HostB]),
|
||||
remove_host_info(Host).
|
||||
|
||||
remove_host_info(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,
|
||||
ejabberd_auth:start(Host),
|
||||
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)),
|
||||
ejabberd_auth:stop(Host).
|
||||
|
||||
%% 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}.
|
||||
|
||||
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}}
|
||||
].
|
||||
@@ -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
|
||||
@@ -35,7 +35,8 @@
|
||||
stop_listener/2,
|
||||
parse_listener_portip/2,
|
||||
add_listener/3,
|
||||
delete_listener/2
|
||||
delete_listener/2,
|
||||
rate_limit/2
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -274,6 +275,9 @@ get_ip_tuple(IPOpt, _IPVOpt) ->
|
||||
IPOpt.
|
||||
|
||||
accept(ListenSocket, Module, Opts) ->
|
||||
accept(ListenSocket, Module, Opts, 0).
|
||||
accept(ListenSocket, Module, Opts, Interval) ->
|
||||
NewInterval = check_rate_limit(Interval),
|
||||
case gen_tcp:accept(ListenSocket) of
|
||||
{ok, Socket} ->
|
||||
case {inet:sockname(Socket), inet:peername(Socket)} of
|
||||
@@ -288,11 +292,11 @@ accept(ListenSocket, Module, Opts) ->
|
||||
false -> ejabberd_socket
|
||||
end,
|
||||
CallMod:start(strip_frontend(Module), gen_tcp, Socket, Opts),
|
||||
accept(ListenSocket, Module, Opts);
|
||||
accept(ListenSocket, Module, Opts, NewInterval);
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("(~w) Failed TCP accept: ~w",
|
||||
[ListenSocket, Reason]),
|
||||
accept(ListenSocket, Module, Opts)
|
||||
?INFO_MSG("(~w) Failed TCP accept: ~w",
|
||||
[ListenSocket, Reason]),
|
||||
accept(ListenSocket, Module, Opts, NewInterval)
|
||||
end.
|
||||
|
||||
udp_recv(Socket, Module, Opts) ->
|
||||
@@ -520,3 +524,39 @@ format_error(Reason) ->
|
||||
ReasonStr ->
|
||||
ReasonStr
|
||||
end.
|
||||
|
||||
%% Set interval between two accepts on given port
|
||||
rate_limit([], _Interval) ->
|
||||
ok;
|
||||
rate_limit([Port|Ports], Interval) ->
|
||||
rate_limit(Port, Interval),
|
||||
rate_limit(Ports, Interval);
|
||||
rate_limit(Port, Interval) ->
|
||||
case get_listener_pid_by_port(Port) of
|
||||
undefined -> no_listener;
|
||||
Pid -> Pid ! {rate_limit, Interval}, ok
|
||||
end.
|
||||
|
||||
get_listener_pid_by_port(Port) ->
|
||||
ListenerPids = [Pid || {{P,_,_},Pid,_,_} <-
|
||||
supervisor:which_children(erlang:whereis(ejabberd_listeners)),
|
||||
P == Port],
|
||||
ListenerPid = case ListenerPids of
|
||||
[] -> undefined;
|
||||
[LPid|_] -> LPid
|
||||
end,
|
||||
ListenerPid.
|
||||
|
||||
check_rate_limit(Interval) ->
|
||||
NewInterval = receive
|
||||
{rate_limit, AcceptInterval} ->
|
||||
AcceptInterval
|
||||
after 0 ->
|
||||
Interval
|
||||
end,
|
||||
case NewInterval of
|
||||
0 -> ok;
|
||||
Ms ->
|
||||
timer:sleep(Ms)
|
||||
end,
|
||||
NewInterval.
|
||||
|
||||
+93
-77
@@ -5,7 +5,7 @@
|
||||
%%% Created : 30 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
|
||||
@@ -50,8 +50,9 @@
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
@@ -62,6 +63,12 @@
|
||||
%% This value is used in SIP and Megaco for a transaction lifetime.
|
||||
-define(IQ_TIMEOUT, 32000).
|
||||
|
||||
% These are the namespace already declared by the stream opening. This is
|
||||
% used at serialization time.
|
||||
-define(DEFAULT_NS, ?NS_JABBER_CLIENT).
|
||||
-define(PREFIXED_NS,
|
||||
[{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}]).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@@ -73,33 +80,38 @@ start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
process_iq(From, To, Packet) ->
|
||||
IQ = jlib:iq_query_info(Packet),
|
||||
case IQ of
|
||||
#iq{xmlns = XMLNS} ->
|
||||
Host = To#jid.lserver,
|
||||
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
|
||||
case exmpp_iq:xmlel_to_iq(Packet) of
|
||||
#iq{kind = request, ns = XMLNS} = IQ_Rec ->
|
||||
Host = exmpp_jid:prep_domain(To),
|
||||
case ets:lookup(?IQTABLE, {XMLNS, ejabberd:normalize_host(Host)}) of
|
||||
[{_, Module, Function}] ->
|
||||
ResIQ = Module:Function(From, To, IQ),
|
||||
ResIQ = Module:Function(From, To, IQ_Rec),
|
||||
if
|
||||
ResIQ /= ignore ->
|
||||
ejabberd_router:route(
|
||||
To, From, jlib:iq_to_xml(ResIQ));
|
||||
Reply = exmpp_iq:iq_to_xmlel(ResIQ, To, From),
|
||||
ejabberd_router:route(To, From, Reply);
|
||||
true ->
|
||||
ok
|
||||
end;
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, Opts,
|
||||
From, To, IQ);
|
||||
From, To, IQ_Rec);
|
||||
[] ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_FEATURE_NOT_IMPLEMENTED),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
case ets:lookup(?IQTABLE, {XMLNS, global}) of
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:handle(
|
||||
global, Module, Function, Opts,
|
||||
From, To, IQ_Rec);
|
||||
[] ->
|
||||
Err = exmpp_iq:error(Packet, 'feature-not-implemented'),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end
|
||||
end;
|
||||
reply ->
|
||||
IQReply = jlib:iq_query_or_response_info(Packet),
|
||||
process_iq_reply(From, To, IQReply);
|
||||
#iq{kind = response} = IQReply ->
|
||||
%%IQReply = jlib:iq_query_or_response_info(IQ_Rec),
|
||||
process_iq_reply(From, To, IQReply);
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
|
||||
Err = exmpp_iq:error(Packet, 'bad-request'),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
ok
|
||||
end.
|
||||
@@ -115,7 +127,18 @@ process_iq_reply(From, To, #iq{id = ID} = IQ) ->
|
||||
_ ->
|
||||
nothing
|
||||
end.
|
||||
|
||||
|
||||
%% #xmlelement{} used for retro-compatibility
|
||||
route(FromOld, ToOld, #xmlelement{} = PacketOld) ->
|
||||
catch throw(for_stacktrace), % To have a stacktrace.
|
||||
io:format("~nLOCAL: old #xmlelement:~n~p~n~p~n~n",
|
||||
[PacketOld, erlang:get_stacktrace()]),
|
||||
% XXX OLD FORMAT: From, To, Packet.
|
||||
From = jlib:from_old_jid(FromOld),
|
||||
To = jlib:from_old_jid(ToOld),
|
||||
Packet = exmpp_xml:xmlelement_to_xmlel(PacketOld, [?NS_JABBER_CLIENT],
|
||||
[{?NS_XMPP, ?NS_XMPP_pfx}]),
|
||||
route(From, To, Packet);
|
||||
route(From, To, Packet) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
@@ -130,12 +153,12 @@ route_iq(From, To, IQ, F) ->
|
||||
|
||||
route_iq(From, To, #iq{type = Type} = IQ, F, Timeout) when is_function(F) ->
|
||||
Packet = if Type == set; Type == get ->
|
||||
ID = randoms:get_string(),
|
||||
Host = From#jid.lserver,
|
||||
ID = list_to_binary(ejabberd_router:make_id()),
|
||||
Host = exmpp_jid:prep_domain(From),
|
||||
register_iq_response_handler(Host, ID, undefined, F, Timeout),
|
||||
jlib:iq_to_xml(IQ#iq{id = ID});
|
||||
exmpp_iq:iq_to_xmlel(IQ#iq{id = ID});
|
||||
true ->
|
||||
jlib:iq_to_xml(IQ)
|
||||
exmpp_iq:iq_to_xmlel(IQ)
|
||||
end,
|
||||
ejabberd_router:route(From, To, Packet).
|
||||
|
||||
@@ -150,10 +173,10 @@ register_iq_response_handler(_Host, ID, Module, Function, Timeout0) ->
|
||||
N
|
||||
end,
|
||||
TRef = erlang:start_timer(Timeout, ejabberd_local, ID),
|
||||
mnesia:dirty_write(#iq_response{id = ID,
|
||||
module = Module,
|
||||
function = Function,
|
||||
timer = TRef}).
|
||||
ets:insert(iq_response, #iq_response{id = ID,
|
||||
module = Module,
|
||||
function = Function,
|
||||
timer = TRef}).
|
||||
|
||||
register_iq_handler(Host, XMLNS, Module, Fun) ->
|
||||
ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun}.
|
||||
@@ -172,7 +195,7 @@ refresh_iq_handlers() ->
|
||||
ejabberd_local ! refresh_iq_handlers.
|
||||
|
||||
bounce_resource_packet(From, To, Packet) ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_ITEM_NOT_FOUND),
|
||||
Err = exmpp_stanza:reply_with_error(Packet, 'item-not-found'),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
stop.
|
||||
|
||||
@@ -190,16 +213,14 @@ bounce_resource_packet(From, To, Packet) ->
|
||||
init([]) ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
ejabberd_router:register_route(Host, {apply, ?MODULE, route}),
|
||||
ejabberd_hooks:add(local_send_to_resource_hook, Host,
|
||||
?MODULE, bounce_resource_packet, 100)
|
||||
ejabberd_router:register_route(Host, {apply, ?MODULE, route})
|
||||
end, ?MYHOSTS),
|
||||
ejabberd_hooks:add(local_send_to_resource_hook, global,
|
||||
?MODULE, bounce_resource_packet, 100),
|
||||
catch ets:new(?IQTABLE, [named_table, public]),
|
||||
update_table(),
|
||||
mnesia:create_table(iq_response,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, iq_response)}]),
|
||||
mnesia:add_table_copy(iq_response, node(), ram_copies),
|
||||
mnesia:delete_table(iq_response),
|
||||
catch ets:new(iq_response, [named_table, public,
|
||||
{keypos, #iq_response.id}]),
|
||||
{ok, #state{}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -230,6 +251,18 @@ handle_cast(_Msg, State) ->
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
%% #xmlelement{} used for retro-compatibility
|
||||
handle_info({route, FromOld, ToOld, #xmlelement{} = PacketOld}, State) ->
|
||||
catch throw(for_stacktrace), % To have a stacktrace.
|
||||
io:format("~nLOCAL: old #xmlelement:~n~p~n~p~n~n",
|
||||
[PacketOld, erlang:get_stacktrace()]),
|
||||
% XXX OLD FORMAT: From, To, Packet.
|
||||
From = jlib:from_old_jid(FromOld),
|
||||
To = jlib:from_old_jid(ToOld),
|
||||
Packet = exmpp_xml:xmlelement_to_xmlel(PacketOld, [?NS_JABBER_CLIENT],
|
||||
[{?NS_XMPP, ?NS_XMPP_pfx}]),
|
||||
handle_info({route, From, To, Packet}, State);
|
||||
handle_info({route, From, To, Packet}, State) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
@@ -240,21 +273,21 @@ handle_info({route, From, To, Packet}, State) ->
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
|
||||
ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}),
|
||||
ets:insert(?IQTABLE, {{XMLNS, ejabberd:normalize_host(Host)}, Module, Function}),
|
||||
catch mod_disco:register_feature(Host, XMLNS),
|
||||
{noreply, State};
|
||||
handle_info({register_iq_handler, Host, XMLNS, Module, Function, Opts}, State) ->
|
||||
ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function, Opts}),
|
||||
ets:insert(?IQTABLE, {{XMLNS, ejabberd:normalize_host(Host)}, Module, Function, Opts}),
|
||||
catch mod_disco:register_feature(Host, XMLNS),
|
||||
{noreply, State};
|
||||
handle_info({unregister_iq_handler, Host, XMLNS}, State) ->
|
||||
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
|
||||
case ets:lookup(?IQTABLE, {XMLNS, ejabberd:normalize_host(Host)}) of
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:stop_iq_handler(Module, Function, Opts);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ets:delete(?IQTABLE, {XMLNS, Host}),
|
||||
ets:delete(?IQTABLE, {XMLNS, ejabberd:normalize_host(Host)}),
|
||||
catch mod_disco:unregister_feature(Host, XMLNS),
|
||||
{noreply, State};
|
||||
handle_info(refresh_iq_handlers, State) ->
|
||||
@@ -271,7 +304,7 @@ handle_info(refresh_iq_handlers, State) ->
|
||||
end, ets:tab2list(?IQTABLE)),
|
||||
{noreply, State};
|
||||
handle_info({timeout, _TRef, ID}, State) ->
|
||||
process_iq_timeout(ID),
|
||||
spawn(fun() -> process_iq_timeout(ID) end),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
@@ -299,67 +332,50 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
do_route(From, To, Packet) ->
|
||||
?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
|
||||
[From, To, Packet, 8]),
|
||||
|
||||
LNode = exmpp_jid:prep_node(To),
|
||||
LResource = exmpp_jid:prep_resource(To),
|
||||
if
|
||||
To#jid.luser /= "" ->
|
||||
LNode /= undefined ->
|
||||
ejabberd_sm:route(From, To, Packet);
|
||||
To#jid.lresource == "" ->
|
||||
{xmlelement, Name, _Attrs, _Els} = Packet,
|
||||
case Name of
|
||||
"iq" ->
|
||||
LResource == undefined ->
|
||||
case Packet of
|
||||
_ when ?IS_IQ(Packet) ->
|
||||
process_iq(From, To, Packet);
|
||||
"message" ->
|
||||
_ when ?IS_MESSAGE(Packet) ->
|
||||
ok;
|
||||
"presence" ->
|
||||
_ when ?IS_PRESENCE(Packet) ->
|
||||
ok;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
true ->
|
||||
{xmlelement, _Name, Attrs, _Els} = Packet,
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
"error" -> ok;
|
||||
"result" -> ok;
|
||||
case exmpp_stanza:get_type(Packet) of
|
||||
<<"error">> -> ok;
|
||||
<<"result">> -> ok;
|
||||
_ ->
|
||||
ejabberd_hooks:run(local_send_to_resource_hook,
|
||||
To#jid.lserver,
|
||||
exmpp_jid:prep_domain(To),
|
||||
[From, To, Packet])
|
||||
end
|
||||
end.
|
||||
|
||||
update_table() ->
|
||||
case catch mnesia:table_info(iq_response, attributes) of
|
||||
[id, module, function] ->
|
||||
mnesia:delete_table(iq_response);
|
||||
[id, module, function, timer] ->
|
||||
ok;
|
||||
{'EXIT', _} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
get_iq_callback(ID) ->
|
||||
case mnesia:dirty_read(iq_response, ID) of
|
||||
case ets:lookup(iq_response, ID) of
|
||||
[#iq_response{module = Module, timer = TRef,
|
||||
function = Function}] ->
|
||||
cancel_timer(TRef),
|
||||
mnesia:dirty_delete(iq_response, ID),
|
||||
ets:delete(iq_response, ID),
|
||||
{ok, Module, Function};
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
process_iq_timeout(ID) ->
|
||||
spawn(fun process_iq_timeout/0) ! ID.
|
||||
|
||||
process_iq_timeout() ->
|
||||
receive
|
||||
ID ->
|
||||
case get_iq_callback(ID) of
|
||||
{ok, undefined, Function} ->
|
||||
Function(timeout);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
after 5000 ->
|
||||
case get_iq_callback(ID) of
|
||||
{ok, undefined, Function} ->
|
||||
Function(timeout);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 23 Oct 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
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
%%% Created : 29 Nov 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
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
-export([set/1,
|
||||
get/0,
|
||||
get_default/0,
|
||||
set_custom/2,
|
||||
clear_custom/0,
|
||||
clear_custom/1]).
|
||||
@@ -67,6 +68,11 @@
|
||||
%% @spec () -> {DefaultLevelOrdinal::integer(), [{Module::atom(), LevelOrdinal::integer()}]}
|
||||
%% @doc Get the default and all custom levels
|
||||
get() ->
|
||||
ejabberd_logger:get().
|
||||
|
||||
%% @spec () -> {Ordinal::integer(), Name::atom(), Description::string()}
|
||||
%% @doc Get the default level
|
||||
get_default() ->
|
||||
{DefaultLevel, _CustomLevels} = ejabberd_logger:get(),
|
||||
case lists:keysearch(DefaultLevel, #loglevel.ordinal, ?LOG_LEVELS) of
|
||||
{value, Result = #loglevel{}} ->
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 1 Nov 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
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
%% API
|
||||
-export([start_link/0,
|
||||
start/0,
|
||||
join/1,
|
||||
leave/1,
|
||||
get_members/1,
|
||||
@@ -40,14 +41,14 @@
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-record(state, {groups = []}).
|
||||
|
||||
-ifdef(SSL40).
|
||||
-define(PG2, pg2).
|
||||
-else.
|
||||
-define(PG2, pg2_backport).
|
||||
-endif.
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@@ -55,6 +56,15 @@
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start() ->
|
||||
ChildSpec = {?MODULE,
|
||||
{?MODULE, start_link, []},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
@@ -87,30 +97,19 @@ get_closest_node(Name) ->
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
{FE, BE} =
|
||||
Groups =
|
||||
case ejabberd_config:get_local_option(node_type) of
|
||||
frontend ->
|
||||
{true, false};
|
||||
[frontend];
|
||||
backend ->
|
||||
{false, true};
|
||||
[backend];
|
||||
generic ->
|
||||
{true, true};
|
||||
[frontend, backend];
|
||||
undefined ->
|
||||
{true, true}
|
||||
[frontend, backend]
|
||||
end,
|
||||
if
|
||||
FE ->
|
||||
join(frontend);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if
|
||||
BE ->
|
||||
join(backend);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{ok, #state{}}.
|
||||
lists:foreach(fun join/1, Groups),
|
||||
{ok, #state{groups = Groups}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
@@ -150,7 +149,8 @@ handle_info(_Info, State) ->
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
terminate(_Reason, #state{groups = Groups}) ->
|
||||
lists:foreach(fun leave/1, Groups),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
+127
-173
@@ -5,7 +5,7 @@
|
||||
%%% Created : 17 Jul 2008 by Pablo Polvorin <pablo.polvorin@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,7 +25,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%% Not implemented:
|
||||
%%% - write mod_piefxis with ejabberdctl commands
|
||||
%%% - Export from mod_offline_odbc.erl
|
||||
%%% - Export from mod_private_odbc.erl
|
||||
%%% - XEP-227: 6. Security Considerations
|
||||
@@ -41,45 +40,11 @@
|
||||
-record(parsing_state, {parser, host, dir}).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
%%-include_lib("exmpp/include/exmpp.hrl").
|
||||
%%-include_lib("exmpp/include/exmpp_client.hrl").
|
||||
%% Copied from exmpp header files:
|
||||
-define(NS_ROSTER, "jabber:iq:roster").
|
||||
-define(NS_VCARD, "vcard-temp").
|
||||
-record(xmlcdata, {
|
||||
cdata = <<>>
|
||||
}).
|
||||
-record(xmlattr, {
|
||||
ns = undefined,
|
||||
name,
|
||||
value
|
||||
}).
|
||||
-record(xmlel, {
|
||||
ns = undefined,
|
||||
declared_ns = [],
|
||||
name,
|
||||
attrs = [],
|
||||
children = []
|
||||
}).
|
||||
-record(iq, {
|
||||
kind,
|
||||
type,
|
||||
id,
|
||||
ns,
|
||||
payload,
|
||||
error,
|
||||
lang,
|
||||
iq_ns
|
||||
}).
|
||||
-record(xmlendtag, {
|
||||
ns = undefined,
|
||||
name
|
||||
}).
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
-include_lib("exmpp/include/exmpp_client.hrl").
|
||||
|
||||
%% Copied from mod_private.erl
|
||||
-record(private_storage, {usns, xml}).
|
||||
-record(private_storage, {user_host_ns, xml}).
|
||||
|
||||
%%-define(ERROR_MSG(M,Args),io:format(M,Args)).
|
||||
%%-define(INFO_MSG(M,Args),ok).
|
||||
@@ -96,7 +61,6 @@
|
||||
%%%% Import file
|
||||
|
||||
import_file(FileName) ->
|
||||
_ = #xmlattr{}, %% this stupid line is only to prevent compilation warning about "recod xmlattr is unused"
|
||||
import_file(FileName, 2).
|
||||
|
||||
import_file(FileName, RootDepth) ->
|
||||
@@ -159,7 +123,7 @@ process_element(El=#xmlel{name=user, ns=_XMLNS},
|
||||
State;
|
||||
|
||||
process_element(H=#xmlel{name=host},State) ->
|
||||
State#parsing_state{host=?BTL(exmpp_xml:get_attribute(H, <<"jid">>, none))};
|
||||
State#parsing_state{host=exmpp_xml:get_attribute(H,<<"jid">>,none)};
|
||||
|
||||
process_element(#xmlel{name='server-data'},State) ->
|
||||
State;
|
||||
@@ -194,25 +158,23 @@ process_element(El,State) ->
|
||||
%%%% Add user
|
||||
|
||||
add_user(El, Domain) ->
|
||||
User = exmpp_xml:get_attribute(El, <<"name">>, none),
|
||||
PasswordFormat = exmpp_xml:get_attribute(El, <<"password-format">>, <<"plaintext">>),
|
||||
Password = exmpp_xml:get_attribute(El, <<"password">>, none),
|
||||
User = exmpp_xml:get_attribute(El,<<"name">>,none),
|
||||
PasswordFormat = exmpp_xml:get_attribute(El,<<"password-format">>,<<"plaintext">>),
|
||||
Password = exmpp_xml:get_attribute(El,<<"password">>,none),
|
||||
add_user(El, Domain, User, PasswordFormat, Password).
|
||||
|
||||
%% @spec (El::xmlel(), Domain::string(), User::binary(), Password::binary() | none)
|
||||
%% @spec (El::xmlel(), Domain::string(), User::binary(), PasswordFormat::binary(), Password::binary() | none)
|
||||
%% -> ok | {error, ErrorText::string()}
|
||||
%% @doc Add a new user to the database.
|
||||
%% If user already exists, it will be only updated.
|
||||
add_user(El, Domain, UserBinary, <<"plaintext">>, none) ->
|
||||
User = ?BTL(UserBinary),
|
||||
add_user(El, Domain, User, <<"plaintext">>, none) ->
|
||||
io:format("Account ~s@~s will not be created, updating it...~n",
|
||||
[User, Domain]),
|
||||
io:format(""),
|
||||
populate_user_with_elements(El, Domain, User),
|
||||
ok;
|
||||
add_user(El, Domain, UserBinary, PasswordFormat, PasswordBinary) ->
|
||||
User = ?BTL(UserBinary),
|
||||
Password2 = prepare_password(PasswordFormat, PasswordBinary, El),
|
||||
add_user(El, Domain, User, <<"scram">> = PasswordFormat, Password) ->
|
||||
Password2 = prepare_password(PasswordFormat, Password, El),
|
||||
case create_user(User,Password2,Domain) of
|
||||
ok ->
|
||||
populate_user_with_elements(El, Domain, User),
|
||||
@@ -254,7 +216,7 @@ populate_user_with_elements(El, Domain, User) ->
|
||||
%% -> ok | {atomic, exists} | {error, not_allowed}
|
||||
%% @doc Create a new user
|
||||
create_user(User,Password,Domain) ->
|
||||
case ejabberd_auth:try_register(User,Domain,Password) of
|
||||
case ejabberd_auth:try_register(?BTL(User),?BTL(Domain),Password) of
|
||||
{atomic,ok} -> ok;
|
||||
{atomic, exists} -> {atomic, exists};
|
||||
{error, not_allowed} -> {error, not_allowed};
|
||||
@@ -289,10 +251,9 @@ create_user(User,Password,Domain) ->
|
||||
|
||||
populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) ->
|
||||
io:format("Trying to add/update roster list...",[]),
|
||||
case loaded_module(Domain, mod_roster) of
|
||||
{ok, _DBType} ->
|
||||
case mod_roster:set_items(User, Domain,
|
||||
exmpp_xml:xmlel_to_xmlelement(El)) of
|
||||
case loaded_module(Domain,[mod_roster]) of
|
||||
{ok, M} ->
|
||||
case M:set_items(User, Domain, El) of
|
||||
{atomic, ok} ->
|
||||
io:format(" DONE.~n",[]),
|
||||
ok;
|
||||
@@ -303,7 +264,7 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) ->
|
||||
{error, not_found}
|
||||
end;
|
||||
E -> io:format(" ERROR: ~p~n",[E]),
|
||||
?ERROR_MSG("No modules loaded [mod_roster] ~s ~n",
|
||||
?ERROR_MSG("Module not loaded: mod_roster ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
@@ -331,10 +292,10 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) ->
|
||||
|
||||
populate_user(User,Domain,El=#xmlel{name='vCard', ns='vcard-temp'}) ->
|
||||
io:format("Trying to add/update vCards...",[]),
|
||||
case loaded_module(Domain, mod_vcard) of
|
||||
{ok, _} -> FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)),
|
||||
IQ = iq_to_old_iq(#iq{type = set, payload = El}),
|
||||
case mod_vcard:process_sm_iq(FullUser, FullUser , IQ) of
|
||||
case loaded_module(Domain,[mod_vcard]) of
|
||||
{ok, M} -> FullUser = exmpp_jid:make(User, Domain),
|
||||
IQ = #iq{kind=request, type = set, payload = El},
|
||||
case M:process_sm_iq(FullUser, FullUser , IQ) of
|
||||
{error,_Err} ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("Error processing vcard ~s : ~p ~n",
|
||||
@@ -344,7 +305,7 @@ populate_user(User,Domain,El=#xmlel{name='vCard', ns='vcard-temp'}) ->
|
||||
end;
|
||||
_ ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("No modules loaded [mod_vcard] ~s ~n",
|
||||
?ERROR_MSG("Module not loaded: mod_vcard ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
@@ -357,22 +318,20 @@ populate_user(User,Domain,El=#xmlel{name='vCard', ns='vcard-temp'}) ->
|
||||
|
||||
populate_user(User,Domain,El=#xmlel{name='offline-messages'}) ->
|
||||
io:format("Trying to add/update offline-messages...",[]),
|
||||
case loaded_module(Domain, mod_offline) of
|
||||
{ok, _DBType} ->
|
||||
case loaded_module(Domain, [mod_offline]) of
|
||||
{ok, M} ->
|
||||
ok = exmpp_xml:foreach(
|
||||
fun (_Element, {xmlcdata, _}) ->
|
||||
ok;
|
||||
(_Element, Child) ->
|
||||
From = exmpp_xml:get_attribute(Child, <<"from">>,none),
|
||||
FullFrom = jid_to_old_jid(exmpp_jid:parse(From)),
|
||||
FullUser = jid_to_old_jid(exmpp_jid:make(User,
|
||||
Domain)),
|
||||
OldChild = exmpp_xml:xmlel_to_xmlelement(Child),
|
||||
_R = mod_offline:store_packet(FullFrom, FullUser, OldChild)
|
||||
From = exmpp_xml:get_attribute(Child,<<"from">>,none),
|
||||
FullFrom = exmpp_jid:parse(From),
|
||||
FullUser = exmpp_jid:make(User, Domain),
|
||||
_R = M:store_packet(FullFrom, FullUser, Child)
|
||||
end, El), io:format(" DONE.~n",[]);
|
||||
_ ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("No modules loaded [mod_offline] ~s ~n",
|
||||
?ERROR_MSG("Module not loaded: mod_offline ~s ~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
@@ -385,15 +344,15 @@ populate_user(User,Domain,El=#xmlel{name='offline-messages'}) ->
|
||||
|
||||
populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:private'}) ->
|
||||
io:format("Trying to add/update private storage...",[]),
|
||||
case loaded_module(Domain, mod_private) of
|
||||
{ok, _DBType} ->
|
||||
FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)),
|
||||
IQ = iq_to_old_iq(#iq{type = set,
|
||||
ns = 'jabber:iq:private',
|
||||
kind = request,
|
||||
iq_ns = 'jabberd:client',
|
||||
payload = El}),
|
||||
case mod_private:process_sm_iq(FullUser, FullUser, IQ ) of
|
||||
case loaded_module(Domain,[mod_private]) of
|
||||
{ok, M} ->
|
||||
FullUser = exmpp_jid:make(User, Domain),
|
||||
IQ = #iq{type = set,
|
||||
ns = 'jabber:iq:private',
|
||||
kind = request,
|
||||
iq_ns = 'jabberd:client',
|
||||
payload = El},
|
||||
case M:process_sm_iq(FullUser, FullUser, IQ ) of
|
||||
{error, _Err} ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("Error processing private storage ~s : ~p ~n",
|
||||
@@ -402,7 +361,7 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:private'}) ->
|
||||
end;
|
||||
_ ->
|
||||
io:format(" ERROR.~n",[]),
|
||||
?ERROR_MSG("No modules loaded [mod_private] ~s ~n",
|
||||
?ERROR_MSG("Module not loaded: mod_private ~s~n",
|
||||
[exmpp_xml:document_to_list(El)]),
|
||||
{error, not_found}
|
||||
end;
|
||||
@@ -416,31 +375,17 @@ populate_user(_User, _Domain, _El) ->
|
||||
%%%==================================
|
||||
%%%% Utilities
|
||||
|
||||
loaded_module(Domain, Module) ->
|
||||
case gen_mod:is_loaded(Domain, Module) of
|
||||
true ->
|
||||
{ok, gen_mod:db_type(Domain, Module)};
|
||||
false ->
|
||||
{error, not_found}
|
||||
loaded_module(Domain,Options) when is_binary(Domain) ->
|
||||
loaded_module(?BTL(Domain),Options);
|
||||
loaded_module(Domain,Options) ->
|
||||
LoadedModules = gen_mod:loaded_modules(Domain),
|
||||
case lists:filter(fun(Module) ->
|
||||
lists:member(Module, LoadedModules)
|
||||
end, Options) of
|
||||
[M|_] -> {ok, M};
|
||||
[] -> {error,not_found}
|
||||
end.
|
||||
|
||||
jid_to_old_jid(Jid) ->
|
||||
{jid, to_list(exmpp_jid:node_as_list(Jid)),
|
||||
to_list(exmpp_jid:domain_as_list(Jid)),
|
||||
to_list(exmpp_jid:resource_as_list(Jid)),
|
||||
to_list(exmpp_jid:prep_node_as_list(Jid)),
|
||||
to_list(exmpp_jid:prep_domain_as_list(Jid)),
|
||||
to_list(exmpp_jid:prep_resource_as_list(Jid))}.
|
||||
|
||||
iq_to_old_iq(#iq{id = ID, type = Type, lang = Lang, ns= NS, payload = El }) ->
|
||||
{iq, to_list(ID), Type, to_list(NS), to_list(Lang),
|
||||
exmpp_xml:xmlel_to_xmlelement(El)}.
|
||||
|
||||
to_list(L) when is_list(L) -> L;
|
||||
to_list(B) when is_binary(B) -> binary_to_list(B);
|
||||
to_list(undefined) -> "";
|
||||
to_list(B) when is_atom(B) -> atom_to_list(B).
|
||||
|
||||
%%%==================================
|
||||
|
||||
%%%% Export hosts
|
||||
@@ -544,7 +489,7 @@ make_xinclude(Fn) ->
|
||||
%% @doc Extract user information and print it.
|
||||
export_user(Fd, Username, Host) ->
|
||||
try extract_user(Username, Host) of
|
||||
UserString ->
|
||||
UserString when is_list(UserString) ->
|
||||
print(Fd, UserString)
|
||||
catch
|
||||
E1:E2 ->
|
||||
@@ -574,62 +519,54 @@ build_password_string(Password) when is_list(Password) ->
|
||||
|
||||
%% @spec (InfoName::atom(), Username::string(), Host::string()) -> string()
|
||||
extract_user_info(roster, Username, Host) ->
|
||||
case loaded_module(Host, mod_roster) of
|
||||
{ok, _DBType} ->
|
||||
From = To = jlib:make_jid(Username, Host, ""),
|
||||
SubelGet = {xmlelement, "query", [{"xmlns",?NS_ROSTER}], []},
|
||||
%%IQGet = #iq{type=get, xmlns=?NS_ROSTER, payload=SubelGet}, % this is for 3.0.0 version
|
||||
IQGet = {iq, "", get, ?NS_ROSTER, "" , SubelGet},
|
||||
Res = mod_roster:process_local_iq(From, To, IQGet),
|
||||
%%[El] = Res#iq.payload, % this is for 3.0.0 version
|
||||
{iq, _, result, _, _, Els} = Res,
|
||||
case Els of
|
||||
[El] -> exmpp_xml:document_to_list(El);
|
||||
[] -> ""
|
||||
case loaded_module(Host,[mod_roster]) of
|
||||
{ok, M} ->
|
||||
From = To = exmpp_jid:make(Username, Host, ""),
|
||||
SubelGet = exmpp_xml:element(?NS_ROSTER, 'query', [], []),
|
||||
IQGet = #iq{kind=request, type=get, ns=?NS_ROSTER, payload=SubelGet},
|
||||
Res = M:process_local_iq(From, To, IQGet),
|
||||
case Res#iq.payload of
|
||||
undefined -> "";
|
||||
El -> exmpp_xml:document_to_list(El)
|
||||
end;
|
||||
_E ->
|
||||
""
|
||||
end;
|
||||
|
||||
extract_user_info(offline, Username, Host) ->
|
||||
case loaded_module(Host, mod_offline) of
|
||||
{ok, mnesia} ->
|
||||
case loaded_module(Host,[mod_offline]) of
|
||||
{ok, mod_offline} ->
|
||||
Els = mnesia_pop_offline_messages([], Username, Host),
|
||||
case Els of
|
||||
[] -> "";
|
||||
Els ->
|
||||
OfEl = {xmlelement, "offline-messages", [], Els},
|
||||
OfEl = #xmlel{name = 'offline-messages',
|
||||
children = Els},
|
||||
%OfEl = {xmlelement, "offline-messages", [], Els},
|
||||
exmpp_xml:document_to_list(OfEl)
|
||||
end;
|
||||
{ok, odbc} ->
|
||||
"";
|
||||
_E ->
|
||||
""
|
||||
end;
|
||||
|
||||
extract_user_info(private, Username, Host) ->
|
||||
case loaded_module(Host, mod_private) of
|
||||
{ok, mnesia} ->
|
||||
case loaded_module(Host,[mod_private]) of
|
||||
{ok, mod_private} ->
|
||||
get_user_private_mnesia(Username, Host);
|
||||
{ok, odbc} ->
|
||||
"";
|
||||
_E ->
|
||||
""
|
||||
end;
|
||||
|
||||
extract_user_info(vcard, Username, Host) ->
|
||||
case loaded_module(Host, mod_vcard) of
|
||||
{ok, _DBType} ->
|
||||
From = To = jlib:make_jid(Username, Host, ""),
|
||||
SubelGet = {xmlelement, "vCard", [{"xmlns",?NS_VCARD}], []},
|
||||
%%IQGet = #iq{type=get, xmlns=?NS_VCARD, payload=SubelGet}, % this is for 3.0.0 version
|
||||
IQGet = {iq, "", get, ?NS_VCARD, "" , SubelGet},
|
||||
Res = mod_vcard:process_sm_iq(From, To, IQGet),
|
||||
%%[El] = Res#iq.payload, % this is for 3.0.0 version
|
||||
{iq, _, result, _, _, Els} = Res,
|
||||
case Els of
|
||||
[El] -> exmpp_xml:document_to_list(El);
|
||||
[] -> ""
|
||||
case loaded_module(Host,[mod_vcard]) of
|
||||
{ok, M} ->
|
||||
From = To = exmpp_jid:make(Username, Host, ""),
|
||||
SubelGet = exmpp_xml:element(?NS_VCARD, 'vCard', [], []),
|
||||
IQGet = #iq{kind=request, type=get, ns=?NS_VCARD, payload=SubelGet},
|
||||
Res = M:process_sm_iq(From, To, IQGet),
|
||||
case Res#iq.payload of
|
||||
undefined -> "";
|
||||
El -> exmpp_xml:document_to_list(El)
|
||||
end;
|
||||
_E ->
|
||||
""
|
||||
@@ -639,53 +576,70 @@ extract_user_info(vcard, Username, Host) ->
|
||||
%%%% Interface with ejabberd offline storage
|
||||
|
||||
%% Copied from mod_offline.erl and customized
|
||||
-record(offline_msg, {us, timestamp, expire, from, to, packet}).
|
||||
-record(offline_msg, {user_host, timestamp, expire, from, to, packet}).
|
||||
mnesia_pop_offline_messages(Ls, User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
Rs = mnesia:wread({offline_msg, US}),
|
||||
%%mnesia:delete({offline_msg, US}),
|
||||
Rs
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, Rs} ->
|
||||
TS = now(),
|
||||
Ls ++ lists:map(
|
||||
fun(R) ->
|
||||
{xmlelement, Name, Attrs, Els} = R#offline_msg.packet,
|
||||
FromString = jlib:jid_to_string(R#offline_msg.from),
|
||||
Attrs2 = lists:keystore("from", 1, Attrs, {"from", FromString}),
|
||||
Attrs3 = lists:keystore("xmlns", 1, Attrs2, {"xmlns", "jabber:client"}),
|
||||
{xmlelement, Name, Attrs3,
|
||||
Els ++
|
||||
[jlib:timestamp_to_xml(
|
||||
calendar:now_to_universal_time(
|
||||
R#offline_msg.timestamp))]}
|
||||
end,
|
||||
lists:filter(
|
||||
fun(R) ->
|
||||
case R#offline_msg.expire of
|
||||
never ->
|
||||
true;
|
||||
TimeStamp ->
|
||||
TS < TimeStamp
|
||||
end
|
||||
end,
|
||||
lists:keysort(#offline_msg.timestamp, Rs)));
|
||||
try
|
||||
LUser = User,
|
||||
LServer = Server,
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
Rs = mnesia:wread({offline_msg, US}),
|
||||
%% mnesia:delete({offline_msg, US}),
|
||||
Rs
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, Rs} ->
|
||||
TS = make_timestamp(),
|
||||
Ls ++ lists:map(
|
||||
fun(R) ->
|
||||
[Packet] = exmpp_xml:parse_document(R#offline_msg.packet, [names_as_atom]),
|
||||
FromString = exmpp_jid:prep_to_list(R#offline_msg.from),
|
||||
Packet2 = exmpp_xml:set_attribute(Packet, <<"from">>, FromString),
|
||||
Packet3 = Packet2#xmlel{ns = ?NS_JABBER_CLIENT},
|
||||
exmpp_xml:append_children(
|
||||
Packet3,
|
||||
[jlib:timestamp_to_xml(
|
||||
calendar:gregorian_seconds_to_datetime(
|
||||
R#offline_msg.timestamp),
|
||||
utc,
|
||||
exmpp_jid:make("", Server, ""),
|
||||
"Offline Storage"),
|
||||
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
|
||||
jlib:timestamp_to_xml(
|
||||
calendar:gregorian_seconds_to_datetime(
|
||||
R#offline_msg.timestamp))]
|
||||
)
|
||||
end,
|
||||
lists:filter(
|
||||
fun(R) ->
|
||||
case R#offline_msg.expire of
|
||||
0 ->
|
||||
true;
|
||||
TimeStamp ->
|
||||
TS < TimeStamp
|
||||
end
|
||||
end,
|
||||
lists:keysort(#offline_msg.timestamp, Rs)));
|
||||
_ ->
|
||||
Ls
|
||||
end
|
||||
catch
|
||||
_ ->
|
||||
Ls
|
||||
end.
|
||||
|
||||
make_timestamp() ->
|
||||
{MegaSecs, Secs, _MicroSecs} = now(),
|
||||
MegaSecs * 1000000 + Secs.
|
||||
|
||||
%%%==================================
|
||||
%%%% Interface with ejabberd private storage
|
||||
|
||||
get_user_private_mnesia(Username, Host) ->
|
||||
ListNsEl = mnesia:dirty_select(private_storage,
|
||||
[{#private_storage{usns={Username, Host, '$1'}, xml = '$2'},
|
||||
[{#private_storage{user_host_ns={?LTB(Username), ?LTB(Host), '$1'}, xml = '$2'},
|
||||
[], ['$$']}]),
|
||||
Els = [exmpp_xml:document_to_list(El) || [_Ns, El] <- ListNsEl],
|
||||
Els = [lists:flatten(exmpp_xml:document_to_list(El)) || [_Ns, El] <- ListNsEl],
|
||||
case lists:flatten(Els) of
|
||||
"" -> "";
|
||||
ElsString ->
|
||||
|
||||
+63
-11
@@ -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
|
||||
@@ -27,8 +27,15 @@
|
||||
-module(ejabberd_rdbms).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0]).
|
||||
-export([start/0
|
||||
,start_odbc/1
|
||||
,stop_odbc/1
|
||||
,running/1
|
||||
]).
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_config.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
-define(SUPERVISOR, ejabberd_sup).
|
||||
|
||||
start() ->
|
||||
%% Check if ejabberd has been compiled with ODBC
|
||||
@@ -52,27 +59,72 @@ start_hosts() ->
|
||||
|
||||
%% Start the ODBC module on the given host
|
||||
start_odbc(Host) ->
|
||||
Supervisor_name = gen_mod:get_module_proc(Host, ejabberd_odbc_sup),
|
||||
SupervisorName = sup_name(Host),
|
||||
ChildSpec =
|
||||
{Supervisor_name,
|
||||
{SupervisorName,
|
||||
{ejabberd_odbc_sup, start_link, [Host]},
|
||||
transient,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_odbc_sup]},
|
||||
case supervisor:start_child(ejabberd_sup, ChildSpec) of
|
||||
case supervisor:start_child(?SUPERVISOR, ChildSpec) of
|
||||
{ok, _PID} ->
|
||||
ok;
|
||||
_Error ->
|
||||
?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying...~n", [Supervisor_name, _Error]),
|
||||
?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying...~n", [SupervisorName, _Error]),
|
||||
start_odbc(Host)
|
||||
end.
|
||||
|
||||
stop_odbc(Host) ->
|
||||
SupervisorName = sup_name(Host),
|
||||
case running(Host) of
|
||||
false -> ok;
|
||||
true ->
|
||||
case [H || H <- dependent_hosts(Host), ejabberd_hosts:running(H)] of
|
||||
[] ->
|
||||
?INFO_MSG("About to terminate ~p", [SupervisorName]),
|
||||
ok = supervisor:terminate_child(?SUPERVISOR, SupervisorName),
|
||||
ok = supervisor:delete_child(?SUPERVISOR, SupervisorName);
|
||||
RunningHosts ->
|
||||
?WARNING_MSG("Not stopping ODBC for ~p because the virtual hosts ~p are still using it.",
|
||||
[Host, RunningHosts]),
|
||||
{error, still_in_use}
|
||||
end
|
||||
end.
|
||||
|
||||
%% Returns true if we have configured odbc_server for the given host
|
||||
needs_odbc(Host) ->
|
||||
LHost = jlib:nameprep(Host),
|
||||
case ejabberd_config:get_local_option({odbc_server, LHost}) of
|
||||
undefined ->
|
||||
false;
|
||||
_ -> true
|
||||
try
|
||||
LHost = exmpp_stringprep:nameprep(Host),
|
||||
case ejabberd_config:get_local_option({odbc_server, LHost}) of
|
||||
undefined ->
|
||||
false;
|
||||
{host, _} ->
|
||||
false;
|
||||
_ ->
|
||||
true
|
||||
end
|
||||
catch
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
running(Host) ->
|
||||
Supervisors = supervisor:which_children(?SUPERVISOR),
|
||||
SupervisorName = gen_mod:get_module_proc(Host, ejabberd_odbc_sup),
|
||||
case lists:keysearch(SupervisorName, 1, Supervisors) of
|
||||
false -> false;
|
||||
{value, Cspec} when is_tuple(Cspec) -> true
|
||||
end.
|
||||
|
||||
|
||||
dependent_hosts(Host) ->
|
||||
MS = ets:fun2ms(fun (#local_config{key={odbc_server, DHost},
|
||||
value={host, H}})
|
||||
when H =:= Host ->
|
||||
DHost
|
||||
end),
|
||||
ejabberd_config:search(MS).
|
||||
|
||||
sup_name(Host) ->
|
||||
gen_mod:get_module_proc(Host, ejabberd_odbc_sup).
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user