Compare commits
1280 Commits
master
...
v3.0.0-alpha-2
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -0,0 +1,38 @@
|
||||
#
|
||||
# You can add personal rules in your file .git/info/exclude
|
||||
*.swp
|
||||
*~
|
||||
/doc/*.aux
|
||||
/doc/*.haux
|
||||
/doc/*.html
|
||||
/doc/*.htoc
|
||||
/doc/*.idx
|
||||
/doc/*.ilg
|
||||
/doc/*.ind
|
||||
/doc/*.log
|
||||
/doc/*.out
|
||||
/doc/*.pdf
|
||||
/doc/*.toc
|
||||
/doc/contributed_modules.tex
|
||||
/doc/version.tex
|
||||
/src/*.beam
|
||||
/src/*.so
|
||||
/src/*.so.dSYM
|
||||
/src/*/*.beam
|
||||
/src/*/Makefile
|
||||
/src/Makefile
|
||||
/src/XmppAddr.asn1db
|
||||
/src/XmppAddr.erl
|
||||
/src/XmppAddr.hrl
|
||||
/src/aclocal.m4
|
||||
/src/autom4te.cache
|
||||
/src/config.log
|
||||
/src/config.status
|
||||
/src/configure
|
||||
/src/ejabberd.init
|
||||
/src/ejabberdctl.example
|
||||
/src/eldap/ELDAPv3.asn1db
|
||||
/src/eldap/ELDAPv3.erl
|
||||
/src/eldap/ELDAPv3.hrl
|
||||
/src/eldap/eldap_filter_yecc.erl
|
||||
/src/epam
|
||||
@@ -8,20 +8,17 @@ Quickstart guide
|
||||
To compile ejabberd you need:
|
||||
- GNU Make
|
||||
- GCC
|
||||
- Libexpat 1.95 or higher
|
||||
- Erlang/OTP R10B-9 or higher. The recommended version is R12B-5.
|
||||
Support for R13 is experimental.
|
||||
- OpenSSL 0.9.6 or higher, for STARTTLS, SASL and SSL
|
||||
encryption. Optional, highly recommended.
|
||||
- Erlang/OTP 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.
|
||||
- ImageMagick’s Convert program. Optional. For CAPTCHA challenges.
|
||||
- exmpp 0.9.1 or higher. Optional. For import/export XEP-0227 files.
|
||||
- ESASL library. Optional. For SASL GSSAPI authentication.
|
||||
- ImageMagick's Convert program. Optional. For CAPTCHA challenges.
|
||||
|
||||
|
||||
1. Compile and install on *nix systems
|
||||
@@ -30,6 +27,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,5 +1,3 @@
|
||||
# $Id$
|
||||
|
||||
SHELL = /bin/bash
|
||||
|
||||
CONTRIBUTED_MODULES = ""
|
||||
@@ -16,7 +14,7 @@ release:
|
||||
@echo "* Do not forget to update the version number in src/ejabberd.app!"
|
||||
@echo "* Do not forget to update the features in introduction.tex (including \new{} and \improved{} tags)."
|
||||
@echo "Press any key to continue"
|
||||
@read foo
|
||||
##@read foo
|
||||
@echo "% ejabberd version (automatically generated)." > version.tex
|
||||
@echo "\newcommand{\version}{"`sed '/vsn/!d;s/\(.*\)"\(.*\)"\(.*\)/\2/' ../src/ejabberd.app`"}" >> version.tex
|
||||
@echo -n "% Contributed modules (automatically generated)." > contributed_modules.tex
|
||||
@@ -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 2007 ProcessOne
|
||||
@version {@vsn}, {@date} {@time}
|
||||
@title ejabberd Development API Documentation
|
||||
|
||||
@doc
|
||||
== Introduction ==
|
||||
|
||||
TODO: Insert content from Jerome documentation.
|
||||
@@ -1,410 +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.0 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.0 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 sended 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></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
|
||||
@@ -176,6 +172,9 @@ That script is supposed to do theses actions, in an infinite loop:
|
||||
\item auth:User:Server:Password (check if a username/password pair is correct)
|
||||
\item isuser:User:Server (check if it's a valid user)
|
||||
\item setpass:User:Server:Password (set user's password)
|
||||
\item tryregister:User:Server:Password (try to register an account)
|
||||
\item removeuser:User:Server (remove this account)
|
||||
\item removeuser3:User:Server:Password (remove this account if the password is correct)
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\item write to stdout: AABB
|
||||
|
||||
@@ -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
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 976 B |
|
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.
|
||||
|
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;
|
||||
}
|
||||
|
||||
|
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.0 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.0 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>
|
||||
@@ -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}
|
||||
|
||||
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 21 KiB |
@@ -135,6 +135,8 @@
|
||||
- Implicit item deletion is not notified when deleting node
|
||||
- Make PubSub x-data configuration form handles list value
|
||||
- Make default node name convention XEP-compatible, document usage of hierarchy
|
||||
- Node names are used verbatim, without separating by slash, unless a
|
||||
node plugin uses its own separator
|
||||
- Send authorization update event (XEP-0060, 8.6)
|
||||
- Support of collection node subscription options
|
||||
- Support ODBC storage. Experimental, needs more testing.
|
||||
@@ -207,14 +209,24 @@
|
||||
|
||||
Upgrading From ejabberd 2.0.x:
|
||||
|
||||
- The database schemas didn't change since ejabberd 1.1.4.
|
||||
Anyway, it is recommended to backup the Mnesia spool directory and
|
||||
- The database schemas have three changes since ejabberd 2.0.x.
|
||||
Check the database creation SQL files and update your database.
|
||||
1) New table roster_version to support roster versioning.
|
||||
2) Six new tables for optional pubsub ODBC storage.
|
||||
3) Some tables in the MySQL database have a new created_at column.
|
||||
|
||||
- As usual, it is recommended to backup the Mnesia spool directory and
|
||||
your SQL database (if used) before upgrading ejabberd.
|
||||
|
||||
- The plugin of mod_pubsub "default" is renamed to "flat". You need
|
||||
to edit the ejabberd configuration file and replace those names.
|
||||
- Between ejabberd 2.0.0 and 2.0.5, mod_pubsub used "default" as the
|
||||
default node plugin. But in 2.1.0 this is renamed to "hometree".
|
||||
You have to edit your ejabberd config file and replace those names.
|
||||
If you used ejabberd 2.0.5 or older, the database will be updated
|
||||
automatically. But if you were using ejabberd from SVN, you must
|
||||
manually run ejabberdctl with the command: rename_default_nodeplugin.
|
||||
Running this command on already updated database does nothing.
|
||||
|
||||
- The listener options 'ip' and inet6' are not documented anymore
|
||||
- The listener options 'ip' and 'inet6' are not documented anymore
|
||||
but they are supported and you can continue using them.
|
||||
There is a new syntax to define IP address and IP version.
|
||||
As usual, check the ejabberd Guide for more information.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.1
|
||||
|
||||
ejabberd 2.1.1 is the first bugfix release in ejabberd 2.1.x branch.
|
||||
|
||||
ejabberd 2.1.1 includes several important bugfixes.
|
||||
More details of those fixes can be retrieved from:
|
||||
http://redir.process-one.net/ejabberd-2.1.1
|
||||
|
||||
The new code can be downloaded from ejabberd download page:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The changes are:
|
||||
|
||||
* Core
|
||||
- Call ejabberd_router:route/3 instead of sending a message
|
||||
- Can't connect if starttls_required and zlib are set
|
||||
- Routes vCard request to the occupant full JID, but should to bare JID
|
||||
- S2S: fix allow_host/2 on subdomains. added hook s2s_allow_host
|
||||
|
||||
* MUC
|
||||
- Support converting one-to-one chat to MUC
|
||||
- Add support for serving a Unique Room Name
|
||||
|
||||
* Publish Subscribe
|
||||
- Receive same last published PEP items at reconnect if several resources online
|
||||
- Typo in mod_pubsub_odbc breaks Service Discovery and more
|
||||
|
||||
* Web
|
||||
- Fix memory and port leak when TLS is enabled in HTTP
|
||||
- WebAdmin doesn't report correct last activity with postgresql backend
|
||||
- Option to define custom HTTP headers in mod_http_fileserver
|
||||
- Show informative webpage when browsing the HTTP-Poll page
|
||||
|
||||
* Other
|
||||
- Change captcha.sh to not depend on bash
|
||||
- Generate main XML file also when exporting only a vhost
|
||||
- Fix last newline in ejabberdctl result
|
||||
- Guide: fix -setcookie, mod_pubsub_odbc host, content_types
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.2
|
||||
|
||||
ejabberd 2.1.2 is the second bugfix release in ejabberd 2.1.x branch.
|
||||
|
||||
ejabberd 2.1.2 includes several bugfixes.
|
||||
More details of those fixes can be retrieved from:
|
||||
http://redir.process-one.net/ejabberd-2.1.2
|
||||
|
||||
The new code can be downloaded from ejabberd download page:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The major changes are:
|
||||
|
||||
* Core
|
||||
- Close sessions that were half connected
|
||||
- Fix SASL PLAIN authentication message for RFC4616 compliance
|
||||
- Fix support for old Erlang/OTP R10 and R11
|
||||
- Return proper error (not 'conflict') when register is forbidden by ACL
|
||||
- When ejabberd stops, send stream close to clients
|
||||
|
||||
* ejabberdctl
|
||||
- Check for EGID in ejabberdctl command
|
||||
- Command to stop ejabberd informing users, with grace period
|
||||
- If there's a problem in config file, display config lines and stop node
|
||||
|
||||
* MUC
|
||||
- Kick occupants with reason when room is stopped due to MUC shutdown
|
||||
- Write in room log when a room is created, destroyed, started, stopped
|
||||
|
||||
* PubSub and PEP
|
||||
- Don't call gen_server on internal event (improves performance and scalability)
|
||||
- Fix duplicate SHIM header in Pubsub message
|
||||
- Notification messages of Pubsub node config change contained a SHIM header
|
||||
- SubID SHIM header missing in Pubsub message with multiple
|
||||
subscriptions on the same node
|
||||
- PEP: last published item not sent from unavailable users when the
|
||||
subscription is implicit (XEP-0115)
|
||||
- pep_mapping not working due to Node type mismatch
|
||||
|
||||
* WebAdmin
|
||||
- If big offline message queue, show only subset on WebAdmin
|
||||
- Support in user list page of WebAdmin when mod_offline is disabled
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -0,0 +1,91 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.3
|
||||
|
||||
ejabberd 2.1.3 is the third release in ejabberd 2.1.x branch.
|
||||
|
||||
ejabberd 2.1.3 includes many bugfixes, and some improvements.
|
||||
More details of those fixes can be retrieved from:
|
||||
http://redir.process-one.net/ejabberd-2.1.3
|
||||
|
||||
The new code can be downloaded from ejabberd download page:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
This is the full list of changes:
|
||||
|
||||
* Client connections
|
||||
- Avoid 'invalid' value in iq record
|
||||
- Avoid resending stream:error stanzas on terminate (EJAB-1180)
|
||||
- Close also legacy sessions that were half connected (EJAB-1165)
|
||||
- iq_query_info/1 now returns 'invalid' if XMLNS is invalid
|
||||
- New ejabberd_c2s option support: max_fsm_queue
|
||||
- Rewrite mnesia counter functions to use dirty_update_counter (EJAB-1177)
|
||||
- Run user_receive_packet also when sending offline messages (EJAB-1193)
|
||||
- Use p1_fsm behaviour in c2s FSM (EJAB-1173)
|
||||
|
||||
* Clustering
|
||||
- Fix cluster race condition in route read
|
||||
- New command to set master Mnesia node
|
||||
- Use mnesia:async_dirty when cleaning table from failed node
|
||||
|
||||
* Documentation
|
||||
- Add quotes in documentation of some erl arguments (EJAB-1191)
|
||||
- Add option access_from (EJAB-1187)
|
||||
- Add option max_fsm_queue (EJAB-1185)
|
||||
- Fix documentation installation, no need for executable permission (EJAB-1170)
|
||||
- Fix typo in EJABBERD_BIN_PATH (EJAB-891)
|
||||
- Fix typos in example config comments (EJAB-1192)
|
||||
|
||||
* ejabberdctl
|
||||
- Support concurrent connections with bound connection names
|
||||
- Add support for Jot in ctl and TTY in debug
|
||||
- Support help command names with old - characters
|
||||
- Fix to really use the variable ERL_PROCESSES
|
||||
|
||||
* Erlang compatibility
|
||||
- Don't call queue:filter/2 to keep compatibility with older Erlang versions
|
||||
- Use alternative of file:read_line/1 to not require R13B02
|
||||
|
||||
* HTTP
|
||||
- Add new debugging hook to the http receiving process
|
||||
- Allow a request_handler to serve a file in root of HTTP
|
||||
|
||||
* HTTP-Bind (BOSH)
|
||||
- Cross-domain HTTP-Bind support (EJAB-1168)
|
||||
- Hibernate http-bind process after handling a request
|
||||
- Reduce verbosity of HTTP Binding log messages
|
||||
|
||||
* LDAP
|
||||
- Document ldap_dn_filter, fetch only needed attributes in search (EJAB-1204)
|
||||
- Use "%u" pattern as default for ldap_uids (EJAB-1203)
|
||||
|
||||
* Localization
|
||||
- Fix German translation (EJAB-1195)
|
||||
- Fix Russian translation
|
||||
|
||||
* ODBC
|
||||
- Fix MSSQL support, which was broken (EJAB-1201)
|
||||
- Improved SQL reconnect behaviour
|
||||
|
||||
* Pubsub, PEP and Caps
|
||||
- Add extended stanza addressing 'replyto' on PEP (EJAB-1198)
|
||||
- Add pubsub#purge_offline (EJAB-1186)
|
||||
- Fix pubsub#title option (EJAB-1190)
|
||||
- Fix remove_user for node subscriptions (EJAB-1172)
|
||||
- Optimizations in mod_caps
|
||||
|
||||
* Other
|
||||
- mod_register: Add new acl access_from, default is to deny
|
||||
- mod_sic: new module for the experimental XEP-0279 Server IP Check (EJAB-1205)
|
||||
- PIEFXIS: Catch errors when exporting to PIEFXIS file (EJAB-1178)
|
||||
- Proxy65: new option "hostname" (EJAB-838)
|
||||
- Roster: Fix resending authorization problem
|
||||
- Shared Roster Groups: get contacts nickname from vcard (EJAB-114)
|
||||
- S2S: Improved s2s connections clean up (EJAB-1202)
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -0,0 +1,80 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.4
|
||||
|
||||
ejabberd 2.1.4 is the fourth release in ejabberd 2.1.x branch,
|
||||
and includes many small bugfixes and improvements.
|
||||
|
||||
Read more details about the changes in:
|
||||
http://redir.process-one.net/ejabberd-2.1.4
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
This is the full list of changes:
|
||||
|
||||
* Authentication
|
||||
- Extauth: Optionally cache extauth users in mnesia (EJAB-641)
|
||||
- LDAP: Allow inband password change (EJAB-199)
|
||||
- LDAP: Extensible match support (EJAB-722)
|
||||
- LDAP: New option ldap_tls_verify is added (EJAB-1229)
|
||||
- PAM: New option pam_userinfotype to provide username or JID (EJAB-652)
|
||||
|
||||
* HTTP
|
||||
- Add xml default content type
|
||||
- Don't show HTTP request in logs, because reveals password (EJAB-1231)
|
||||
- Move HTTP session timeout log from warning level to info
|
||||
- New Access rule webadmin_view for read-only
|
||||
|
||||
* HTTP-Bind (BOSH)
|
||||
- Change max inactivity from 30 to 120 seconds
|
||||
- Export functions to facilitate prebinding methods
|
||||
- Use dirty_delete when removing the session
|
||||
- Remove an unneeded delay of 100 milliseconds
|
||||
|
||||
* Pubsub, PEP and Caps
|
||||
- Enforce pubsub#presence_based_delivery (EJAB-1221)
|
||||
- Enforce pubsub#show_values subscription option (EJAB-1096)
|
||||
- Fix error code when unsubscribing from a non-existent node
|
||||
- Fix to send node notifications (EJAB-1225)
|
||||
- Full support for XEP-0115 v1.5 (EJAB-1223)(EJAB-1189)
|
||||
- Make last_item_cache feature to be cluster aware
|
||||
- Prevent orphaned pubsub node (EJAB-1233)
|
||||
- Send created node notifications
|
||||
|
||||
* Other
|
||||
- Bounce messages when closing c2s session
|
||||
- Bugfixes when handling Service Discovery to contacts (EJAB-1207)
|
||||
- Compilation of ejabberd_debug.erl is now optional
|
||||
- Don't send error stanza as reply to error stanza (EJAB-930)
|
||||
- Don't store blocked messages in offline queue
|
||||
- Reduce verbosity of log when captcha_cmd is checked but not configured
|
||||
- Use a standard method to get a random seed (EJAB-1229)
|
||||
- Commands: new update_list and update to update modified modules (EJAB-1237)
|
||||
- Localization: Updated most translations
|
||||
- MUC: Refactor code to reduce calls to get_affiliation and get_role
|
||||
- ODBC: Add created_at column also to PostgreSQL schema
|
||||
- Vcard: Automatic vcard avatar addition in presence
|
||||
|
||||
|
||||
Upgrading From previous ejabberd releases:
|
||||
|
||||
- If you use PostgreSQL, maybe you want to add the column created_at
|
||||
to several tables. This is only a suggestion; ejabberd doesn't use
|
||||
that column. Add it to your existing database executing those SQL
|
||||
statements:
|
||||
|
||||
ALTER TABLE users ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
ALTER TABLE rosterusers ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
ALTER TABLE spool ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
ALTER TABLE vcard ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
ALTER TABLE privacy_list ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
ALTER TABLE privacy_storage ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.5
|
||||
|
||||
ejabberd 2.1.5 is the fifth release in ejabberd 2.1.x branch,
|
||||
and includes several minor bugfixes and a few improvements.
|
||||
|
||||
Read more details about the changes in:
|
||||
http://redir.process-one.net/ejabberd-2.1.5
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
This is the full list of changes:
|
||||
|
||||
* Authentication
|
||||
- Extauth: Support parallel script running (EJAB-1280)
|
||||
- mod_register: Return Registered element when account exists
|
||||
|
||||
* ejabberdctl
|
||||
- Fix print of command result that contains ~
|
||||
- Fix problem when FIREWALL_WINDOW options for erl kernel were used
|
||||
- Fix typo in update_list command (EJAB-1237)
|
||||
- Some systems delete the lock dir; in such case don't use flock at all
|
||||
- The command Update now returns meaningful message and exit-status (EJAB-1237)
|
||||
|
||||
* HTTP-Bind (BOSH)
|
||||
- Don't say v1.2 in the Bind HTTP page
|
||||
- New optional BOSH connection attribute process-delay (EJAB-1257)
|
||||
|
||||
* MUC
|
||||
- Document the mod_muc option captcha_protected
|
||||
- Now admins are able to see private rooms in disco (EJAB-1269)
|
||||
- Show some more room options in the log file
|
||||
|
||||
* ODBC
|
||||
- Correct handling of SQL boolean types (EJAB-1275)
|
||||
- Discard too old queued requests (the caller has already got a timeout)
|
||||
- Fixes wrong SQL escaping when --enable-full-xml is set
|
||||
- Use ets insead of asking supervisor in ejabberd_odbc_sup:get_pids/1
|
||||
|
||||
* Pubsub, PEP and Caps
|
||||
- Enforce disco features results (EJAB-1033, EJAB-1228, EJAB-1238)
|
||||
- Support all the hash functions required by Caps XEP-0115
|
||||
|
||||
* Requirements
|
||||
- Fixed support for Erlang R12; which doesn't support: true andalso ok
|
||||
- Support OTP R14A by using public_key library instead of old ssl (EJAB-953)
|
||||
- Requirement of OpenSSL increased from 0.9.6 to 0.9.8
|
||||
- OpenSSL is now required, not optional
|
||||
|
||||
* Other
|
||||
- Don't ask for client certificate when using tls (EJAB-1267)
|
||||
- Fix typo in --enable-transient_supervisors
|
||||
- Fix privacy check when serving local Last (EJAB-1271)
|
||||
- Inform client that SSL session caching is disabled
|
||||
- New configure option: --enable-nif
|
||||
- Use driver allocator in C files for reflecting memory in erlang:memory(system)
|
||||
- Debug: New p1_prof compiled with: make debugtools=true
|
||||
- Debug: Added functions to collect stats about queues, memory, reductions etc
|
||||
- HTTP: Log error if request has ambiguous Host header (EJAB-1261)
|
||||
- Logs: When logging s2s out connection attempt or success, log if TLS is used
|
||||
- Shared Rosters: When account is deleted, delete also member of stored rosters
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -0,0 +1,67 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.6
|
||||
|
||||
ejabberd 2.1.6 is the sixth release in ejabberd 2.1.x branch,
|
||||
and includes a lot of bugfixes and improvements.
|
||||
|
||||
Read more details about the changes in:
|
||||
http://redir.process-one.net/ejabberd-2.1.6
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
Some of the changes are:
|
||||
|
||||
* Account register
|
||||
- mod_register: New ip_access option restricts which IPs can register (EJAB-915)
|
||||
- mod_register: Default configuration allows registrations only from localhost
|
||||
- mod_register: New password_strength for entropy check (EJAB-1326)
|
||||
- mod_register: New captcha_protected option to require CAPTCHA (EJAB-1262)
|
||||
- mod_register_web: New module, with CAPTCHA support (EJAB-471)
|
||||
|
||||
* BOSH
|
||||
- Don't loop when there is nothing after a stream start (EJAB-1358)
|
||||
- Fix http-bind supervisor to support multiple vhosts (EJAB-1321)
|
||||
- Support to restart http-bind (EJAB-1318)
|
||||
- Support for X-Forwarded-For HTTP header (EJAB-1356)
|
||||
|
||||
* Erlang/OTP compatibility
|
||||
- R11: Fix detection of Erlang R11 and older (EJAB-1287)
|
||||
- R12B5: Fix compatibility in ejabberd_http_bind.erl (EJAB-1343)
|
||||
- R14A: Make xml.c correctly compile (EJAB-1288)
|
||||
- R14A, R14B: Disapprove the use of R14A and R14B due to the rwlock bug
|
||||
- R14B: Use pg2 from this version in systems with older ones (EJAB-1349)
|
||||
|
||||
* Listeners
|
||||
- Bind listener ports early and start accepting connections later
|
||||
- Fix a leak of ejabberd_receiver processes
|
||||
- Speed up ejabberd_s2s:is_service/2, allow_host/2 (EJAB-1319)
|
||||
- S2S: New option to require encryption (EJAB-495)
|
||||
- S2S: New option to reject connection if untrusted certificate (EJAB-464)
|
||||
- S2S: Include From attribute in the stream header of outgoing S2S connections
|
||||
- S2S: Fix domain_certfile tlsopts modifications for S2S connections (EJAB-1086)
|
||||
|
||||
* Pubsub/PEP/Caps
|
||||
- Fix pubsub cross domain eventing (EJAB-1340)
|
||||
- Use one_queue IQ discipline by default
|
||||
- Implement lifetime for broken hashes
|
||||
- New CAPS processing
|
||||
|
||||
* ODBC
|
||||
- Increase maximum restart strategy to handle some SQL timeouts
|
||||
- Support PostgreSQL 9.0 (EJAB-1359)
|
||||
- Use MEDIUMTEXT type for vcard avatars in MySQL schema (EJAB-1252)
|
||||
|
||||
* Miscellanea:
|
||||
- mod_shared_roster_ldap: New Shared Roster Groups using LDAP information
|
||||
- mod_privacy: Fix to allow block by group and subscription again
|
||||
- Support timezone West of UTC (EJAB-1301)
|
||||
- Support to change loglevel per module at runtime (EJAB-225)
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -0,0 +1,104 @@
|
||||
|
||||
******************************************************************************
|
||||
* 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.
|
||||
|
||||
|
||||
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)
|
||||
- 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)
|
||||
- Use binaries instead of lists where possible (EJAB-17)
|
||||
- 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.0}
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# PROVIDE: ejabberd
|
||||
# REQUIRE: DAEMON
|
||||
# KEYWORD: shutdown
|
||||
#
|
||||
|
||||
HOME=/usr/pkg/jabber D=/usr/pkg/jabber/ejabberd export HOME
|
||||
|
||||
name="ejabberd"
|
||||
rcvar=$name
|
||||
|
||||
if [ -r /etc/rc.conf ]
|
||||
then
|
||||
. /etc/rc.conf
|
||||
else
|
||||
eval ${rcvar}=YES
|
||||
fi
|
||||
|
||||
# $flags from environment overrides ${rcvar}_flags
|
||||
if [ -n "${flags}" ]
|
||||
then
|
||||
eval ${rcvar}_flags="${flags}"
|
||||
fi
|
||||
|
||||
checkyesno()
|
||||
{
|
||||
eval _value=\$${1}
|
||||
case $_value in
|
||||
[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) return 0 ;;
|
||||
[Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0) return 1 ;;
|
||||
*)
|
||||
echo "\$${1} is not set properly."
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
cmd=${1:-start}
|
||||
case ${cmd} in
|
||||
force*)
|
||||
cmd=${cmd#force}
|
||||
eval ${rcvar}=YES
|
||||
;;
|
||||
esac
|
||||
|
||||
if checkyesno ${rcvar}
|
||||
then
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
|
||||
case ${cmd} in
|
||||
start)
|
||||
if [ -x $D/src ]; then
|
||||
echo "Starting ${name}."
|
||||
cd $D/src
|
||||
ERL_MAX_PORTS=32000 export ERL_MAX_PORTS
|
||||
ulimit -n $ERL_MAX_PORTS
|
||||
su jabber -c "/usr/pkg/bin/erl -sname ejabberd -s ejabberd -heart -detached -sasl sasl_error_logger '{file, \"ejabberd-sasl.log\"}' &" \
|
||||
1>/dev/null 2>&1
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
echo "rpc:call('ejabberd@`hostname -s`', init, stop, [])." | \
|
||||
su jabber -c "/usr/pkg/bin/erl -sname ejabberdstop"
|
||||
;;
|
||||
restart)
|
||||
echo "rpc:call('ejabberd@`hostname -s`', init, restart, [])." | \
|
||||
su jabber -c "/usr/pkg/bin/erl -sname ejabberdrestart"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo '1. fetch, compile, and install erlang'
|
||||
|
||||
if [ ! pkg_info erlang 1>/dev/null 2>&1 ]; then
|
||||
cd /usr/pkgsrc/lang/erlang
|
||||
make fetch-list|sh
|
||||
make
|
||||
make install
|
||||
fi
|
||||
if pkg_info erlang | grep -q erlang-9.1nb1; then
|
||||
else
|
||||
echo "erlang-9.1nb1 not installed" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
echo '2. install crypt_drv.so'
|
||||
|
||||
if [ ! -d /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib ] ; then
|
||||
mkdir -p /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib
|
||||
fi
|
||||
if [ ! -f /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib/crypto_drv.so ]; then
|
||||
cp work/otp*/lib/crypto/priv/*/*/crypto_drv.so \
|
||||
/usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib
|
||||
fi
|
||||
|
||||
|
||||
echo '3. compile and install elibcrypto.so'
|
||||
|
||||
if [ ! -f /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib/elibcrypto.so ]; then
|
||||
cd /usr/pkgsrc/lang/erlang/work/otp_src_R9B-1/lib/crypto/c_src
|
||||
ld -r -u CRYPTO_set_mem_functions -u MD5 -u MD5_Init -u MD5_Update \
|
||||
-u MD5_Final -u SHA1 -u SHA1_Init -u SHA1_Update -u SHA1_Final \
|
||||
-u des_set_key -u des_ncbc_encrypt -u des_ede3_cbc_encrypt \
|
||||
-L/usr/lib -lcrypto -o ../priv/obj/i386--netbsdelf/elibcrypto.o
|
||||
cc -shared \
|
||||
-L/usr/pkgsrc/lang/erlang/work/otp_src_R9B-1/lib/erl_interface/obj/i386--netbsdelf \
|
||||
-o ../priv/obj/i386--netbsdelf/elibcrypto.so \
|
||||
../priv/obj/i386--netbsdelf/elibcrypto.o -L/usr/lib -lcrypto
|
||||
cp ../priv/obj/i386--netbsdelf/elibcrypto.so \
|
||||
/usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib
|
||||
fi
|
||||
|
||||
|
||||
echo '4. compile and install ssl_esock'
|
||||
|
||||
if [ ! -f /usr/pkg/lib/erlang/lib/ssl-2.3.5/priv/bin/ssl_esock ]; then
|
||||
cd /usr/pkg/lib/erlang/lib/ssl-2.3.5/priv/obj/
|
||||
make
|
||||
fi
|
||||
|
||||
|
||||
echo '5. initial ejabberd configuration'
|
||||
|
||||
cd /usr/pkg/jabber/ejabberd/src
|
||||
./configure
|
||||
|
||||
|
||||
echo '6. edit ejabberd Makefiles'
|
||||
|
||||
for M in Makefile mod_*/Makefile; do
|
||||
if [ ! -f $M.orig ]; then
|
||||
mv $M $M.orig
|
||||
sed -e s%/usr/local%/usr/pkg%g < $M.orig > $M
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
echo '7. compile ejabberd'
|
||||
|
||||
gmake
|
||||
for A in mod_irc mod_muc mod_pubsub; do
|
||||
(cd $A; gmake)
|
||||
done
|
||||
|
||||
|
||||
echo ''
|
||||
echo 'now edit ejabberd.cfg'
|
||||
echo ''
|
||||
echo 'to start ejabberd: erl -sname ejabberd -s ejabberd'
|
||||
@@ -1,66 +0,0 @@
|
||||
% jabber.dbc.mtview.ca.us
|
||||
|
||||
override_acls.
|
||||
|
||||
{acl, admin, {user, "mrose", "jabber.dbc.mtview.ca.us"}}.
|
||||
|
||||
|
||||
{access, announce, [{allow, admin},
|
||||
{deny, all}]}.
|
||||
{access, c2s, [{deny, blocked},
|
||||
{allow, all}]}.
|
||||
{access, c2s_shaper, [{none, admin},
|
||||
{normal, all}]}.
|
||||
{access, configure, [{allow, admin},
|
||||
{deny, all}]}.
|
||||
{access, disco_admin, [{allow, admin},
|
||||
{deny, all}]}.
|
||||
{access, muc_admin, [{allow, admin},
|
||||
{deny, all}]}.
|
||||
{access, register, [{deny, all}]}.
|
||||
{access, s2s_shaper, [{fast, all}]}.
|
||||
|
||||
|
||||
{auth_method, internal}.
|
||||
{host, "jabber.dbc.mtview.ca.us"}.
|
||||
{outgoing_s2s_port, 5269}.
|
||||
{shaper, normal, {maxrate, 1000}}.
|
||||
{shaper, fast, {maxrate, 50000}}.
|
||||
{welcome_message, none}.
|
||||
|
||||
|
||||
{listen, [{5222, ejabberd_c2s,
|
||||
[{access, c2s},
|
||||
{shaper, c2s_shaper}]},
|
||||
{5223, ejabberd_c2s,
|
||||
[{access, c2s},
|
||||
{shaper, c2s_shaper},
|
||||
{ssl, [{certfile, "/etc/openssl/certs/ejabberd.pem"}]}]},
|
||||
{5269, ejabberd_s2s_in,
|
||||
[{shaper, s2s_shaper}]}]}.
|
||||
|
||||
|
||||
{modules, [
|
||||
{mod_register, []},
|
||||
{mod_roster, []},
|
||||
{mod_privacy, []},
|
||||
{mod_configure, []},
|
||||
{mod_disco, []},
|
||||
{mod_stats, []},
|
||||
{mod_vcard, []},
|
||||
{mod_offline, []},
|
||||
{mod_echo, [{host, "echo.jabber.dbc.mtview.ca.us"}]},
|
||||
{mod_private, []},
|
||||
% {mod_irc, []},
|
||||
{mod_muc, []},
|
||||
{mod_pubsub, []},
|
||||
{mod_time, []},
|
||||
{mod_last, []},
|
||||
{mod_version, []}
|
||||
]}.
|
||||
|
||||
|
||||
|
||||
% Local Variables:
|
||||
% mode: erlang
|
||||
% End:
|
||||
@@ -1,77 +0,0 @@
|
||||
<!-- aim-transport.xml -->
|
||||
|
||||
<jabber>
|
||||
|
||||
<!--
|
||||
You need to add elogger and rlogger entries when using ejabberd.
|
||||
In this case the transport will do the logging.
|
||||
-->
|
||||
|
||||
<log id='elogger'>
|
||||
<host/>
|
||||
<logtype/>
|
||||
<format>%d: [%t] (%h): %s</format>
|
||||
<file>/var/log/jabber/aim-transport-error.log</file>
|
||||
</log>
|
||||
|
||||
<log id='rlogger'>
|
||||
<host/>
|
||||
<logtype>record</logtype>
|
||||
<format>%d %h %s</format>
|
||||
<file>/var/log/jabber/aim-transport-record.log</file>
|
||||
</log>
|
||||
|
||||
<!--
|
||||
ejabberd do not provide XDB services.
|
||||
xdb_file.so is loaded in to handle all XDB requests.
|
||||
-->
|
||||
|
||||
<xdb id="xdb">
|
||||
<host/>
|
||||
<load>
|
||||
<xdb_file>/usr/local/lib/jabber/libjabberdxdbfile.so</xdb_file> <!-- This file is part of jabberd-1.4.x. -->
|
||||
</load>
|
||||
<xdb_file xmlns="jabber:config:xdb_file">
|
||||
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
|
||||
</xdb_file>
|
||||
</xdb>
|
||||
|
||||
<!--
|
||||
Make sure that all host names here are resolveable via DNS if you
|
||||
want the transport to be available to the public.
|
||||
-->
|
||||
|
||||
<service id='aim.SERVER.COM'>
|
||||
<!-- aim-transport configuration. -->
|
||||
<aimtrans xmlns='jabber:config:aimtrans'>
|
||||
<vCard>
|
||||
<FN>AIM/ICQ Transport</FN>
|
||||
<DESC>This is the AIM/ICQ Transport.</DESC>
|
||||
<MAIL>EMAIL@ADDRESS.COM</MAIL>
|
||||
<URL>http://aim-transport.jabberstudio.org/</URL>
|
||||
</vCard>
|
||||
<charset>cp1252</charset>
|
||||
</aimtrans>
|
||||
<!-- aim-transport module. -->
|
||||
<load>
|
||||
<aim_transport>/usr/local/lib/jabber/aim-transport.so</aim_transport>
|
||||
</load>
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The settings below have to match the settings you made
|
||||
in your ejabberd.cfg configuration file.
|
||||
-->
|
||||
|
||||
<service id="icq-linker">
|
||||
<uplink/>
|
||||
<connect>
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>5233</port>
|
||||
<secret>SECRET</secret>
|
||||
</connect>
|
||||
</service>
|
||||
|
||||
<pidfile>/var/run/jabber/aim-transport.pid</pidfile>
|
||||
|
||||
</jabber>
|
||||
@@ -1,136 +0,0 @@
|
||||
<!-- ile.xml -->
|
||||
|
||||
<config>
|
||||
<jabber>
|
||||
<server>127.0.0.1</server>
|
||||
<port>5238</port>
|
||||
<secret>SECRET</secret>
|
||||
<service>ile.SERVER.COM</service>
|
||||
<connectsleep>7</connectsleep> <!-- seconds to wait if we get disconnected -->
|
||||
<language>en</language>
|
||||
<vCard>
|
||||
<FN>I Love Email</FN>
|
||||
<DESC>With this service you can receive email notifications.
|
||||
|
||||
Security warning: Be careful when using this. Your password will travel in clear from your client to your jabber server if you don't use SSL and it will probably travel in clear from the jabber server to your email server. Use with care. This shouldn't be an issue in your Intranet, but it is if you use an ILE installed in a foreign jabber server.</DESC>
|
||||
<MAIL>EMAIL@ADDRESS.COM</MAIL>
|
||||
<URL>http://ile.jabberstudio.org/</URL>
|
||||
</vCard>
|
||||
</jabber>
|
||||
|
||||
<debug>
|
||||
<file>/var/log/jabber/ile.log</file>
|
||||
<level>1</level> <!-- man Net::Jabber::Debug -->
|
||||
</debug>
|
||||
|
||||
<mail>
|
||||
<checkinterval>10</checkinterval> <!-- in minutes -->
|
||||
<timeout>20</timeout> <!-- timeout for IMAP/POP connection, in seconds -->
|
||||
</mail>
|
||||
|
||||
<files>
|
||||
<users>/var/spool/jabber/ile.SERVER.COM/users.db</users>
|
||||
<passwords>/var/spool/jabber/ile.SERVER.COM/passwords.db</passwords>
|
||||
<hosts>/var/spool/jabber/ile.SERVER.COM/hosts.db</hosts>
|
||||
<types>/var/spool/jabber/ile.SERVER.COM/types.db</types>
|
||||
<notifyxa>/var/spool/jabber/ile.SERVER.COM/notifyxa.db</notifyxa>
|
||||
<notifydnd>/var/spool/jabber/ile.SERVER.COM/notifydnd.db</notifydnd>
|
||||
<urls>/var/spool/jabber/ile.SERVER.COM/urls.db</urls>
|
||||
</files>
|
||||
|
||||
<form>
|
||||
<en>
|
||||
<instructions>Please fill in the fields,according to your email account settings and notification preferences</instructions>
|
||||
<title>ILE: Email notification service</title>
|
||||
<email_options>Email account settings</email_options>
|
||||
<user>Username</user>
|
||||
<pass>Password</pass>
|
||||
<host>Hostname</host>
|
||||
<type>Type</type>
|
||||
<newmail>You have received NUM email messages since last time I checked, which was CHECKINTERVAL minutes ago.</newmail>
|
||||
<errorcheck>There was an error while trying to check mail for ACCOUNT.</errorcheck>
|
||||
<notify_options>Notification Options</notify_options>
|
||||
<notifyxa>Notify even when Xtended Away (XA)</notifyxa>
|
||||
<notifydnd>Notify even when Do Not Disturb (DND)</notifydnd>
|
||||
<webmail_url>Webmail URL</webmail_url>
|
||||
<webmail_login>Login to ACCOUNT</webmail_login>
|
||||
<iledesc>ILE: an email notifier component: http://ile.jabberstudio.org</iledesc>
|
||||
</en>
|
||||
|
||||
<es>
|
||||
<instructions>Por favor, rellene los campos del formulario.</instructions>
|
||||
<title>ILE: Servicio de notificación de correo</title>
|
||||
<email_options>Configuración de la cuenta de correo</email_options>
|
||||
<user>Usuario</user>
|
||||
<pass>Clave</pass>
|
||||
<host>Host</host>
|
||||
<type>Tipo</type>
|
||||
<newmail>Ha recibido NUM email(s) desde la última comprobación que fue hace CHECKINTERVAL minutos</newmail>
|
||||
<errorcheck>Ha habido un error en la comprobación del correo para la cuenta ACCOUNT.</errorcheck>
|
||||
<notify_options>Opciones de notificación</notify_options>
|
||||
<notifyxa>Notificar incluso si muy ausente (XA)</notifyxa>
|
||||
<notifydnd>Notificar incluso si no molestar (DND)</notifydnd>
|
||||
<webmail_url>Webmail URL</webmail_url>
|
||||
<webmail_login>Leer correo de ACCOUNT</webmail_login>
|
||||
<iledesc>ILE: un notificador de nuevo email - http://ile.jabberstudio.org</iledesc>
|
||||
</es>
|
||||
|
||||
<ca>
|
||||
<instructions>Ompli els camps del formulari.</instructions>
|
||||
<title>ILE: Servei de notificació de nou email</title>
|
||||
<email_options>Dades del compte de mail</email_options>
|
||||
<user>Usuari</user>
|
||||
<pass>Clau</pass>
|
||||
<host>Host</host>
|
||||
<type>Tipus</type>
|
||||
<newmail>Ha rebut NUM email(s) des de la última comprobació que va ser fa CHECKINTERVAL minuts.</newmail>
|
||||
<errorcheck>S'ha produit un error en la comprobació del correu per al compte ACCOUNT.</errorcheck>
|
||||
<notify_options>Opcions de notificació</notify_options>
|
||||
<notifyxa>Notificar si molt absent (XA)</notifyxa>
|
||||
<notifydnd>Notificar si no molestar (DND)</notifydnd>
|
||||
<webmail_url>Webmail URL</webmail_url>
|
||||
<webmail_login>Llegir correu de ACCOUNT</webmail_login>
|
||||
<iledesc>ILE: un notificador de nou email - http://ile.jabberstudio.org</iledesc>
|
||||
</ca>
|
||||
|
||||
<ro>
|
||||
<!-- Contributed by Adrian Rappa -->
|
||||
<instructions>Va rog completati urmatoarele campuri</instructions>
|
||||
<title>I Love Email: new email notification service</title>
|
||||
<email_options>Email account settings</email_options>
|
||||
<user>Nume utilizator</user>
|
||||
<pass>Parola</pass>
|
||||
<host>Nume gazda</host>
|
||||
<type>Tip</type>
|
||||
<newmail>Ati primit NUM mesaj(e) de la ultima verificare, care a fost acum CHECKINTERVAL minute.</newmail>
|
||||
<errorcheck>A fost eroare in timp ce incercam sa verific posta pentru ACCOUNT.</errorcheck>
|
||||
<notify_options>Notification Options</notify_options>
|
||||
<notifyxa>Notify even when Xtended Away (XA)</notifyxa>
|
||||
<notifydnd>Notify even when Do Not Disturb (DND)</notifydnd>
|
||||
<webmail_url>Webmail URL</webmail_url>
|
||||
<webmail_login>Login to ACCOUNT</webmail_login>
|
||||
<iledesc>ILE: an email notifier component: http://ile.jabberstudio.org</iledesc>
|
||||
</ro>
|
||||
|
||||
<nl>
|
||||
<!-- Contributed by Sander Devrieze -->
|
||||
<instructions>Vul volgende velden in.</instructions>
|
||||
<title>ILE: Dienst voor e-mailnotificaties</title>
|
||||
<email_options>Instellingen van e-mailaccount</email_options>
|
||||
<user>Gebruikersnaam</user>
|
||||
<pass>Wachtwoord</pass>
|
||||
<host>Inkomende mailserver</host>
|
||||
<type>Type verbinding</type>
|
||||
<newmail>U hebt NUM berichten ontvangen sinds CHECKINTERVAL minuten geleden.</newmail>
|
||||
<errorcheck>Fout tijdens controle op nieuwe e-mails bij ACCOUNT. ILE zal deze account niet meer opnieuw controleren tot u uw registratiegegevens wijzigt of opnieuw aanmeldt.</errorcheck>
|
||||
<notify_options>Notificatie-instellingen</notify_options>
|
||||
<notifyxa>Notificeer ook in de status Niet Beschikbaar (XA)</notifyxa>
|
||||
<notifydnd>Notificeer ook in de status Niet Storen (DND)</notifydnd>
|
||||
<webmail_url>URL van webmail</webmail_url>
|
||||
<webmail_login>Aanmelden op ACCOUNT</webmail_login>
|
||||
<iledesc>ILE: een dienst om e-mailnotificaties te ontvangen: http://ile.jabberstudio.org</iledesc>
|
||||
</nl>
|
||||
|
||||
</form>
|
||||
|
||||
</config>
|
||||
@@ -1,149 +0,0 @@
|
||||
<jggtrans>
|
||||
|
||||
<service jid="gg.SERVER.COM"/>
|
||||
|
||||
<!-- This connects the jabber-gg-transport process to ejabberd. -->
|
||||
<connect id="gglinker">
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>5237</port>
|
||||
<secret>SECRET</secret>
|
||||
</connect>
|
||||
|
||||
<register>
|
||||
<!-- This tag contains the message displayed to users at registration time.
|
||||
You can use <p/> and/or <br/> to break lines. Multiple spaces and newlines
|
||||
are converted to just one, so formatting of config file doesn't really matter. -->
|
||||
<instructions>
|
||||
Fill in your GG number (after "username")
|
||||
and password to register on the transport.
|
||||
<p/>To change your information in the GaduGadu directory you need to fill in the other fields.
|
||||
<p/>To remove registration you need to leave the form blank.
|
||||
</instructions>
|
||||
</register>
|
||||
|
||||
<search>
|
||||
<!-- This tag contains the message displayed to users at search time. -->
|
||||
<instructions>
|
||||
To search people:<br/>
|
||||
First fill in surname or family name, nickname, city, birthyear or range of birthyears (eg. 1950-1960)
|
||||
and gender (you may fill in more fields at once).<br/>
|
||||
or<br/>
|
||||
Fill in phone number<br/>
|
||||
or<br/>
|
||||
Fill in the GG number of the person you are searching.
|
||||
</instructions>
|
||||
</search>
|
||||
|
||||
<gateway>
|
||||
<!-- This is message, that may be displayed to user when adding gg contact. -->
|
||||
<desc>Please fill in the GaduGadu number of the person you want to add.</desc>
|
||||
<!-- And this is the prompt for GG number. -->
|
||||
<prompt>GG Nummer</prompt>
|
||||
</gateway>
|
||||
|
||||
<vCard>
|
||||
<FN>Gadu-Gadu Transport</FN>
|
||||
<DESC>This is the Gadu-Gadu Transport.</DESC>
|
||||
<EMAIL>EMAIL@ADDRESS.COM</EMAIL>
|
||||
<URL>http://www.jabberstudio.org/projects/jabber-gg-transport/</URL>
|
||||
</vCard>
|
||||
|
||||
<!-- Default user locale (language).
|
||||
Empty means system locale setting,
|
||||
no (or commented-out) <default_locale> tag means no translations. -->
|
||||
<!-- <default_locale>pl_PL</default_locale> -->
|
||||
|
||||
<!-- Logger configuration.
|
||||
You may configure one logger of type "syslog" and/or one of type "file".
|
||||
You may also not configure logging at all. -->
|
||||
<log type="syslog" facility="local0"/>
|
||||
<log type="file">/var/log/jabber/jabber-gg-transport.log</log>
|
||||
|
||||
<!-- Uncomment this, if you want proxy to be used for Gadu-Gadu connection. -->
|
||||
<!--
|
||||
<proxy>
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>8080</port>
|
||||
</proxy>
|
||||
-->
|
||||
|
||||
<!-- You can change these values according to your needs. -->
|
||||
<conn_timeout>60</conn_timeout>
|
||||
<ping_interval>10</ping_interval>
|
||||
|
||||
<!-- Gadu-Gadu server doesn't seem to answer pings anymore :-(
|
||||
So let's give it 10 year :-) -->
|
||||
<pong_timeout>315360000</pong_timeout>
|
||||
|
||||
<!-- This time after disconnection from Gadu-Gadu server the transport
|
||||
will try to connect again. -->
|
||||
<reconnect>300</reconnect>
|
||||
|
||||
<!-- How long to wait before restart, after jabber server connection is broken
|
||||
negative value means, that jggtrans should terminate. -->
|
||||
<restart_timeout>60</restart_timeout>
|
||||
|
||||
<!-- Delay between the unavailable presence is received from user and loggin out
|
||||
from Gadu-Gadu - for nice <presence type="invisible"/> support. -->
|
||||
<disconnect_delay>5</disconnect_delay>
|
||||
|
||||
<!-- list of Gadu-Gadu servers to use.
|
||||
<hub/> means "use GG hub to find server"
|
||||
<server/> tag should contain server address and may contain "port"
|
||||
attribute with port number. When TLS is available (supported by libgadu)
|
||||
it will be used unless "tls" attribute is set to "no". Please notice,
|
||||
that not all servers will accept TLS connections.
|
||||
Servers (including hub) are tried in order as they appear in <servers/>
|
||||
element.
|
||||
A reasonable default server list is hardcoded in jggtrans.
|
||||
-->
|
||||
<!--
|
||||
<servers>
|
||||
<hub/>
|
||||
<server port="443">217.17.41.90</server>
|
||||
<server tls="no">217.17.41.85</server>
|
||||
<server tls="no">217.17.41.88</server>
|
||||
</servers>
|
||||
-->
|
||||
|
||||
<!-- Spool directory. This is the place, where user info will be stored. -->
|
||||
<!-- Be careful about permissions - users' Gadu-Gadu passwords are stored there. -->
|
||||
<spool>/var/spool/jabber/gg.SERVER.COM/</spool>
|
||||
|
||||
<!-- Where to store pid file. This tag is optional. -->
|
||||
<pidfile>/var/run/jabber/jabber-gg-transport.pid</pidfile>
|
||||
|
||||
<!-- jid allowed to do some administrative task (eg. discovering online users).
|
||||
May be used multiple times. -->
|
||||
<admin>GG_TRANSPORT_ADMIN@SERVER.COM</admin>
|
||||
|
||||
<!-- ACL gives detailed access control to the transport. -->
|
||||
<acl>
|
||||
<!-- Example entries: -->
|
||||
|
||||
<allow who="admin@SERVER.COM" what="iq/query?xmlns=http://jabber.org/protocol/stats"/>
|
||||
<!-- will allow statistics gathering to admin@SERVER.COM -->
|
||||
|
||||
<deny who="*" what="iq/query?xmlns=http://jabber.org/protocol/stats"/>
|
||||
<!-- will deny statistics gathering for anybody else -->
|
||||
|
||||
<!-- <allow who="*@SERVER.COM"/> -->
|
||||
<!-- will allow anything else to users from "SERVER.COM" -->
|
||||
|
||||
<!-- <deny what="iq/query?xmlns=jabber:x:register"/> -->
|
||||
<!-- will deny registration for all other users -->
|
||||
|
||||
<!-- <allow what="presence"/> -->
|
||||
<!-- allow presence from anybody -->
|
||||
|
||||
<!-- <allow what="iq"/> -->
|
||||
<!-- allow iq from anybody -->
|
||||
|
||||
<!-- <allow what="message"/> -->
|
||||
<!-- allow message from anybody -->
|
||||
|
||||
<!-- <deny/> -->
|
||||
<!-- will deny anything else -->
|
||||
</acl>
|
||||
|
||||
</jggtrans>
|
||||
@@ -1,128 +0,0 @@
|
||||
<!-- jit.xml -->
|
||||
|
||||
<jabber>
|
||||
|
||||
<!--
|
||||
You need to add elogger and rlogger entries here when using ejabberd.
|
||||
In this case the transport will do the logging.
|
||||
-->
|
||||
|
||||
<log id='elogger'>
|
||||
<host/>
|
||||
<logtype/>
|
||||
<file>/var/log/jabber/jit-error</file> <!-- WPJabber logs with date. -->
|
||||
</log>
|
||||
|
||||
<log id='rlogger'>
|
||||
<host/>
|
||||
<logtype>record</logtype>
|
||||
<file>/var/log/jabber/jit-record</file> <!-- WPJabber logs with date. -->
|
||||
</log>
|
||||
|
||||
<!--
|
||||
ejabberd do not provide XDB services.
|
||||
xdb_file-jit.so (the renamed xdb_file.so from WPJabber) is
|
||||
loaded in to handle all XDB requests.
|
||||
Read also the documentation in xdb_file/README from the JIT package.
|
||||
-->
|
||||
|
||||
<xdb id="xdb">
|
||||
<host/>
|
||||
<load>
|
||||
<xdb_file>/usr/local/lib/jabber/xdb_file.so</xdb_file> <!-- The xdb_file.so from WPJabber/JIT. -->
|
||||
</load>
|
||||
<xdb_file xmlns="jabber:config:xdb_file">
|
||||
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
|
||||
</xdb_file>
|
||||
</xdb>
|
||||
|
||||
<!--
|
||||
Make sure that all host names here are resolveable via DNS if you
|
||||
want the transport to be available to the public.
|
||||
-->
|
||||
|
||||
<service id="icq.SERVER.COM">
|
||||
<!--
|
||||
Replace SERVER.COM with the same as above to enable sms.
|
||||
-->
|
||||
<host>sms.icq.SERVER.COM</host>
|
||||
<!-- JIT configuration. -->
|
||||
<icqtrans xmlns="jabber:config:icqtrans">
|
||||
<sms>
|
||||
<host>sms.icq.SERVER.COM</host>
|
||||
<!-- Status of virtual "sms-contacts". -->
|
||||
<show>away</show>
|
||||
<status/>
|
||||
</sms>
|
||||
<instructions>Fill in your UIN and password.</instructions>
|
||||
<search>Search ICQ users.</search>
|
||||
<vCard>
|
||||
<FN>ICQ Transport (JIT)</FN>
|
||||
<DESC>This is the Jabber ICQ Transport.</DESC>
|
||||
<MAIL>EMAIL@ADDRESS.COM</MAIL>
|
||||
<URL>http://jit.jabberstudio.org/</URL>
|
||||
</vCard>
|
||||
<!-- Hashtable for users. -->
|
||||
<prime>3907</prime>
|
||||
<!-- Send messages from ICQ as chat to Jabber clients. -->
|
||||
<chat/>
|
||||
<!-- Enable this for ICQ web presence. -->
|
||||
<web/>
|
||||
<!--
|
||||
If you don't want jabber:x:data forms
|
||||
in reg and search uncomment this tag
|
||||
(Not recomended).
|
||||
-->
|
||||
<no_xdata/>
|
||||
<!--
|
||||
This tag is necessary when using ejabberd.
|
||||
In this way JIT will have its own contact list.
|
||||
-->
|
||||
<own_roster/>
|
||||
<!--
|
||||
When present, this tag will tell JIT not to try to
|
||||
get the user's roster (which will take a bit of time
|
||||
to fail in scenarios described above).
|
||||
-->
|
||||
<no_jabber_roster/>
|
||||
<!-- File with stats. -->
|
||||
<user_count_file>/var/spool/jabber/jit-count</user_count_file>
|
||||
<!--
|
||||
Interval beetween checking sessions: ping, messages, acks.
|
||||
-->
|
||||
<session_check>5</session_check>
|
||||
<!-- Reconnect retries. -->
|
||||
<reconnects>5</reconnects>
|
||||
<!--
|
||||
Time in sec when session can be inactive, 0=disabled.
|
||||
-->
|
||||
<session_timeout>18000</session_timeout>
|
||||
<charset>windows-1252</charset>
|
||||
<server>
|
||||
<host port="5190">login.icq.com</host>
|
||||
</server>
|
||||
</icqtrans>
|
||||
<!-- JIT module. -->
|
||||
<load>
|
||||
<icqtrans>/usr/local/lib/jabber/jit.so</icqtrans>
|
||||
</load>
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The settings below have to match the settings you made
|
||||
in your ejabberd.cfg configuration file.
|
||||
-->
|
||||
|
||||
<service id="icq-linker">
|
||||
<host>SERVER.COM</host>
|
||||
<uplink/>
|
||||
<connect>
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>5234</port>
|
||||
<secret>SECRET</secret>
|
||||
</connect>
|
||||
</service>
|
||||
|
||||
<pidfile>/var/run/jabber/jit.pid</pidfile>
|
||||
|
||||
</jabber>
|
||||
@@ -1,118 +0,0 @@
|
||||
<!-- msn-transport.xml -->
|
||||
|
||||
<jabber>
|
||||
|
||||
<!--
|
||||
You need to add elogger and rlogger entries here when using ejabberd.
|
||||
In this case the transport will do the logging.
|
||||
-->
|
||||
|
||||
<log id='elogger'>
|
||||
<host/>
|
||||
<logtype/>
|
||||
<format>%d: [%t] (%h): %s</format>
|
||||
<file>/var/log/jabber/msn-transport-error.log</file>
|
||||
</log>
|
||||
|
||||
<log id='rlogger'>
|
||||
<host/>
|
||||
<logtype>record</logtype>
|
||||
<format>%d %h %s</format>
|
||||
<file>/var/log/jabber/msn-transport-record.log</file>
|
||||
</log>
|
||||
|
||||
<!--
|
||||
ejabberd do not provide XDB services.
|
||||
xdb_file.so is loaded in to handle all XDB requests.
|
||||
-->
|
||||
|
||||
<xdb id="xdb">
|
||||
<host/>
|
||||
<load>
|
||||
<xdb_file>/usr/local/lib/jabber/libjabberdxdbfile.so</xdb_file>
|
||||
</load>
|
||||
<xdb_file xmlns="jabber:config:xdb_file">
|
||||
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
|
||||
</xdb_file>
|
||||
</xdb>
|
||||
|
||||
<!--
|
||||
Make sure that all host names here are resolveable via DNS if you
|
||||
want the transport to be available to the public.
|
||||
-->
|
||||
|
||||
<service id="msn.SERVER.COM">
|
||||
<!-- msn-transport configuration. -->
|
||||
<msntrans xmlns="jabber:config:msntrans">
|
||||
<instructions>Fill in your MSN account and password (eg: user1@hotmail.com). A nickname is optional.</instructions>
|
||||
<vCard>
|
||||
<FN>MSN Transport</FN>
|
||||
<DESC>This is the MSN Transport.</DESC>
|
||||
<EMAIL>EMAIL@ADDRESS.COM</EMAIL>
|
||||
<URL>http://msn-transport.jabberstudio.org/</URL>
|
||||
</vCard>
|
||||
<!--
|
||||
Conference support allows you to create groupchat rooms on the
|
||||
msn-transport and invite MSN users to join.
|
||||
-->
|
||||
<conference id="conference.msn.SERVER.COM">
|
||||
<!--
|
||||
This will make MSN transport invite you to a special groupchat
|
||||
room when more then one user joins a normal one-on-one session.
|
||||
Joining this room will make MSN transport "switch" the session
|
||||
into groupchat mode. If you ignore it, MSN transport will
|
||||
continue to send the messages as one-on-one chats.
|
||||
-->
|
||||
<invite>More than one user entered this chat session. Enter this room to switch to groupchat modus.</invite>
|
||||
<notice>
|
||||
<join> is available</join>
|
||||
<leave> has leaved the room</leave>
|
||||
</notice>
|
||||
</conference>
|
||||
<!-- Enable Hotmail inbox notification. -->
|
||||
<headlines/>
|
||||
<!--
|
||||
Enable fancy friendly names
|
||||
If the user enters a nickname upon registration, and the user has
|
||||
a status message, their MSN friendly name will be "nickname - status message".
|
||||
|
||||
If the user does not enter a nickname on registration, but they do have
|
||||
a status message, their friendly name will just be their status message.
|
||||
|
||||
If the user did enter a nickname on registration, but they have a blank status message,
|
||||
then their friendly name will just be the registered nickname.
|
||||
|
||||
If the user did not enter a nickname on registration, and they have a blank status message,
|
||||
their nickname will just be the username portion of their JID.
|
||||
|
||||
If the above chosen friendly name is too long, then it will be truncated and "..." placed
|
||||
at the end. MSN only supports friendly names of 128 characters, so this is unavoidable.
|
||||
|
||||
If this is disabled, then the registered nick is always sent as the MSN friendly name,
|
||||
or if that is blank, the username portion of their JID is sent instead.
|
||||
-->
|
||||
<fancy_friendly/>
|
||||
</msntrans>
|
||||
<!-- msn-transport module. -->
|
||||
<load>
|
||||
<msntrans>/usr/local/lib/jabber/msn-transport.so</msntrans>
|
||||
</load>
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The settings below have to match the settings you made
|
||||
in your ejabberd.cfg configuration file.
|
||||
-->
|
||||
|
||||
<service id="msn-linker">
|
||||
<uplink/>
|
||||
<connect>
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>5235</port>
|
||||
<secret>SECRET</secret>
|
||||
</connect>
|
||||
</service>
|
||||
|
||||
<pidfile>/var/run/jabber/msn-transport.pid</pidfile>
|
||||
|
||||
</jabber>
|
||||
@@ -1,86 +0,0 @@
|
||||
<!-- yahoo-transport-2.xml -->
|
||||
|
||||
<jabber>
|
||||
|
||||
<!--
|
||||
You need to add the elogger entry here when using ejabberd.
|
||||
In this case the transport will do the logging.
|
||||
-->
|
||||
|
||||
<log id='elogger'>
|
||||
<host/>
|
||||
<logtype/>
|
||||
<format>%d: [%t] (%h): %s</format>
|
||||
<file>/var/log/jabber/yahoo-transport-2-error.log</file>
|
||||
<stderr/>
|
||||
</log>
|
||||
|
||||
<!--
|
||||
ejabberd do not provide XDB services.
|
||||
xdb_file.so is loaded in to handle all XDB requests.
|
||||
-->
|
||||
|
||||
<xdb id="xdb">
|
||||
<host/>
|
||||
<load>
|
||||
<xdb_file>/usr/local/lib/jabber/libjabberdxdbfile.so</xdb_file>
|
||||
</load>
|
||||
<xdb_file xmlns="jabber:config:xdb_file">
|
||||
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
|
||||
</xdb_file>
|
||||
</xdb>
|
||||
|
||||
<!--
|
||||
Make sure that all host names here are resolveable via DNS if you
|
||||
want the transport to be available to the public.
|
||||
-->
|
||||
|
||||
<service id="yahoo.SERVER.COM">
|
||||
<!-- yahoo-transport-2 configuration. -->
|
||||
<config xmlns="jabber:config:yahoo">
|
||||
<vCard>
|
||||
<NAME>Yahoo! Transport</NAME>
|
||||
<FN>vCard not implemented in current version</FN>
|
||||
<DESC>This is the Yahoo! transport.</DESC>
|
||||
<MAIL>EMAIL@ADDRESS.COM</MAIL>
|
||||
<URL>http://yahoo-transport-2.jabberstudio.org/</URL>
|
||||
</vCard>
|
||||
<instructions>Fill in your YAHOO! Messenger username and password to register on this transport.</instructions>
|
||||
<server>scs.msg.yahoo.com</server>
|
||||
<port>5050</port>
|
||||
<!--
|
||||
The character map. This provides character set translation from UTF-8
|
||||
to the indicated character map. See the man page for 'iconv' for available
|
||||
character maps on your platform. CP1252 is the standard Windows character
|
||||
set.
|
||||
-->
|
||||
<charmap>CP1252</charmap>
|
||||
<!--
|
||||
When this element exists, the transport will send new mail notifications as
|
||||
well as a count of unread messages when the user initially logs in.
|
||||
-->
|
||||
<newmail/>
|
||||
</config>
|
||||
<!-- yahoo-transport-2 module. -->
|
||||
<load>
|
||||
<yahoo_transport>/usr/local/lib/jabber/yahoo-transport-2.so</yahoo_transport>
|
||||
</load>
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The settings below have to match the settings you made
|
||||
in your ejabberd.cfg configuration file.
|
||||
-->
|
||||
|
||||
<service id="yahoo-linker">
|
||||
<uplink/>
|
||||
<connect>
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>5236</port>
|
||||
<secret>SECRET</secret>
|
||||
</connect>
|
||||
</service>
|
||||
|
||||
<pidfile>/var/run/jabber/yahoo-transport-2.pid</pidfile>
|
||||
|
||||
</jabber>
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/bin/sh
|
||||
#########################################################
|
||||
#
|
||||
# aim-transport -- script to start aim-transport.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/jabberd-aim-transport
|
||||
CONF=/etc/jabber/aim-transport.xml
|
||||
NAME=jabberd-aim-transport
|
||||
HOME=/etc/jabber/
|
||||
USER=ejabberd
|
||||
|
||||
#########################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
debug)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in debugging mode."
|
||||
$DAEMON -D -H $HOME -c $CONF &
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON -H $HOME -c $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
;;
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/bin/sh
|
||||
#########################################################
|
||||
#
|
||||
# ile -- script to start ILE.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/ile.pl
|
||||
NAME=ile.pl
|
||||
CONF=/etc/jabber/ile.xml
|
||||
USER=ejabberd
|
||||
|
||||
#########################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
debug)
|
||||
echo "Not implemented yet. Starting in normal mode"
|
||||
$0 start
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
;;
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|status|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/bin/sh
|
||||
#########################################################
|
||||
#
|
||||
# jabber-gg-transport -- script to start jabber-gg-transport.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/jggtrans
|
||||
CONF=/etc/jabber/jabber-gg-transport.xml
|
||||
NAME=jggtrans
|
||||
HOME=/etc/jabber/
|
||||
USER=ejabberd
|
||||
|
||||
#########################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
debug)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in debugging mode."
|
||||
$DAEMON -D -H $HOME -c $CONF &
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
rm /var/run/jabber/jabber-gg-transport.pid
|
||||
;;
|
||||
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/bin/sh
|
||||
#########################################################
|
||||
#
|
||||
# jit -- script to start JIT.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/wpjabber-jit
|
||||
CONF=/etc/jabber/jit.xml
|
||||
NAME=wpjabber-jit
|
||||
HOME=/etc/jabber/
|
||||
USER=ejabberd
|
||||
|
||||
#########################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
debug)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in debugging mode."
|
||||
$DAEMON -D -H $HOME -c $CONF &
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON -H $HOME -c $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
;;
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/bin/sh
|
||||
#########################################################
|
||||
#
|
||||
# msn-transport -- script to start MSN Transport.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/jabberd-msn-transport
|
||||
CONF=/etc/jabber/msn-transport.xml
|
||||
NAME=jabberd-msn-transport
|
||||
HOME=/etc/jabber/
|
||||
USER=ejabberd
|
||||
|
||||
#########################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
strace)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in strace mode."
|
||||
strace -o /opt/ejabberd/var/log/jabber/strace.log $DAEMON -H $HOME -c $CONF &
|
||||
;;
|
||||
debug)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in debugging mode."
|
||||
$DAEMON -D -H $HOME -c $CONF &
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON -H $HOME -c $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
;;
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/bin/sh
|
||||
##############################################################
|
||||
#
|
||||
# yahoo-transport-2 -- script to start Yahoo-transport-2.
|
||||
#
|
||||
#############################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/jabberd-yahoo-transport-2
|
||||
CONF=/etc/jabber/yahoo-transport-2.xml
|
||||
NAME=jabberd-yahoo-transport-2
|
||||
HOME=/etc/jabber/
|
||||
USER=ejabberd
|
||||
|
||||
#############################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
debug)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in debugging mode."
|
||||
$DAEMON -D -H $HOME -c $CONF &
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON -H $HOME -c $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
;;
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -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
|
||||
@@ -30,15 +26,16 @@ else
|
||||
INIT_USER=$(INSTALLUSER)
|
||||
endif
|
||||
|
||||
EFLAGS += @ERLANG_SSL39@ -pa .
|
||||
EFLAGS += @ERLANG_SSLVER@ -pa .
|
||||
|
||||
# make debug=true to compile Erlang module with debug informations.
|
||||
ifdef debug
|
||||
EFLAGS+=+debug_info
|
||||
EFLAGS+=+debug_info +export_all
|
||||
endif
|
||||
|
||||
ifdef ejabberd_debug
|
||||
EFLAGS+=-Dejabberd_debug
|
||||
DEBUGTOOLS = p1_prof.erl etop_tr.erl
|
||||
ifdef debugtools
|
||||
SOURCES+=$(DEBUGTOOLS)
|
||||
endif
|
||||
|
||||
ifeq (@hipe@, true)
|
||||
@@ -57,6 +54,11 @@ ifeq (@transient_supervisors@, false)
|
||||
EFLAGS+=-DNO_TRANSIENT_SUPERVISORS
|
||||
endif
|
||||
|
||||
ifeq (@md2@, true)
|
||||
EFLAGS+=-DHAVE_MD2
|
||||
ERLANG_CFLAGS += -DHAVE_MD2
|
||||
endif
|
||||
|
||||
INSTALL_EPAM=
|
||||
ifeq (@pam@, pam)
|
||||
INSTALL_EPAM=install -m 750 $(O_USER) epam $(PBINDIR)
|
||||
@@ -65,11 +67,12 @@ 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@ @ejabberd_zlib@
|
||||
ERLSHLIBS =
|
||||
ERLBEHAVS = cyrsasl.erl gen_mod.erl p1_fsm.erl
|
||||
SOURCES_ALL = $(wildcard *.erl)
|
||||
SOURCES = $(filter-out $(ERLBEHAVS),$(SOURCES_ALL))
|
||||
SOURCES_MISC = $(ERLBEHAVS) $(DEBUGTOOLS)
|
||||
SOURCES += $(filter-out $(SOURCES_MISC),$(SOURCES_ALL))
|
||||
ERLBEHAVBEAMS = $(ERLBEHAVS:.erl=.beam)
|
||||
BEAMS = $(SOURCES:.erl=.beam)
|
||||
|
||||
@@ -110,6 +113,9 @@ MSGSDIR = $(PRIVDIR)/msgs
|
||||
# /var/lib/ejabberd/
|
||||
SPOOLDIR = $(DESTDIR)@localstatedir@/lib/ejabberd
|
||||
|
||||
# /var/lock/ejabberdctl
|
||||
CTLLOCKDIR = $(DESTDIR)@localstatedir@/lock/ejabberdctl
|
||||
|
||||
# /var/lib/ejabberd/.erlang.cookie
|
||||
COOKIEFILE = $(SPOOLDIR)/.erlang.cookie
|
||||
|
||||
@@ -138,7 +144,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,,'`; \
|
||||
@@ -154,15 +160,13 @@ mostlyclean-recursive maintainer-clean-recursive:
|
||||
$(ERLSHLIBS): %.so: %.c
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) \
|
||||
$(subst ../,,$(subst .so,.c,$@)) \
|
||||
$(EXPAT_LIBS) \
|
||||
$(EXPAT_CFLAGS) \
|
||||
$(ERLANG_LIBS) \
|
||||
$(ERLANG_CFLAGS) \
|
||||
-o $@ \
|
||||
$(DYNAMIC_LIB_CFLAGS)
|
||||
|
||||
translations:
|
||||
../contrib/extract_translations/prepare-translation.sh -updateall
|
||||
../tools/extract_translations/prepare-translation.sh -updateall
|
||||
|
||||
install: all
|
||||
#
|
||||
@@ -232,6 +236,11 @@ install: all
|
||||
chmod -R 750 $(SPOOLDIR)
|
||||
[ ! -f $(COOKIEFILE) ] || { $(CHOWN_COMMAND) @INSTALLUSER@ $(COOKIEFILE) >$(CHOWN_OUTPUT) ; chmod 400 $(COOKIEFILE) ; }
|
||||
#
|
||||
# ejabberdctl lock directory
|
||||
install -d -m 750 $(O_USER) $(CTLLOCKDIR)
|
||||
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(CTLLOCKDIR) >$(CHOWN_OUTPUT)
|
||||
chmod -R 750 $(CTLLOCKDIR)
|
||||
#
|
||||
# Log directory
|
||||
install -d -m 750 $(O_USER) $(LOGDIR)
|
||||
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(LOGDIR) >$(CHOWN_OUTPUT)
|
||||
@@ -239,9 +248,16 @@ install: all
|
||||
#
|
||||
# Documentation
|
||||
install -d $(DOCDIR)
|
||||
install ../doc/guide.html $(DOCDIR)
|
||||
install ../doc/*.png $(DOCDIR)
|
||||
install ../doc/*.txt $(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) \
|
||||
|| echo "No ../doc/guide.pdf was built"
|
||||
install -m 644 ../COPYING $(DOCDIR)
|
||||
|
||||
uninstall: uninstall-binary
|
||||
|
||||
@@ -265,9 +281,10 @@ uninstall-all: uninstall-binary
|
||||
rm -rf $(ETCDIR)
|
||||
rm -rf $(EJABBERDDIR)
|
||||
rm -rf $(SPOOLDIR)
|
||||
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
|
||||
@@ -277,7 +294,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
|
||||
@@ -287,11 +303,89 @@ Makefile: Makefile.in
|
||||
dialyzer: $(BEAMS)
|
||||
@dialyzer -c .
|
||||
|
||||
LASTSVNREVCHANGELOG = 2075
|
||||
changelog:
|
||||
svn up -r $(LASTSVNREVCHANGELOG) ../ChangeLog
|
||||
mv ../ChangeLog ../ChangeLog.old
|
||||
svn2cl -r BASE:$(LASTSVNREVCHANGELOG) -o ../ChangeLog --group-by-day \
|
||||
--separate-daylogs --break-before-msg --reparagraph ..
|
||||
cat ../ChangeLog.old >> ../ChangeLog
|
||||
rm ../ChangeLog.old
|
||||
## 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
|
||||
|
||||
@@ -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
|
||||
@@ -85,17 +82,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
|
||||
@@ -127,8 +122,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 +130,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
|
||||
@@ -164,10 +155,10 @@ 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:$(DLL) $(OBJECT)
|
||||
|
||||
@@ -1,38 +1,4 @@
|
||||
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_DEFUN([AM_WITH_ZLIB],
|
||||
[ AC_ARG_WITH(zlib,
|
||||
[AC_HELP_STRING([--with-zlib=PREFIX], [prefix where zlib is installed])])
|
||||
|
||||
@@ -68,7 +34,7 @@ if test x"$ejabberd_zlib" != x; then
|
||||
fi
|
||||
])
|
||||
|
||||
AC_DEFUN(AM_WITH_PAM,
|
||||
AC_DEFUN([AM_WITH_PAM],
|
||||
[ AC_ARG_WITH(pam,
|
||||
[AC_HELP_STRING([--with-pam=PREFIX], [prefix where PAM is installed])])
|
||||
if test x"$pam" != x; then
|
||||
@@ -103,7 +69,7 @@ if test x"$pam" != x; then
|
||||
fi
|
||||
])
|
||||
|
||||
AC_DEFUN(AM_WITH_ERLANG,
|
||||
AC_DEFUN([AM_WITH_ERLANG],
|
||||
[ AC_ARG_WITH(erlang,
|
||||
[AC_HELP_STRING([--with-erlang=PREFIX], [path to erlc and erl])])
|
||||
|
||||
@@ -121,20 +87,27 @@ AC_DEFUN(AM_WITH_ERLANG,
|
||||
-author('alexey@sevcom.net').
|
||||
|
||||
-export([[start/0]]).
|
||||
-include_lib("ssl/include/ssl_pkix.hrl").
|
||||
|
||||
start() ->
|
||||
EIDirS = code:lib_dir("erl_interface") ++ "\n",
|
||||
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().
|
||||
|
||||
-[ifdef]('id-pkix').
|
||||
ssldef() -> "-DSSL39\n".
|
||||
-else.
|
||||
ssldef() -> "\n".
|
||||
-endif.
|
||||
ssldef() ->
|
||||
OTP = (catch erlang:system_info(otp_release)),
|
||||
if
|
||||
OTP >= "R14" -> "-DSSL40\n";
|
||||
OTP >= "R12" -> "-DSSL39\n";
|
||||
true -> "\n"
|
||||
end.
|
||||
|
||||
%% return physical architecture based on OS/Processor
|
||||
archname() ->
|
||||
@@ -184,7 +157,9 @@ _EOF
|
||||
# Second line
|
||||
ERLANG_EI_LIB=`cat conftest.out | head -n 2 | tail -n 1`
|
||||
# Third line
|
||||
ERLANG_SSL39=`cat conftest.out | head -n 3 | tail -n 1`
|
||||
ERLANG_SSLVER=`cat conftest.out | head -n 3 | tail -n 1`
|
||||
# Fourth line
|
||||
ERLANG_EXMPP=`cat conftest.out | head -n 4 | tail -n 1`
|
||||
# End line
|
||||
ERLANG_DIR=`cat conftest.out | tail -n 1`
|
||||
|
||||
@@ -193,12 +168,13 @@ _EOF
|
||||
|
||||
AC_SUBST(ERLANG_CFLAGS)
|
||||
AC_SUBST(ERLANG_LIBS)
|
||||
AC_SUBST(ERLANG_SSL39)
|
||||
AC_SUBST(ERLANG_SSLVER)
|
||||
AC_SUBST(ERLANG_EXMPP)
|
||||
AC_SUBST(ERLC)
|
||||
AC_SUBST(ERL)
|
||||
])
|
||||
|
||||
AC_DEFUN(AC_MOD_ENABLE,
|
||||
AC_DEFUN([AC_MOD_ENABLE],
|
||||
[
|
||||
$1=
|
||||
make_$1=
|
||||
@@ -217,98 +193,8 @@ 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_DEFUN([AM_WITH_OPENSSL],
|
||||
[ AC_ARG_WITH(openssl,
|
||||
[AC_HELP_STRING([--with-openssl=PREFIX], [prefix where OPENSSL is installed])])
|
||||
unset SSL_LIBS;
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 18 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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,17 +245,21 @@ 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_shared_roster:is_user_in_group({User, Server}, G, Host);
|
||||
@@ -197,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} ->
|
||||
@@ -218,24 +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 regexp:first_match(String, RegExp) of
|
||||
try re:run(String, RegExp, [{capture, none}]) of
|
||||
nomatch ->
|
||||
false;
|
||||
{match, _, _} ->
|
||||
true;
|
||||
{error, ErrDesc} ->
|
||||
match ->
|
||||
true
|
||||
catch
|
||||
_:ErrDesc ->
|
||||
?ERROR_MSG(
|
||||
"Wrong regexp ~p in ACL: ~p",
|
||||
[RegExp, lists:flatten(regexp:format_error(ErrDesc))]),
|
||||
"Wrong regexp ~p in ACL:~n~p",
|
||||
[RegExp, ErrDesc]),
|
||||
false
|
||||
end.
|
||||
|
||||
%% @spec (String, Glob) -> bool()
|
||||
%% String = string() | undefined
|
||||
%% Glob = string()
|
||||
|
||||
is_glob_match(String, Glob) ->
|
||||
is_regexp_match(String, regexp:sh_to_awk(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)).
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : adhoc.erl
|
||||
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%% Purpose : Provide helper functions for ad-hoc commands (JEP-0050)
|
||||
%%% Purpose : Provide helper functions for ad-hoc commands (XEP-0050)
|
||||
%%% Created : 31 Oct 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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,47 +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,
|
||||
if XData ->
|
||||
Others = lists:delete(XData, AllEls);
|
||||
true ->
|
||||
Others = 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).
|
||||
|
||||
@@ -109,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,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 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
|
||||
|
||||
@@ -0,0 +1,604 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : cache_tab.erl
|
||||
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Description : Caching key-value table
|
||||
%%%
|
||||
%%% Created : 29 Aug 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(cache_tab).
|
||||
|
||||
-define(GEN_SERVER, gen_server).
|
||||
|
||||
-behaviour(?GEN_SERVER).
|
||||
|
||||
%% API
|
||||
-export([start_link/4, new/2, delete/1, delete/3, lookup/3,
|
||||
insert/4, info/2, tab2list/1, setopts/2,
|
||||
dirty_lookup/3, dirty_insert/4, dirty_delete/3,
|
||||
all/0, test/0]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(state, {tab = treap:empty(),
|
||||
name,
|
||||
size = 0,
|
||||
owner,
|
||||
max_size,
|
||||
life_time,
|
||||
warn,
|
||||
hits = 0,
|
||||
miss = 0,
|
||||
procs_num,
|
||||
cache_missed,
|
||||
lru,
|
||||
shrink_size}).
|
||||
|
||||
-define(PROCNAME, ?MODULE).
|
||||
-define(CALL_TIMEOUT, 60000).
|
||||
|
||||
%% Defaults
|
||||
-define(MAX_SIZE, 1000).
|
||||
-define(WARN, true).
|
||||
-define(CACHE_MISSED, true).
|
||||
-define(LRU, true).
|
||||
-define(LIFETIME, 600). %% 10 minutes
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
start_link(Proc, Tab, Opts, Owner) ->
|
||||
?GEN_SERVER:start_link({local, Proc}, ?MODULE,
|
||||
[Tab, Opts, get_proc_num(), Owner], []).
|
||||
|
||||
new(Tab, Opts) ->
|
||||
Res = lists:flatmap(
|
||||
fun(Proc) ->
|
||||
Spec = {{Tab, Proc},
|
||||
{?MODULE, start_link,
|
||||
[Proc, Tab, Opts, self()]},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[?MODULE]},
|
||||
case supervisor:start_child(cache_tab_sup, Spec) of
|
||||
{ok, _Pid} ->
|
||||
[ok];
|
||||
R ->
|
||||
[R]
|
||||
end
|
||||
end, get_all_procs(Tab)),
|
||||
case lists:filter(fun(ok) -> false; (_) -> true end, Res) of
|
||||
[] ->
|
||||
ok;
|
||||
Err ->
|
||||
{error, Err}
|
||||
end.
|
||||
|
||||
delete(Tab) ->
|
||||
lists:foreach(
|
||||
fun(Proc) ->
|
||||
supervisor:terminate_child(cache_tab_sup, {Tab, Proc}),
|
||||
supervisor:delete_child(cache_tab_sup, {Tab, Proc})
|
||||
end, get_all_procs(Tab)).
|
||||
|
||||
delete(Tab, Key, F) ->
|
||||
?GEN_SERVER:call(
|
||||
get_proc_by_hash(Tab, Key), {delete, Key, F}, ?CALL_TIMEOUT).
|
||||
|
||||
dirty_delete(Tab, Key, F) ->
|
||||
F(),
|
||||
?GEN_SERVER:call(
|
||||
get_proc_by_hash(Tab, Key), {cache_delete, Key}, ?CALL_TIMEOUT).
|
||||
|
||||
lookup(Tab, Key, F) ->
|
||||
?GEN_SERVER:call(
|
||||
get_proc_by_hash(Tab, Key), {lookup, Key, F}, ?CALL_TIMEOUT).
|
||||
|
||||
dirty_lookup(Tab, Key, F) ->
|
||||
Proc = get_proc_by_hash(Tab, Key),
|
||||
case ?GEN_SERVER:call(Proc, {cache_lookup, Key}, ?CALL_TIMEOUT) of
|
||||
{ok, '$cached_mismatch'} ->
|
||||
error;
|
||||
{ok, Val} ->
|
||||
{ok, Val};
|
||||
_ ->
|
||||
{Result, NewVal} = case F() of
|
||||
{ok, Val} ->
|
||||
{{ok, Val}, Val};
|
||||
_ ->
|
||||
{error, '$cached_mismatch'}
|
||||
end,
|
||||
?GEN_SERVER:call(
|
||||
Proc, {cache_insert, Key, NewVal}, ?CALL_TIMEOUT),
|
||||
Result
|
||||
end.
|
||||
|
||||
insert(Tab, Key, Val, F) ->
|
||||
?GEN_SERVER:call(
|
||||
get_proc_by_hash(Tab, Key), {insert, Key, Val, F}, ?CALL_TIMEOUT).
|
||||
|
||||
dirty_insert(Tab, Key, Val, F) ->
|
||||
F(),
|
||||
?GEN_SERVER:call(
|
||||
get_proc_by_hash(Tab, Key), {cache_insert, Key, Val}, ?CALL_TIMEOUT).
|
||||
|
||||
info(Tab, Info) ->
|
||||
case lists:map(
|
||||
fun(Proc) ->
|
||||
?GEN_SERVER:call(Proc, {info, Info}, ?CALL_TIMEOUT)
|
||||
end, get_all_procs(Tab)) of
|
||||
Res when Info == size ->
|
||||
{ok, lists:sum(Res)};
|
||||
Res when Info == all ->
|
||||
{ok, Res};
|
||||
Res when Info == ratio ->
|
||||
{H, M} = lists:foldl(
|
||||
fun({Hits, Miss}, {HitsAcc, MissAcc}) ->
|
||||
{HitsAcc + Hits, MissAcc + Miss}
|
||||
end, {0, 0}, Res),
|
||||
{ok, [{hits, H}, {miss, M}]};
|
||||
_ ->
|
||||
{error, badarg}
|
||||
end.
|
||||
|
||||
setopts(Tab, Opts) ->
|
||||
lists:foreach(
|
||||
fun(Proc) ->
|
||||
?GEN_SERVER:call(Proc, {setopts, Opts}, ?CALL_TIMEOUT)
|
||||
end, get_all_procs(Tab)).
|
||||
|
||||
tab2list(Tab) ->
|
||||
lists:flatmap(
|
||||
fun(Proc) ->
|
||||
?GEN_SERVER:call(Proc, tab2list, ?CALL_TIMEOUT)
|
||||
end, get_all_procs(Tab)).
|
||||
|
||||
all() ->
|
||||
lists:usort(
|
||||
[Tab || {{Tab, _}, _, _, _} <- supervisor:which_children(cache_tab_sup)]).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([Tab, Opts, N, Pid]) ->
|
||||
State = #state{procs_num = N,
|
||||
owner = Pid,
|
||||
name = Tab},
|
||||
{ok, do_setopts(State, Opts)}.
|
||||
|
||||
handle_call({lookup, Key, F}, _From, #state{tab = T} = State) ->
|
||||
CleanPrio = clean_priority(State#state.life_time),
|
||||
case treap:lookup(Key, T) of
|
||||
{ok, Prio, Val} when (State#state.lru == true) or (Prio =< CleanPrio) ->
|
||||
Hits = State#state.hits,
|
||||
NewState = treap_update(Key, Val, State#state{hits = Hits + 1}),
|
||||
case Val of
|
||||
'$cached_mismatch' ->
|
||||
{reply, error, NewState};
|
||||
_ ->
|
||||
{reply, {ok, Val}, NewState}
|
||||
end;
|
||||
_ ->
|
||||
case catch F() of
|
||||
{ok, Val} ->
|
||||
Miss = State#state.miss,
|
||||
NewState = treap_insert(Key, Val, State),
|
||||
{reply, {ok, Val}, NewState#state{miss = Miss + 1}};
|
||||
{'EXIT', Reason} ->
|
||||
print_error(lookup, [Key], Reason, State),
|
||||
{reply, error, State};
|
||||
_ ->
|
||||
Miss = State#state.miss,
|
||||
NewState = State#state{miss = Miss + 1},
|
||||
if State#state.cache_missed ->
|
||||
{reply, error,
|
||||
treap_insert(Key, '$cached_mismatch', NewState)};
|
||||
true ->
|
||||
{reply, error, NewState}
|
||||
end
|
||||
end
|
||||
end;
|
||||
handle_call({cache_lookup, Key}, _From, #state{tab = T} = State) ->
|
||||
CleanPrio = clean_priority(State#state.life_time),
|
||||
case treap:lookup(Key, T) of
|
||||
{ok, Prio, Val} when (State#state.lru == true) or (Prio =< CleanPrio) ->
|
||||
Hits = State#state.hits,
|
||||
NewState = treap_update(Key, Val, State#state{hits = Hits + 1}),
|
||||
{reply, {ok, Val}, NewState};
|
||||
_ ->
|
||||
Miss = State#state.miss,
|
||||
NewState = State#state{miss = Miss + 1},
|
||||
{reply, error, NewState}
|
||||
end;
|
||||
handle_call({insert, Key, Val, F}, _From, #state{tab = T} = State) ->
|
||||
case treap:lookup(Key, T) of
|
||||
{ok, _Prio, Val} ->
|
||||
{reply, ok, treap_update(Key, Val, State)};
|
||||
_ ->
|
||||
case catch F() of
|
||||
{'EXIT', Reason} ->
|
||||
print_error(insert, [Key, Val], Reason, State),
|
||||
{reply, ok, State};
|
||||
_ ->
|
||||
{reply, ok, treap_insert(Key, Val, State)}
|
||||
end
|
||||
end;
|
||||
handle_call({cache_insert, _, '$cached_mismatch'}, _From,
|
||||
#state{cache_missed = false} = State) ->
|
||||
{reply, ok, State};
|
||||
handle_call({cache_insert, Key, Val}, _From, State) ->
|
||||
{reply, ok, treap_insert(Key, Val, State)};
|
||||
handle_call({delete, Key, F}, _From, State) ->
|
||||
NewState = treap_delete(Key, State),
|
||||
case catch F() of
|
||||
{'EXIT', Reason} ->
|
||||
print_error(delete, [Key], Reason, State);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{reply, ok, NewState};
|
||||
handle_call({cache_delete, Key}, _From, State) ->
|
||||
NewState = treap_delete(Key, State),
|
||||
{reply, ok, NewState};
|
||||
handle_call({info, Info}, _From, State) ->
|
||||
Res = case Info of
|
||||
size ->
|
||||
State#state.size;
|
||||
ratio ->
|
||||
{State#state.hits, State#state.miss};
|
||||
all ->
|
||||
[{max_size, State#state.max_size},
|
||||
{life_time, State#state.life_time},
|
||||
{shrink_size, State#state.shrink_size},
|
||||
{size, State#state.size},
|
||||
{owner, State#state.owner},
|
||||
{hits, State#state.hits},
|
||||
{miss, State#state.miss},
|
||||
{cache_missed, State#state.cache_missed},
|
||||
{lru, State#state.lru},
|
||||
{warn, State#state.warn}];
|
||||
_ ->
|
||||
badarg
|
||||
end,
|
||||
{reply, Res, State};
|
||||
handle_call(tab2list, _From, #state{tab = T} = State) ->
|
||||
Res = treap:fold(
|
||||
fun({Key, _, Val}, Acc) ->
|
||||
[{Key, Val}|Acc]
|
||||
end, [], T),
|
||||
{reply, Res, State};
|
||||
handle_call({setopts, Opts}, _From, State) ->
|
||||
{reply, ok, do_setopts(State, Opts)};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
do_setopts(#state{procs_num = N} = State, Opts) ->
|
||||
MaxSize = case {proplists:get_value(max_size, Opts),
|
||||
State#state.max_size} of
|
||||
{MS, _} when is_integer(MS), MS > 0 ->
|
||||
round(MS/N);
|
||||
{unlimited, _} ->
|
||||
unlimited;
|
||||
{_, undefined} ->
|
||||
round(?MAX_SIZE/N);
|
||||
{_, MS} ->
|
||||
MS
|
||||
end,
|
||||
LifeTime = case {proplists:get_value(life_time, Opts),
|
||||
State#state.life_time} of
|
||||
{LT, _} when is_integer(LT), LT > 0 ->
|
||||
LT*1000*1000;
|
||||
{unlimited, _} ->
|
||||
unlimited;
|
||||
{_, undefined} ->
|
||||
?LIFETIME*1000*1000;
|
||||
{_, LT} ->
|
||||
LT
|
||||
end,
|
||||
ShrinkSize = case {proplists:get_value(shrink_size, Opts),
|
||||
State#state.shrink_size} of
|
||||
{SS, _} when is_integer(SS), SS > 0 ->
|
||||
round(SS/N);
|
||||
_ when is_integer(MaxSize) ->
|
||||
round(MaxSize/2);
|
||||
_ ->
|
||||
unlimited
|
||||
end,
|
||||
Warn = case {proplists:get_value(warn, Opts),
|
||||
State#state.warn} of
|
||||
{true, _} ->
|
||||
true;
|
||||
{false, _} ->
|
||||
false;
|
||||
{_, undefined} ->
|
||||
?WARN;
|
||||
{_, W} ->
|
||||
W
|
||||
end,
|
||||
CacheMissed = case proplists:get_value(
|
||||
cache_missed, Opts, State#state.cache_missed) of
|
||||
false ->
|
||||
false;
|
||||
true ->
|
||||
true;
|
||||
_ ->
|
||||
?CACHE_MISSED
|
||||
end,
|
||||
LRU = case proplists:get_value(
|
||||
lru, Opts, State#state.lru) of
|
||||
false ->
|
||||
false;
|
||||
true ->
|
||||
true;
|
||||
_ ->
|
||||
?LRU
|
||||
end,
|
||||
State#state{max_size = MaxSize,
|
||||
warn = Warn,
|
||||
life_time = LifeTime,
|
||||
cache_missed = CacheMissed,
|
||||
lru = LRU,
|
||||
shrink_size = ShrinkSize}.
|
||||
|
||||
get_proc_num() ->
|
||||
erlang:system_info(logical_processors).
|
||||
|
||||
get_proc_by_hash(Tab, Term) ->
|
||||
N = erlang:phash2(Term, get_proc_num()) + 1,
|
||||
get_proc(Tab, N).
|
||||
|
||||
get_proc(Tab, N) ->
|
||||
list_to_atom(atom_to_list(?PROCNAME) ++ "_" ++
|
||||
atom_to_list(Tab) ++ "_" ++ integer_to_list(N)).
|
||||
|
||||
get_all_procs(Tab) ->
|
||||
[get_proc(Tab, N) || N <- lists:seq(1, get_proc_num())].
|
||||
|
||||
now_priority() ->
|
||||
{MSec, Sec, USec} = now(),
|
||||
-((MSec*1000000 + Sec)*1000000 + USec).
|
||||
|
||||
clean_priority(LifeTime) ->
|
||||
if is_integer(LifeTime) ->
|
||||
now_priority() + LifeTime;
|
||||
true ->
|
||||
unlimited
|
||||
end.
|
||||
|
||||
treap_update(Key, Val, #state{tab = T, lru = LRU} = State) ->
|
||||
if LRU ->
|
||||
Priority = now_priority(),
|
||||
NewT = treap:insert(Key, Priority, Val, T),
|
||||
State#state{tab = NewT};
|
||||
true ->
|
||||
State
|
||||
end.
|
||||
|
||||
treap_insert(Key, Val, State) ->
|
||||
State1 = clean_treap(State),
|
||||
#state{size = Size} = State2 = shrink_treap(State1),
|
||||
T = State2#state.tab,
|
||||
case treap:lookup(Key, T) of
|
||||
{ok, _, Val} ->
|
||||
treap_update(Key, Val, State2);
|
||||
{ok, _, _} ->
|
||||
NewT = treap:insert(Key, now_priority(), Val, T),
|
||||
State2#state{tab = NewT};
|
||||
_ ->
|
||||
NewT = treap:insert(Key, now_priority(), Val, T),
|
||||
State2#state{tab = NewT, size = Size+1}
|
||||
end.
|
||||
|
||||
treap_delete(Key, #state{tab = T, size = Size} = State) ->
|
||||
case treap:lookup(Key, T) of
|
||||
{ok, _, _} ->
|
||||
NewT = treap:delete(Key, T),
|
||||
clean_treap(State#state{tab = NewT, size = Size-1});
|
||||
_ ->
|
||||
State
|
||||
end.
|
||||
|
||||
clean_treap(#state{tab = T, size = Size, life_time = LifeTime} = State) ->
|
||||
if is_integer(LifeTime) ->
|
||||
Priority = now_priority(),
|
||||
{Cleaned, NewT} = clean_treap(T, Priority + LifeTime, 0),
|
||||
State#state{size = Size - Cleaned, tab = NewT};
|
||||
true ->
|
||||
State
|
||||
end.
|
||||
|
||||
clean_treap(Treap, CleanPriority, N) ->
|
||||
case treap:is_empty(Treap) of
|
||||
true ->
|
||||
{N, Treap};
|
||||
false ->
|
||||
{_Key, Priority, _Value} = treap:get_root(Treap),
|
||||
if Priority > CleanPriority ->
|
||||
clean_treap(treap:delete_root(Treap), CleanPriority, N+1);
|
||||
true ->
|
||||
{N, Treap}
|
||||
end
|
||||
end.
|
||||
|
||||
shrink_treap(#state{tab = T,
|
||||
max_size = MaxSize,
|
||||
shrink_size = ShrinkSize,
|
||||
warn = Warn,
|
||||
size = Size} = State) when Size >= MaxSize ->
|
||||
if Warn ->
|
||||
?WARNING_MSG("shrinking table:~n"
|
||||
"** Table: ~p~n"
|
||||
"** Processes Number: ~p~n"
|
||||
"** Max Size: ~p items~n"
|
||||
"** Shrink Size: ~p items~n"
|
||||
"** Life Time: ~p microseconds~n"
|
||||
"** Hits/Miss: ~p/~p~n"
|
||||
"** Owner: ~p~n"
|
||||
"** Cache Missed: ~p~n"
|
||||
"** Instruction: you have to tune cacheing options"
|
||||
" if this message repeats too frequently",
|
||||
[State#state.name, State#state.procs_num,
|
||||
MaxSize, ShrinkSize, State#state.life_time,
|
||||
State#state.hits, State#state.miss,
|
||||
State#state.owner, State#state.cache_missed]);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{Shrinked, NewT} = shrink_treap(T, ShrinkSize, 0),
|
||||
State#state{tab = NewT, size = Size - Shrinked};
|
||||
shrink_treap(State) ->
|
||||
State.
|
||||
|
||||
shrink_treap(T, ShrinkSize, ShrinkSize) ->
|
||||
{ShrinkSize, T};
|
||||
shrink_treap(T, ShrinkSize, N) ->
|
||||
case treap:is_empty(T) of
|
||||
true ->
|
||||
{N, T};
|
||||
false ->
|
||||
shrink_treap(treap:delete_root(T), ShrinkSize, N+1)
|
||||
end.
|
||||
|
||||
print_error(Operation, Args, Reason, State) ->
|
||||
?ERROR_MSG("callback failed:~n"
|
||||
"** Tab: ~p~n"
|
||||
"** Owner: ~p~n"
|
||||
"** Operation: ~p~n"
|
||||
"** Args: ~p~n"
|
||||
"** Reason: ~p",
|
||||
[State#state.name, State#state.owner,
|
||||
Operation, Args, Reason]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Tests
|
||||
%%--------------------------------------------------------------------
|
||||
-define(lookup, dirty_lookup).
|
||||
-define(delete, dirty_delete).
|
||||
-define(insert, dirty_insert).
|
||||
%%-define(lookup, lookup).
|
||||
%%-define(delete, delete).
|
||||
%%-define(insert, insert).
|
||||
|
||||
test() ->
|
||||
LifeTime = 2,
|
||||
ok = new(test_tbl, [{life_time, LifeTime}, {max_size, unlimited}]),
|
||||
check([]),
|
||||
ok = ?insert(test_tbl, "key", "value", fun() -> ok end),
|
||||
check([{"key", "value"}]),
|
||||
{ok, "value"} = ?lookup(test_tbl, "key", fun() -> error end),
|
||||
check([{"key", "value"}]),
|
||||
io:format("** waiting for ~p seconds to check if LRU works fine...~n",
|
||||
[LifeTime+1]),
|
||||
timer:sleep(timer:seconds(LifeTime+1)),
|
||||
ok = ?insert(test_tbl, "key1", "value1", fun() -> ok end),
|
||||
check([{"key1", "value1"}]),
|
||||
ok = ?delete(test_tbl, "key1", fun() -> ok end),
|
||||
{ok, "value"} = ?lookup(test_tbl, "key", fun() -> {ok, "value"} end),
|
||||
check([{"key", "value"}]),
|
||||
ok = ?delete(test_tbl, "key", fun() -> ok end),
|
||||
check([]),
|
||||
%% io:format("** testing buggy callbacks...~n"),
|
||||
%% delete(test_tbl, "key", fun() -> erlang:error(badarg) end),
|
||||
%% insert(test_tbl, "key", "val", fun() -> erlang:error(badarg) end),
|
||||
%% lookup(test_tbl, "key", fun() -> erlang:error(badarg) end),
|
||||
check([]),
|
||||
delete(test_tbl),
|
||||
test1().
|
||||
|
||||
test1() ->
|
||||
MaxSize = 10,
|
||||
ok = new(test_tbl, [{max_size, MaxSize}, {shrink_size, 1}, {warn, false}]),
|
||||
lists:foreach(
|
||||
fun(N) ->
|
||||
ok = ?insert(test_tbl, N, N, fun() -> ok end)
|
||||
end, lists:seq(1, MaxSize*get_proc_num())),
|
||||
{ok, MaxSize} = info(test_tbl, size),
|
||||
delete(test_tbl),
|
||||
test2().
|
||||
|
||||
test2() ->
|
||||
LifeTime = 2,
|
||||
ok = new(test_tbl, [{life_time, LifeTime},
|
||||
{max_size, unlimited},
|
||||
{lru, false}]),
|
||||
check([]),
|
||||
ok = ?insert(test_tbl, "key", "value", fun() -> ok end),
|
||||
{ok, "value"} = ?lookup(test_tbl, "key", fun() -> error end),
|
||||
check([{"key", "value"}]),
|
||||
io:format("** waiting for ~p seconds to check if non-LRU works fine...~n",
|
||||
[LifeTime+1]),
|
||||
timer:sleep(timer:seconds(LifeTime+1)),
|
||||
error = ?lookup(test_tbl, "key", fun() -> error end),
|
||||
check([{"key", '$cached_mismatch'}]),
|
||||
ok = ?insert(test_tbl, "key", "value1", fun() -> ok end),
|
||||
check([{"key", "value1"}]),
|
||||
delete(test_tbl),
|
||||
io:format("** testing speed, this may take a while...~n"),
|
||||
test3(1000),
|
||||
test3(10000),
|
||||
test3(100000),
|
||||
test3(1000000).
|
||||
|
||||
test3(Iter) ->
|
||||
ok = new(test_tbl, [{max_size, unlimited}, {life_time, unlimited}]),
|
||||
L = lists:seq(1, Iter),
|
||||
T1 = now(),
|
||||
lists:foreach(
|
||||
fun(N) ->
|
||||
ok = ?insert(test_tbl, N, N, fun() -> ok end)
|
||||
end, L),
|
||||
io:format("** average insert (size = ~p): ~p usec~n",
|
||||
[Iter, round(timer:now_diff(now(), T1)/Iter)]),
|
||||
T2 = now(),
|
||||
lists:foreach(
|
||||
fun(N) ->
|
||||
{ok, N} = ?lookup(test_tbl, N, fun() -> ok end)
|
||||
end, L),
|
||||
io:format("** average lookup (size = ~p): ~p usec~n",
|
||||
[Iter, round(timer:now_diff(now(), T2)/Iter)]),
|
||||
{ok, Iter} = info(test_tbl, size),
|
||||
delete(test_tbl).
|
||||
|
||||
check(List) ->
|
||||
Size = length(List),
|
||||
{ok, Size} = info(test_tbl, size),
|
||||
List = tab2list(test_tbl).
|
||||
@@ -1,11 +1,12 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : stringprep_sup.erl
|
||||
%%% Author : Mickael Remond <mremond@process-one.net>
|
||||
%%% Description : Supervisor for the Stringprep worker.
|
||||
%%% Created : 29 Jun 2007 by Mickael Remond <mremond@process-one.net>
|
||||
%%% File : cache_tab_sup.erl
|
||||
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Description : Cache tables supervisor
|
||||
%%%
|
||||
%%% Created : 30 Aug 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 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,8 +24,7 @@
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(stringprep_sup).
|
||||
-module(cache_tab_sup).
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
@@ -39,30 +39,15 @@
|
||||
%%====================================================================
|
||||
%% API functions
|
||||
%%====================================================================
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% Description: Starts the supervisor
|
||||
%%--------------------------------------------------------------------
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
|
||||
|
||||
%%====================================================================
|
||||
%% Supervisor callbacks
|
||||
%%====================================================================
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: init(Args) -> {ok, {SupFlags, [ChildSpec]}} |
|
||||
%% ignore |
|
||||
%% {error, Reason}
|
||||
%% Description: Whenever a supervisor is started using
|
||||
%% supervisor:start_link/[2,3], this function is called by the new process
|
||||
%% to find out about restart strategy, maximum restart frequency and child
|
||||
%% specifications.
|
||||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
StringPrep = {stringprep,
|
||||
{stringprep, start_link, []},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[stringprep]},
|
||||
{ok,{{one_for_all,10,1}, [StringPrep]}}.
|
||||
{ok, {{one_for_one,10,1}, []}}.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
@@ -2,7 +2,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ(2.53)
|
||||
AC_INIT(ejabberd.erl, version, [ejabberd@process-one.net], [ejabberd])
|
||||
AC_INIT(ejabberd, m4_esyscmd([grep -o -E "\{vsn,.\".*\"\}" ejabberd.app | cut -d \" -f 2 | tr -d '\n']), [ejabberd@process-one.net], [ejabberd])
|
||||
|
||||
# Checks for programs.
|
||||
AC_PROG_CC
|
||||
@@ -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)
|
||||
@@ -81,7 +76,7 @@ AC_ARG_ENABLE(transient_supervisors,
|
||||
[case "${enableval}" in
|
||||
yes) transient_supervisors=true ;;
|
||||
no) transient_supervisors=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-full-xml) ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-transient_supervisors) ;;
|
||||
esac],[transient_supervisors=true])
|
||||
AC_SUBST(transient_supervisors)
|
||||
|
||||
@@ -95,14 +90,12 @@ esac],[full_xml=false])
|
||||
AC_SUBST(full_xml)
|
||||
|
||||
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
|
||||
@@ -139,6 +132,9 @@ if test "$ENABLEUSER" != ""; then
|
||||
AC_SUBST([INSTALLUSER], [$ENABLEUSER])
|
||||
fi
|
||||
|
||||
AC_CHECK_HEADER(openssl/md2.h, md2=true, md2=false)
|
||||
AC_SUBST(md2)
|
||||
|
||||
AC_CANONICAL_SYSTEM
|
||||
#AC_DEFINE_UNQUOTED(CPU_VENDOR_OS, "$target")
|
||||
#AC_SUBST(target_os)
|
||||
@@ -167,3 +163,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,11 +1,11 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : configure.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose :
|
||||
%%% Purpose :
|
||||
%%% Created : 27 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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,33 +42,24 @@ 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,
|
||||
|
||||
EVersion = "ERLANG_VERSION = " ++ erlang:system_info(version) ++ "\n",
|
||||
EIDirS = "EI_DIR = " ++ code:lib_dir("erl_interface") ++ "\n",
|
||||
EIDirS = "EI_DIR = " ++ code:lib_dir(erl_interface) ++ "\n",
|
||||
RootDirS = "ERLANG_DIR = " ++ code:root_dir() ++ "\n",
|
||||
%% Load the ejabberd application description so that ?VERSION can read the vsn key
|
||||
application:load(ejabberd),
|
||||
Version = "EJABBERD_VERSION = " ++ ?VERSION ++ "\n",
|
||||
ExpatDir = "EXPAT_DIR = c:\\sdk\\Expat-2.0.0\n",
|
||||
OpenSSLDir = "OPENSSL_DIR = c:\\sdk\\OpenSSL\n",
|
||||
DBType = "DBTYPE = generic\n", %% 'generic' or 'mssql'
|
||||
|
||||
SSLDir = "SSLDIR = " ++ code:lib_dir("ssl") ++ "\n",
|
||||
StdLibDir = "STDLIBDIR = " ++ code:lib_dir("stdlib") ++ "\n",
|
||||
SSLDir = "SSLDIR = " ++ code:lib_dir(ssl) ++ "\n",
|
||||
StdLibDir = "STDLIBDIR = " ++ code:lib_dir(stdlib) ++ "\n",
|
||||
|
||||
file:write_file("Makefile.inc",
|
||||
list_to_binary(EVersion ++
|
||||
@@ -79,13 +70,6 @@ start() ->
|
||||
StdLibDir ++
|
||||
OpenSSLDir ++
|
||||
DBType ++
|
||||
ExpatDir ++
|
||||
ExpatLib ++
|
||||
ExpatFlag ++
|
||||
IconvDir ++
|
||||
IconvLib ++
|
||||
ZlibDir ++
|
||||
ZlibLib)),
|
||||
halt().
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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,22 +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, require_plain_password}).
|
||||
-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,
|
||||
@@ -53,49 +77,81 @@ start() ->
|
||||
cyrsasl_plain:start([]),
|
||||
cyrsasl_digest: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, RequirePlainPassword) ->
|
||||
ets:insert(sasl_mechanism,
|
||||
#sasl_mechanism{mechanism = Mechanism,
|
||||
module = Module,
|
||||
require_plain_password = RequirePlainPassword}).
|
||||
|
||||
%%% 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) ->
|
||||
RequirePlainPassword = ejabberd_auth:plain_password_required(Host),
|
||||
|
||||
@@ -112,35 +168,78 @@ 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, Username, Reason}
|
||||
%% 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}
|
||||
%% Props = [Prop]
|
||||
%% Prop = [{Key, Value}]
|
||||
%% Key = atom()
|
||||
%% Value = string()
|
||||
%% Continue = {continue, ServerOut, New_State}
|
||||
%% ServerOut = string()
|
||||
%% New_State = saslstate()
|
||||
%% Error = {error, Reason} | {error, Username, Reason}
|
||||
%% Reason = term()
|
||||
%% Username = string()
|
||||
|
||||
server_step(State, ClientIn) ->
|
||||
Module = State#sasl_state.mech_mod,
|
||||
MechState = State#sasl_state.mech_state,
|
||||
@@ -161,8 +260,15 @@ server_step(State, ClientIn) ->
|
||||
{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-2009 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, false),
|
||||
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.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 11 Mar 2003 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 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,29 +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}).
|
||||
|
||||
%% @spec (Opts) -> true
|
||||
%% Opts = term()
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, true).
|
||||
|
||||
%% @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,
|
||||
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, Username}
|
||||
%% Reason = term()
|
||||
|
||||
mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
|
||||
{continue,
|
||||
"nonce=\"" ++ Nonce ++
|
||||
@@ -60,23 +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, 'bad-protocol'};
|
||||
KeyVals ->
|
||||
DigestURI = xml:get_attr_s("digest-uri", KeyVals),
|
||||
UserName = xml:get_attr_s("username", KeyVals),
|
||||
DigestURI = proplists:get_value("digest-uri", KeyVals, ""),
|
||||
UserName = proplists:get_value("username", KeyVals, ""),
|
||||
case is_digesturi_valid(DigestURI, State#state.host) of
|
||||
false ->
|
||||
?DEBUG("User login not authorized because digest-uri "
|
||||
"seems invalid: ~p", [DigestURI]),
|
||||
{error, "not-authorized", UserName};
|
||||
{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, _} ->
|
||||
@@ -90,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
|
||||
@@ -105,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, 'bad-protocol'}.
|
||||
|
||||
%% @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) ->
|
||||
@@ -123,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) ->
|
||||
@@ -130,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) ->
|
||||
@@ -139,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) ->
|
||||
@@ -149,6 +193,10 @@ 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.
|
||||
@@ -156,8 +204,9 @@ parse4([], Key, Val, Ts) ->
|
||||
%% is provided by several hosts (being one of them server3.example.org),
|
||||
%% then digest-uri can be like xmpp/server3.example.org/jabber.example.org
|
||||
%% In that case, ejabberd only checks the service name, not the host.
|
||||
|
||||
is_digesturi_valid(DigestURICase, JabberHost) ->
|
||||
DigestURI = stringprep:tolower(DigestURICase),
|
||||
DigestURI = exmpp_stringprep:to_lower(DigestURICase),
|
||||
case catch string:tokens(DigestURI, "/") of
|
||||
["xmpp", Host] when Host == JabberHost ->
|
||||
true;
|
||||
@@ -170,14 +219,20 @@ is_digesturi_valid(DigestURICase, JabberHost) ->
|
||||
|
||||
|
||||
|
||||
%% @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) ->
|
||||
@@ -185,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
|
||||
@@ -214,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}]}.
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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,40 +27,86 @@
|
||||
-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, false),
|
||||
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, Username}
|
||||
%% Reason = term()
|
||||
|
||||
mech_step(State, ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
case prepare(ClientIn) of
|
||||
[AuthzId, User, Password] ->
|
||||
case (State#state.check_password)(User, Password) of
|
||||
{true, AuthModule} ->
|
||||
{ok, [{username, User}, {authzid, AuthzId},
|
||||
{auth_module, AuthModule}]};
|
||||
{false, ReasonAuthFail} when is_list(ReasonAuthFail) ->
|
||||
{error, ReasonAuthFail, User};
|
||||
_ ->
|
||||
{error, "not-authorized", User}
|
||||
{error, 'not-authorized', User}
|
||||
end;
|
||||
_ ->
|
||||
{error, "bad-protocol"}
|
||||
{error, 'bad-protocol'}
|
||||
end.
|
||||
|
||||
prepare(ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
[[], UserMaybeDomain, Password] ->
|
||||
case parse_domain(UserMaybeDomain) of
|
||||
%% <NUL>login@domain<NUL>pwd
|
||||
[User, _Domain] ->
|
||||
[UserMaybeDomain, User, Password];
|
||||
%% <NUL>login<NUL>pwd
|
||||
[User] ->
|
||||
["", User, Password]
|
||||
end;
|
||||
%% login@domain<NUL>login<NUL>pwd
|
||||
[AuthzId, User, Password] ->
|
||||
[AuthzId, User, Password];
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
|
||||
%% @hidden
|
||||
|
||||
parse(S) ->
|
||||
parse1(S, "", []).
|
||||
|
||||
%% @hidden
|
||||
|
||||
parse1([0 | Cs], S, T) ->
|
||||
parse1(Cs, "", [lists:reverse(S) | T]);
|
||||
parse1([C | Cs], S, T) ->
|
||||
@@ -71,5 +117,12 @@ parse1([], S, T) ->
|
||||
lists:reverse([lists:reverse(S) | T]).
|
||||
|
||||
|
||||
parse_domain(S) ->
|
||||
parse_domain1(S, "", []).
|
||||
|
||||
|
||||
parse_domain1([$@ | Cs], S, T) ->
|
||||
parse_domain1(Cs, "", [lists:reverse(S) | T]);
|
||||
parse_domain1([C | Cs], S, T) ->
|
||||
parse_domain1(Cs, [C | S], T);
|
||||
parse_domain1([], S, T) ->
|
||||
lists:reverse([lists:reverse(S) | T]).
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
%% $Id$
|
||||
|
||||
{application, ejabberd,
|
||||
[{description, "ejabberd"},
|
||||
{vsn, "2.1.0"},
|
||||
{vsn, "3.0.0-alpha-x"},
|
||||
{modules, [acl,
|
||||
adhoc,
|
||||
configure,
|
||||
@@ -15,10 +13,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,
|
||||
@@ -37,6 +34,7 @@
|
||||
ejabberd_rdbms,
|
||||
ejabberd_receiver,
|
||||
ejabberd_router,
|
||||
ejabberd_router_multicast,
|
||||
ejabberd_s2s,
|
||||
ejabberd_s2s_in,
|
||||
ejabberd_s2s_out,
|
||||
@@ -61,7 +59,6 @@
|
||||
gen_mod,
|
||||
gen_pubsub_node,
|
||||
gen_pubsub_nodetree,
|
||||
iconv,
|
||||
idna,
|
||||
jd2ejd,
|
||||
jlib,
|
||||
@@ -74,19 +71,13 @@
|
||||
mod_echo,
|
||||
mod_http_bind,
|
||||
mod_http_fileserver,
|
||||
mod_irc,
|
||||
mod_irc_connection,
|
||||
mod_last,
|
||||
mod_last_odbc,
|
||||
mod_muc,
|
||||
mod_muc_log,
|
||||
mod_muc_room,
|
||||
mod_offline,
|
||||
mod_offline_odbc,
|
||||
mod_privacy,
|
||||
mod_privacy_odbc,
|
||||
mod_private,
|
||||
mod_private_odbc,
|
||||
mod_proxy65,
|
||||
mod_proxy65_lib,
|
||||
mod_proxy65_service,
|
||||
@@ -95,14 +86,12 @@
|
||||
mod_pubsub,
|
||||
mod_register,
|
||||
mod_roster,
|
||||
mod_roster_odbc,
|
||||
mod_service_log,
|
||||
mod_shared_roster,
|
||||
mod_stats,
|
||||
mod_time,
|
||||
mod_vcard,
|
||||
mod_vcard_ldap,
|
||||
mod_vcard_odbc,
|
||||
mod_version,
|
||||
node_buddy,
|
||||
node_club,
|
||||
@@ -115,6 +104,7 @@
|
||||
nodetree_virtual,
|
||||
p1_fsm,
|
||||
p1_mnesia,
|
||||
p1_prof,
|
||||
randoms,
|
||||
sha,
|
||||
shaper,
|
||||
@@ -123,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,
|
||||
@@ -142,7 +132,6 @@
|
||||
ejabberd_mod_roster,
|
||||
ejabberd_mod_echo,
|
||||
ejabberd_mod_pubsub,
|
||||
ejabberd_mod_irc,
|
||||
ejabberd_mod_muc,
|
||||
ejabberd_offline,
|
||||
random_generator
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
%%% The parameters used in this configuration file are explained in more detail
|
||||
%%% in the ejabberd Installation and Operation Guide.
|
||||
%%% Please consult the Guide in case of doubts, it is included in
|
||||
%%% Please consult the Guide in case of doubts, it is included with
|
||||
%%% your copy of ejabberd, and is also available online at
|
||||
%%% http://www.process-one.net/en/ejabberd/docs/
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
%%% [http_poll, web_admin, tls]
|
||||
%%%
|
||||
%%% - A keyword of ejabberd is a word in lowercase.
|
||||
%%% The strings are enclosed in "" and can have spaces, dots...
|
||||
%%% Strings are enclosed in "" and can contain spaces, dots, ...
|
||||
%%% {language, "en"}.
|
||||
%%% {ldap_rootdn, "dc=example,dc=com"}.
|
||||
%%%
|
||||
%%% - This term includes a tuple, a keyword, a list and two strings:
|
||||
%%% - This term includes a tuple, a keyword, a list, and two strings:
|
||||
%%% {hosts, ["jabber.example.net", "im.example.com"]}.
|
||||
%%%
|
||||
|
||||
@@ -74,26 +74,36 @@
|
||||
|
||||
%%
|
||||
%% watchdog_admins: Only useful for developers: if an ejabberd process
|
||||
%% consumes a lot of memory, send live notifications to these XMPP
|
||||
%% consumes a lot of memory, send live notifications to these XMPP
|
||||
%% accounts.
|
||||
%%
|
||||
%%{watchdog_admins, ["bob@example.com"]}.
|
||||
|
||||
|
||||
%%%. =====================
|
||||
%%%' 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"]}.
|
||||
|
||||
%%
|
||||
%% route_subdomains: Delegate subdomains to other XMPP server.
|
||||
%% route_subdomains: Delegate subdomains to other XMPP servers.
|
||||
%% For example, if this ejabberd serves example.org and you want
|
||||
%% to allow communication with a XMPP server called im.example.org.
|
||||
%% to allow communication with an XMPP server called im.example.org.
|
||||
%%
|
||||
%%{route_subdomains, s2s}.
|
||||
|
||||
@@ -102,8 +112,8 @@
|
||||
%%%' LISTENING PORTS
|
||||
|
||||
%%
|
||||
%% listen: Which ports will ejabberd listen, which service handles it
|
||||
%% and what options to start it with.
|
||||
%% listen: The ports ejabberd will listen on, which service each is handled
|
||||
%% by and what options to start it with.
|
||||
%%
|
||||
{listen,
|
||||
[
|
||||
@@ -111,8 +121,8 @@
|
||||
{5222, ejabberd_c2s, [
|
||||
|
||||
%%
|
||||
%% If TLS is compiled and you installed a SSL
|
||||
%% certificate, put the correct path to the
|
||||
%% If TLS is compiled in and you installed a SSL
|
||||
%% certificate, specify the full path to the
|
||||
%% file and uncomment this line:
|
||||
%%
|
||||
%%{certfile, "/path/to/ssl.pem"}, starttls,
|
||||
@@ -123,7 +133,7 @@
|
||||
]},
|
||||
|
||||
%%
|
||||
%% To enable the old SSL connection method in port 5223:
|
||||
%% To enable the old SSL connection method on port 5223:
|
||||
%%
|
||||
%%{5223, ejabberd_c2s, [
|
||||
%% {access, c2s},
|
||||
@@ -138,7 +148,7 @@
|
||||
]},
|
||||
|
||||
%%
|
||||
%% ejabberd_service: Interact with external components (transports...)
|
||||
%% ejabberd_service: Interact with external components (transports, ...)
|
||||
%%
|
||||
%%{8888, ejabberd_service, [
|
||||
%% {access, all},
|
||||
@@ -162,6 +172,7 @@
|
||||
captcha,
|
||||
http_bind,
|
||||
http_poll,
|
||||
%%register,
|
||||
web_admin
|
||||
]}
|
||||
|
||||
@@ -169,10 +180,10 @@
|
||||
|
||||
%%
|
||||
%% s2s_use_starttls: Enable STARTTLS + Dialback for S2S connections.
|
||||
%% Allowed values are: true or false.
|
||||
%% Allowed values are: false optional required required_trusted
|
||||
%% You must specify a certificate file.
|
||||
%%
|
||||
%%{s2s_use_starttls, true}.
|
||||
%%{s2s_use_starttls, optional}.
|
||||
|
||||
%%
|
||||
%% s2s_certfile: Specify a certificate file.
|
||||
@@ -212,11 +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}.
|
||||
|
||||
%%
|
||||
%% Authentication using ODBC
|
||||
%% Remember to setup a database in the next section.
|
||||
%%
|
||||
%%{auth_method, storage}.
|
||||
%%{auth_storage, odbc}.
|
||||
|
||||
%%
|
||||
%% Authentication using external script
|
||||
@@ -225,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
|
||||
%%
|
||||
@@ -249,14 +262,14 @@
|
||||
%%{ldap_encrypt, none}.
|
||||
%%{ldap_encrypt, tls}.
|
||||
%%
|
||||
%% Port connect to LDAP servers:
|
||||
%% Port to connect to on LDAP servers:
|
||||
%%{ldap_port, 389}.
|
||||
%%{ldap_port, 636}.
|
||||
%%
|
||||
%% LDAP manager:
|
||||
%%{ldap_rootdn, "dc=example,dc=com"}.
|
||||
%%
|
||||
%% Password to LDAP manager:
|
||||
%% Password of LDAP manager:
|
||||
%%{ldap_password, "******"}.
|
||||
%%
|
||||
%% Search base of LDAP directory:
|
||||
@@ -286,28 +299,38 @@
|
||||
%%%. ==============
|
||||
%%%' DATABASE SETUP
|
||||
|
||||
%% ejabberd uses by default the internal Mnesia database,
|
||||
%% so you can avoid this section.
|
||||
%% ejabberd by default uses the internal Mnesia database,
|
||||
%% so you do not necessarily need this section.
|
||||
%% This section provides configuration examples in case
|
||||
%% you want to use other database backends.
|
||||
%% Please consult the ejabberd Guide for details about database creation.
|
||||
%% Please consult the ejabberd Guide for details on database creation.
|
||||
|
||||
%% 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"
|
||||
%%
|
||||
@@ -324,8 +347,8 @@
|
||||
%%{odbc_pool_size, 10}.
|
||||
|
||||
%%
|
||||
%% Interval to make a dummy SQL request to keep alive the connections
|
||||
%% to the database. Specify in seconds: for example 28800 means 8 hours
|
||||
%% Interval to make a dummy SQL request to keep the connections to the
|
||||
%% database alive. Specify in seconds: for example 28800 means 8 hours
|
||||
%%
|
||||
%%{odbc_keepalive_interval, undefined}.
|
||||
|
||||
@@ -334,22 +357,28 @@
|
||||
%%%' TRAFFIC SHAPERS
|
||||
|
||||
%%
|
||||
%% The "normal" shaper limits traffic speed to 1.000 B/s
|
||||
%% The "normal" shaper limits traffic speed to 1000 B/s
|
||||
%%
|
||||
{shaper, normal, {maxrate, 1000}}.
|
||||
|
||||
%%
|
||||
%% The "fast" shaper limits traffic speed to 50.000 B/s
|
||||
%% The "fast" shaper limits traffic speed to 50000 B/s
|
||||
%%
|
||||
{shaper, fast, {maxrate, 50000}}.
|
||||
|
||||
%%
|
||||
%% This option specifies the maximum number of elements in the queue
|
||||
%% of the FSM. Refer to the documentation for details.
|
||||
%%
|
||||
{max_fsm_queue, 1000}.
|
||||
|
||||
|
||||
%%%. ====================
|
||||
%%%' ACCESS CONTROL LISTS
|
||||
|
||||
%%
|
||||
%% The 'admin' ACL grants administrative privileges to XMPP accounts.
|
||||
%% You can put as many accounts as you want.
|
||||
%% You can put here as many accounts as you want.
|
||||
%%
|
||||
%%{acl, admin, {user, "aleksey", "localhost"}}.
|
||||
%%{acl, admin, {user, "ermine", "example.org"}}.
|
||||
@@ -390,7 +419,7 @@
|
||||
{access, max_user_sessions, [{10, all}]}.
|
||||
|
||||
%% Maximum number of offline messages that users can have:
|
||||
{access, max_user_offline_messages, [{5000, admin}, {100, all}]}.
|
||||
{access, max_user_offline_messages, [{5000, admin}, {100, all}]}.
|
||||
|
||||
%% This rule allows access only for local users:
|
||||
{access, local, [{allow, local}]}.
|
||||
@@ -399,41 +428,41 @@
|
||||
{access, c2s, [{deny, blocked},
|
||||
{allow, all}]}.
|
||||
|
||||
%% For C2S connections, all users except admins use "normal" shaper
|
||||
%% For C2S connections, all users except admins use the "normal" shaper
|
||||
{access, c2s_shaper, [{none, admin},
|
||||
{normal, all}]}.
|
||||
|
||||
%% All S2S connections use "fast" shaper
|
||||
%% All S2S connections use the "fast" shaper
|
||||
{access, s2s_shaper, [{fast, all}]}.
|
||||
|
||||
%% Only admins can send announcement messages:
|
||||
{access, announce, [{allow, admin}]}.
|
||||
|
||||
%% Only admins can use configuration interface:
|
||||
%% Only admins can use the configuration interface:
|
||||
{access, configure, [{allow, admin}]}.
|
||||
|
||||
%% Admins of this server are also admins of MUC service:
|
||||
%% Admins of this server are also admins of the MUC service:
|
||||
{access, muc_admin, [{allow, admin}]}.
|
||||
|
||||
%% Only accounts of the local ejabberd server can create rooms:
|
||||
{access, muc_create, [{allow, local}]}.
|
||||
|
||||
%% All users are allowed to use MUC service:
|
||||
%% All users are allowed to use the MUC service:
|
||||
{access, muc, [{allow, all}]}.
|
||||
|
||||
%% Only accounts in the local ejabberd server can create Pubsub nodes:
|
||||
%% Only accounts on the local ejabberd server can create Pubsub nodes:
|
||||
{access, pubsub_createnode, [{allow, local}]}.
|
||||
|
||||
%% In-band registration allows registration of any possible username.
|
||||
%% To disable in-band registration, replace 'allow' with 'deny'.
|
||||
{access, register, [{allow, all}]}.
|
||||
|
||||
%% By default frequency of account registrations from the same IP
|
||||
%% is limited to 1 account every 10 minutes. To disable put: infinity
|
||||
%% By default the frequency of account registrations from the same IP
|
||||
%% is limited to 1 account every 10 minutes. To disable, specify: infinity
|
||||
%%{registration_timeout, 600}.
|
||||
|
||||
%%
|
||||
%% Define specific Access rules in a virtual host.
|
||||
%% Define specific Access Rules in a virtual host.
|
||||
%%
|
||||
%%{host_config, "localhost",
|
||||
%% [
|
||||
@@ -443,6 +472,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
|
||||
|
||||
@@ -459,61 +501,61 @@
|
||||
%%}.
|
||||
|
||||
|
||||
%%%. =======
|
||||
%%%' 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 part of the URL sent to the user.
|
||||
%%
|
||||
%%{captcha_host, "example.org:5280"}.
|
||||
|
||||
|
||||
%%%. =======
|
||||
%%%' 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_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"},
|
||||
%% {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},
|
||||
{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.
|
||||
%%
|
||||
%%{captcha_protected, true},
|
||||
|
||||
%%
|
||||
%% Set the minimum informational entropy for passwords.
|
||||
%%
|
||||
%%{password_strength, 32},
|
||||
|
||||
%%
|
||||
%% After successful registration, the user receives
|
||||
%% a message with this subject and body.
|
||||
@@ -527,17 +569,53 @@
|
||||
%%
|
||||
%%{registration_watchers, ["admin1@example.org"]},
|
||||
|
||||
%%
|
||||
%% Only clients in the server machine can register accounts
|
||||
%%
|
||||
{ip_access, [{allow, "127.0.0.0/8"},
|
||||
{deny, "0.0.0.0/0"}]},
|
||||
|
||||
%%
|
||||
%% Local c2s or remote s2s users cannot register accounts
|
||||
%%
|
||||
%%{access_from, deny},
|
||||
|
||||
{access, register}
|
||||
]},
|
||||
%%{mod_register_web, [
|
||||
%%
|
||||
%% When a user registers, send a notification to
|
||||
%% these XMPP accounts.
|
||||
%%
|
||||
%%{registration_watchers, ["admin1@example.org"]}
|
||||
%% ]},
|
||||
{mod_roster, []},
|
||||
%%{mod_service_log,[]},
|
||||
{mod_shared_roster,[]},
|
||||
{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,
|
||||
[
|
||||
]}.
|
||||
|
||||
|
||||
%%%.
|
||||
%%%' HOST SPECIFIC MODULES
|
||||
|
||||
%%
|
||||
%% Enable modules with custom options in a specific virtual host
|
||||
%%
|
||||
@@ -553,8 +631,6 @@
|
||||
%%%.
|
||||
%%%'
|
||||
|
||||
%%% $Id$
|
||||
|
||||
%%% Local Variables:
|
||||
%%% mode: erlang
|
||||
%%% End:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 16 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 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)).
|
||||
|
||||
@@ -35,6 +36,8 @@
|
||||
|
||||
-define(S2STIMEOUT, 600000).
|
||||
|
||||
-define(PRIVACY_SUPPORT, true).
|
||||
|
||||
%%-define(DBGFSM, true).
|
||||
|
||||
%% ---------------------------------
|
||||
@@ -58,4 +61,3 @@
|
||||
|
||||
-define(CRITICAL_MSG(Format, Args),
|
||||
ejabberd_logger:critical_msg(?MODULE,?LINE,Format, Args)).
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 7 May 2006 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@@ -30,14 +30,21 @@
|
||||
-export([start/0, stop/0,
|
||||
%% Server
|
||||
status/0, reopen_log/0,
|
||||
stop_kindly/2, send_service_message_all_mucs/2,
|
||||
%% Erlang
|
||||
update_list/0, update/1,
|
||||
%% Accounts
|
||||
register/3, unregister/2,
|
||||
registered_users/1,
|
||||
%% 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
|
||||
set_master/1,
|
||||
backup_mnesia/1, restore_mnesia/1,
|
||||
dump_mnesia/1, dump_table/2, load_mnesia/1,
|
||||
install_fallback_mnesia/1,
|
||||
@@ -79,6 +86,11 @@ commands() ->
|
||||
desc = "Reopen the log files",
|
||||
module = ?MODULE, function = reopen_log,
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = stop_kindly, tags = [server],
|
||||
desc = "Inform users and rooms, wait, and stop the server",
|
||||
module = ?MODULE, function = stop_kindly,
|
||||
args = [{delay, integer}, {announcement, string}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = get_loglevel, tags = [logs, server],
|
||||
desc = "Get the current loglevel",
|
||||
module = ejabberd_loglevel, function = get,
|
||||
@@ -88,6 +100,17 @@ commands() ->
|
||||
{leveldesc, string}
|
||||
]}}},
|
||||
|
||||
#ejabberd_commands{name = update_list, tags = [server],
|
||||
desc = "List modified modules that can be updated",
|
||||
module = ?MODULE, function = update_list,
|
||||
args = [],
|
||||
result = {modules, {list, {module, string}}}},
|
||||
#ejabberd_commands{name = update, tags = [server],
|
||||
desc = "Update the given module, or use the keyword: all",
|
||||
module = ?MODULE, function = update,
|
||||
args = [{module, string}],
|
||||
result = {res, restuple}},
|
||||
|
||||
#ejabberd_commands{name = register, tags = [accounts],
|
||||
desc = "Register a user",
|
||||
module = ?MODULE, function = register,
|
||||
@@ -104,6 +127,21 @@ commands() ->
|
||||
args = [{host, string}],
|
||||
result = {users, {list, {username, string}}}},
|
||||
|
||||
#ejabberd_commands{name = gli, tags = [accounts],
|
||||
desc = "Get information about last access of an account",
|
||||
module = ?MODULE, function = get_last_info,
|
||||
args = [],
|
||||
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",
|
||||
module = ?MODULE, function = import_file,
|
||||
@@ -135,12 +173,13 @@ 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 ejabberd trunk SVN to 2.1.0",
|
||||
module = mod_pubsub, function = rename_default_nodeplugin,
|
||||
args = [], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = set_master, tags = [mnesia],
|
||||
desc = "Set master node of the clustered Mnesia tables",
|
||||
longdesc = "If you provide as nodename \"self\", this "
|
||||
"node will be set as its own master.",
|
||||
module = ?MODULE, function = set_master,
|
||||
args = [{nodename, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = mnesia_change_nodename, tags = [mnesia],
|
||||
desc = "Change the erlang node name in a backup file",
|
||||
module = ?MODULE, function = mnesia_change_nodename,
|
||||
@@ -215,6 +254,75 @@ get_sasl_error_logger_type () ->
|
||||
_ -> all
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% Stop Kindly
|
||||
%%%
|
||||
|
||||
stop_kindly(DelaySeconds, AnnouncementText) ->
|
||||
Subject = io_lib:format("Server stop in ~p seconds!", [DelaySeconds]),
|
||||
WaitingDesc = io_lib:format("Waiting ~p seconds", [DelaySeconds]),
|
||||
Steps = [{"Stopping ejabberd port listeners",
|
||||
ejabberd_listener, stop_listeners, []},
|
||||
{"Sending announcement to connected users",
|
||||
mod_announce, send_announcement_to_all,
|
||||
[?MYNAME, Subject, AnnouncementText]},
|
||||
{"Sending service message to MUC rooms",
|
||||
ejabberd_admin, send_service_message_all_mucs,
|
||||
[Subject, AnnouncementText]},
|
||||
{WaitingDesc, timer, sleep, [DelaySeconds * 1000]},
|
||||
{"Stopping ejabberd", application, stop, [ejabberd]},
|
||||
{"Stopping Mnesia", mnesia, stop, []},
|
||||
{"Stopping Erlang node", init, stop, []}
|
||||
],
|
||||
NumberLast = length(Steps),
|
||||
TimestampStart = calendar:datetime_to_gregorian_seconds({date(), time()}),
|
||||
lists:foldl(
|
||||
fun({Desc, Mod, Func, Args}, NumberThis) ->
|
||||
SecondsDiff =
|
||||
calendar:datetime_to_gregorian_seconds({date(), time()})
|
||||
- TimestampStart,
|
||||
io:format("[~p/~p ~ps] ~s... ",
|
||||
[NumberThis, NumberLast, SecondsDiff, Desc]),
|
||||
Result = apply(Mod, Func, Args),
|
||||
io:format("~p~n", [Result]),
|
||||
NumberThis+1
|
||||
end,
|
||||
1,
|
||||
Steps),
|
||||
ok.
|
||||
|
||||
send_service_message_all_mucs(Subject, AnnouncementText) ->
|
||||
Message = io_lib:format("~s~n~s", [Subject, AnnouncementText]),
|
||||
lists:foreach(
|
||||
fun(ServerHost) ->
|
||||
MUCHost = gen_mod:get_module_opt_host(
|
||||
ServerHost, mod_muc, "conference.@HOST@"),
|
||||
MUCHostB = list_to_binary(MUCHost),
|
||||
mod_muc:broadcast_service_message(MUCHostB, Message)
|
||||
end,
|
||||
?MYHOSTS).
|
||||
|
||||
%%%
|
||||
%%% ejabberd_update
|
||||
%%%
|
||||
|
||||
update_list() ->
|
||||
{ok, _Dir, UpdatedBeams, _Script, _LowLevelScript, _Check} =
|
||||
ejabberd_update:update_info(),
|
||||
[atom_to_list(Beam) || Beam <- UpdatedBeams].
|
||||
|
||||
update("all") ->
|
||||
[update_module(ModStr) || ModStr <- update_list()];
|
||||
update(ModStr) ->
|
||||
update_module(ModStr).
|
||||
|
||||
update_module(ModuleNameString) ->
|
||||
ModuleName = list_to_atom(ModuleNameString),
|
||||
case ejabberd_update:update([ModuleName]) of
|
||||
{ok, Res} -> {ok, io_lib:format("Updated: ~p", [Res])};
|
||||
{error, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% Account management
|
||||
%%%
|
||||
@@ -222,7 +330,7 @@ get_sasl_error_logger_type () ->
|
||||
register(User, Host, Password) ->
|
||||
case ejabberd_auth:try_register(User, Host, Password) of
|
||||
{atomic, ok} ->
|
||||
{ok, io_lib:format("User ~s@~s succesfully registered", [User, Host])};
|
||||
{ok, io_lib:format("User ~s@~s successfully registered", [User, Host])};
|
||||
{atomic, exists} ->
|
||||
String = io_lib:format("User ~s@~s already registered at node ~p",
|
||||
[User, Host, node()]),
|
||||
@@ -242,6 +350,12 @@ registered_users(Host) ->
|
||||
SUsers = lists:sort(Users),
|
||||
lists:map(fun({U, _S}) -> U end, SUsers).
|
||||
|
||||
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
|
||||
@@ -273,11 +387,11 @@ import_dir(Path) ->
|
||||
%%%
|
||||
|
||||
delete_expired_messages() ->
|
||||
{atomic, ok} = mod_offline:remove_expired_messages(),
|
||||
mod_offline:remove_expired_messages(),
|
||||
ok.
|
||||
|
||||
delete_old_messages(Days) ->
|
||||
{atomic, _} = mod_offline:remove_old_messages(Days),
|
||||
mod_offline:remove_old_messages(Days),
|
||||
ok.
|
||||
|
||||
|
||||
@@ -285,6 +399,20 @@ delete_old_messages(Days) ->
|
||||
%%% Mnesia management
|
||||
%%%
|
||||
|
||||
set_master("self") ->
|
||||
set_master(node());
|
||||
set_master(NodeString) when is_list(NodeString) ->
|
||||
set_master(list_to_atom(NodeString));
|
||||
set_master(Node) when is_atom(Node) ->
|
||||
case mnesia:set_master_nodes([Node]) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't set master node ~p at node ~p:~n~p",
|
||||
[Node, node(), Reason]),
|
||||
{error, String}
|
||||
end.
|
||||
|
||||
backup_mnesia(Path) ->
|
||||
case mnesia:backup(Path) of
|
||||
ok ->
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 31 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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,10 +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(),
|
||||
start(),
|
||||
translate:start(),
|
||||
acl:start(),
|
||||
@@ -56,6 +56,8 @@ start(normal, _Args) ->
|
||||
ejabberd_config:start(),
|
||||
ejabberd_check:config(),
|
||||
connect_nodes(),
|
||||
%% Loading ASN.1 driver explicitly to avoid races in LDAP
|
||||
catch asn1rt:load_driver(),
|
||||
Sup = ejabberd_sup:start_link(),
|
||||
ejabberd_rdbms:start(),
|
||||
ejabberd_auth:start(),
|
||||
@@ -64,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;
|
||||
@@ -77,13 +82,15 @@ start(_, _) ->
|
||||
prep_stop(State) ->
|
||||
stop_modules(),
|
||||
ejabberd_admin:stop(),
|
||||
broadcast_c2s_shutdown(),
|
||||
timer:sleep(5000),
|
||||
State.
|
||||
|
||||
%% All the processes were killed when this function is called
|
||||
stop(_State) ->
|
||||
?INFO_MSG("ejabberd ~s is stopped in the node ~p", [?VERSION, node()]),
|
||||
delete_pid_file(),
|
||||
ejabberd_debug:stop(),
|
||||
%%ejabberd_debug:stop(),
|
||||
ok.
|
||||
|
||||
|
||||
@@ -101,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
|
||||
@@ -127,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
|
||||
@@ -198,6 +202,13 @@ add_windows_nameservers() ->
|
||||
lists:foreach(fun(IPT) -> inet_db:add_ns(IPT) end, IPTs).
|
||||
|
||||
|
||||
broadcast_c2s_shutdown() ->
|
||||
Children = supervisor:which_children(ejabberd_c2s_sup),
|
||||
lists:foreach(
|
||||
fun({_, C2SPid, _, _}) ->
|
||||
C2SPid ! system_shutdown
|
||||
end, Children).
|
||||
|
||||
%%%
|
||||
%%% PID file
|
||||
%%%
|
||||
@@ -227,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.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 23 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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,86 +49,149 @@
|
||||
is_user_exists_in_other_modules/3,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
plain_password_required/1
|
||||
plain_password_required/1,
|
||||
entropy/1
|
||||
]).
|
||||
|
||||
-export([start/1
|
||||
,start_module/2
|
||||
,stop_module/2
|
||||
,start_modules/2
|
||||
,start_method/2
|
||||
,stop_method/2
|
||||
,start_methods/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
|
||||
%%%----------------------------------------------------------------------
|
||||
start() ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
lists:foreach(
|
||||
fun(M) ->
|
||||
M:start(Host)
|
||||
end, auth_modules(Host))
|
||||
end, ?MYHOSTS).
|
||||
|
||||
plain_password_required(Server) ->
|
||||
%% @spec () -> term()
|
||||
|
||||
start() ->
|
||||
?DEBUG("About to start auth modules. Hosts: ~p", [?MYHOSTS]),
|
||||
lists:foreach(fun start/1, ?MYHOSTS).
|
||||
|
||||
start(Host) ->
|
||||
start_modules(Host, auth_modules(Host)).
|
||||
|
||||
start_modules(Host, Modules) when is_list(Modules) ->
|
||||
lists:foreach(fun (M) -> start_module(Host, M) end, Modules).
|
||||
start_module(Host, Module) when is_atom(Module) ->
|
||||
Module:start(Host).
|
||||
stop_module(Host, Module) when is_atom(Module) ->
|
||||
Module:stop(Host).
|
||||
|
||||
start_methods(Host, Methods) when is_list(Methods) ->
|
||||
lists:foreach(fun (M) -> start_method(Host, M) end, Methods).
|
||||
start_method(Host, Method) when is_atom(Method) ->
|
||||
start_module(Host, module_name(Method)).
|
||||
stop_method(Host, Method) when is_atom(Method) ->
|
||||
stop_module(Host, module_name(Method)).
|
||||
|
||||
|
||||
%% @spec (Server) -> bool()
|
||||
%% Server = string()
|
||||
|
||||
plain_password_required(Server) when is_list(Server) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
M:plain_password_required()
|
||||
end, 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.
|
||||
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% {true, AuthModule} | {false, Reason::string()}
|
||||
%% where
|
||||
%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external
|
||||
%% | ejabberd_auth_ldap | ejabberd_auth_pam | ejabberd_auth_storage
|
||||
%% @doc Check if the user and password can login in server.
|
||||
%% The user can login if at least an authentication method accepts the user
|
||||
%% and the password.
|
||||
%% The first authentication method that accepts the credentials is returned.
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% {true, AuthModule} | false
|
||||
%% where
|
||||
%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external
|
||||
%% | ejabberd_auth_internal | ejabberd_auth_ldap
|
||||
%% | ejabberd_auth_odbc | ejabberd_auth_pam
|
||||
check_password_with_authmodule(User, Server, Password) ->
|
||||
check_password_loop(auth_modules(Server), [User, Server, Password]).
|
||||
check_password_with_authmodule(User, Server, Password)
|
||||
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);
|
||||
@@ -137,49 +200,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(
|
||||
@@ -191,7 +276,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) ->
|
||||
@@ -204,7 +293,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) ->
|
||||
@@ -217,9 +313,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);
|
||||
@@ -227,7 +327,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 ->
|
||||
"";
|
||||
@@ -235,10 +341,15 @@ get_password_s(User, Server) ->
|
||||
Password
|
||||
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};
|
||||
@@ -246,18 +357,36 @@ 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) ->
|
||||
M:is_user_exists(User, Server)
|
||||
case M:is_user_exists(User, Server) of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("The authentication module ~p returned an "
|
||||
"error~nwhen checking user ~p in server ~p~n"
|
||||
"Error message: ~p",
|
||||
[M, User, Server, Error]),
|
||||
false;
|
||||
Else ->
|
||||
Else
|
||||
end
|
||||
end, auth_modules(Server)).
|
||||
|
||||
%% Check if the user exists in all authentications module except the module
|
||||
%% passed as parameter
|
||||
%% @spec (Module::atom(), User, Server) -> true | false | maybe
|
||||
is_user_exists_in_other_modules(Module, User, Server) ->
|
||||
%% @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).
|
||||
@@ -278,25 +407,33 @@ is_user_exists_in_other_modules_loop([AuthModule|AuthModules], User, Server) ->
|
||||
|
||||
|
||||
%% @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) ->
|
||||
%% 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)),
|
||||
case R of
|
||||
ok -> ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]);
|
||||
_ -> none
|
||||
end,
|
||||
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;
|
||||
@@ -304,17 +441,44 @@ 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.
|
||||
|
||||
%% @spec (IOList) -> non_negative_float()
|
||||
%% @doc Calculate informational entropy.
|
||||
entropy(IOList) ->
|
||||
case binary_to_list(iolist_to_binary(IOList)) of
|
||||
"" ->
|
||||
0.0;
|
||||
S ->
|
||||
Set = lists:foldl(
|
||||
fun(C, [Digit, Printable, LowLetter, HiLetter, Other]) ->
|
||||
if C >= $a, C =< $z ->
|
||||
[Digit, Printable, 26, HiLetter, Other];
|
||||
C >= $0, C =< $9 ->
|
||||
[9, Printable, LowLetter, HiLetter, Other];
|
||||
C >= $A, C =< $Z ->
|
||||
[Digit, Printable, LowLetter, 26, Other];
|
||||
C >= 16#21, C =< 16#7e ->
|
||||
[Digit, 33, LowLetter, HiLetter, Other];
|
||||
true ->
|
||||
[Digit, Printable, LowLetter, HiLetter, 128]
|
||||
end
|
||||
end, [0, 0, 0, 0, 0], S),
|
||||
length(S) * math:log(lists:sum(Set))/math:log(2)
|
||||
end.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
%% Return the lists of all the auth modules actually used in the
|
||||
%% configuration
|
||||
|
||||
%% @spec () -> [authmodule()]
|
||||
%% @doc Return the lists of all the auth modules actually used in the
|
||||
%% configuration.
|
||||
|
||||
auth_modules() ->
|
||||
lists:usort(
|
||||
lists:flatmap(
|
||||
@@ -322,13 +486,19 @@ auth_modules() ->
|
||||
auth_modules(Server)
|
||||
end, ?MYHOSTS)).
|
||||
|
||||
%% Return the list of authenticated modules for a given host
|
||||
auth_modules(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
Method = ejabberd_config:get_local_option({auth_method, LServer}),
|
||||
%% @spec (Server) -> [authmodule()]
|
||||
%% Server = string()
|
||||
%% @doc Return the list of authenticated modules for a given host.
|
||||
|
||||
auth_modules(Server) when is_list(Server) ->
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
Method = ejabberd_config:get_local_option({auth_method, ejabberd:normalize_host(LServer)}),
|
||||
Methods = if
|
||||
Method == undefined -> [];
|
||||
is_list(Method) -> Method;
|
||||
is_atom(Method) -> [Method]
|
||||
end,
|
||||
[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)).
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 17 Feb 2006 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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,
|
||||
@@ -53,33 +54,56 @@
|
||||
remove_user/3,
|
||||
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;
|
||||
false -> false;
|
||||
true ->
|
||||
case anonymous_protocol(Host) of
|
||||
sasl_anon -> true;
|
||||
@@ -88,23 +112,29 @@ 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 ->
|
||||
case anonymous_protocol(Host) of
|
||||
login_anon -> true;
|
||||
both -> true;
|
||||
both -> true;
|
||||
_Other -> false
|
||||
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;
|
||||
@@ -112,18 +142,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) ->
|
||||
case ejabberd_config:get_local_option({allow_multiple_connections, Host}) of
|
||||
true -> true;
|
||||
_Other -> false
|
||||
end.
|
||||
|
||||
%% 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
|
||||
[] ->
|
||||
@@ -132,51 +170,81 @@ 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) ->
|
||||
%% We refuse login for registered accounts (They cannot logged but
|
||||
%% they however are "reserved")
|
||||
case ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
|
||||
case ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
|
||||
User, Server) of
|
||||
%% If user exists in other module, reject anonnymous authentication
|
||||
true -> false;
|
||||
@@ -185,6 +253,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 +271,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 +286,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() ->
|
||||
[].
|
||||
|
||||
get_vh_registered_users(_Server) ->
|
||||
[].
|
||||
%% @spec (Server) -> nil()
|
||||
%% Server = string()
|
||||
|
||||
get_vh_registered_users(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,17 +331,39 @@ 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.
|
||||
|
||||
update_tables() ->
|
||||
case catch mnesia:table_info(anonymous, local_content) of
|
||||
false ->
|
||||
mnesia:delete_table(anonymous);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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,12 +29,16 @@
|
||||
|
||||
%% 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,
|
||||
@@ -43,47 +47,145 @@
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%% @spec (Host) -> ok
|
||||
%% Host = string()
|
||||
|
||||
start(Host) ->
|
||||
extauth:start(
|
||||
Host, ejabberd_config:get_local_option({extauth_program, Host})),
|
||||
ok.
|
||||
case check_cache_last_options(Host) of
|
||||
cache ->
|
||||
ok = ejabberd_auth_storage:start(Host);
|
||||
no_cache ->
|
||||
ok
|
||||
end.
|
||||
|
||||
stop(Host) ->
|
||||
extauth:stop(Host),
|
||||
case check_cache_last_options(Host) of
|
||||
cache ->
|
||||
ok = ejabberd_auth_storage:stop(Host);
|
||||
no_cache ->
|
||||
ok
|
||||
end.
|
||||
|
||||
check_cache_last_options(Server) ->
|
||||
%% if extauth_cache is enabled, then a mod_last module must also be enabled
|
||||
case get_cache_option(Server) of
|
||||
false -> no_cache;
|
||||
{true, _CacheTime} ->
|
||||
case get_mod_last_configured(Server) of
|
||||
no_mod_last ->
|
||||
?ERROR_MSG("In host ~p extauth is used, extauth_cache is enabled but "
|
||||
"mod_last is not enabled.", [Server]),
|
||||
no_cache;
|
||||
_ -> cache
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec () -> bool()
|
||||
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
%% @spec (User, Server, Password) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
extauth:check_password(User, Server, Password) andalso Password /= "".
|
||||
case get_cache_option(Server) of
|
||||
false -> check_password_extauth(User, Server, Password);
|
||||
{true, CacheTime} -> check_password_cache(User, Server, Password, CacheTime)
|
||||
end.
|
||||
|
||||
%% @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 -> ok;
|
||||
true -> set_password_storage(User, Server, Password),
|
||||
ok;
|
||||
_ -> {error, unknown_problem}
|
||||
end.
|
||||
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
%% @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.
|
||||
|
||||
%% TODO
|
||||
%% Return the list of all users handled by external
|
||||
dirty_get_registered_users() ->
|
||||
[].
|
||||
ejabberd_auth_storage:dirty_get_registered_users().
|
||||
|
||||
get_vh_registered_users(_Server) ->
|
||||
[].
|
||||
get_vh_registered_users(Server) ->
|
||||
ejabberd_auth_storage:get_vh_registered_users(Server).
|
||||
|
||||
get_password(_User, _Server) ->
|
||||
false.
|
||||
get_vh_registered_users(Server, Data) ->
|
||||
ejabberd_auth_storage:get_vh_registered_users(Server, Data).
|
||||
|
||||
get_password_s(_User, _Server) ->
|
||||
"".
|
||||
%% @spec (Server) -> nil()
|
||||
%% Server = string()
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
ejabberd_auth_storage:get_vh_registered_users_number(Server).
|
||||
|
||||
get_vh_registered_users_number(Server, Data) ->
|
||||
ejabberd_auth_storage:get_vh_registered_users_number(Server, Data).
|
||||
|
||||
%% @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 -> [];
|
||||
Other -> Other
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
try extauth:is_user_exists(User, Server) of
|
||||
Res -> Res
|
||||
@@ -91,9 +193,188 @@ is_user_exists(User, Server) ->
|
||||
_:Error -> {error, Error}
|
||||
end.
|
||||
|
||||
remove_user(_User, _Server) ->
|
||||
{error, not_allowed}.
|
||||
%% @spec (User, Server) -> {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
|
||||
remove_user(_User, _Server, _Password) ->
|
||||
not_allowed.
|
||||
remove_user(User, Server) ->
|
||||
case extauth:remove_user(User, Server) of
|
||||
false -> false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_storage:remove_user(User, Server)
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> not_allowed
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
case extauth:remove_user(User, Server, Password) of
|
||||
false -> false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_storage:remove_user(User, Server, Password)
|
||||
end
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% Extauth cache management
|
||||
%%%
|
||||
|
||||
%% @spec (Host::string()) -> false | {true, CacheTime::integer()}
|
||||
get_cache_option(Host) ->
|
||||
case ejabberd_config:get_local_option({extauth_cache, Host}) of
|
||||
CacheTime when is_integer(CacheTime) -> {true, CacheTime};
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false
|
||||
check_password_extauth(User, Server, Password) ->
|
||||
extauth:check_password(User, Server, Password) andalso Password /= "".
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false
|
||||
try_register_extauth(User, Server, Password) ->
|
||||
extauth:try_register(User, Server, Password).
|
||||
|
||||
check_password_cache(User, Server, Password, CacheTime) ->
|
||||
case get_last_access(User, Server) of
|
||||
online ->
|
||||
check_password_storage(User, Server, Password);
|
||||
never ->
|
||||
check_password_external_cache(User, Server, Password);
|
||||
mod_last_required ->
|
||||
?ERROR_MSG("extauth is used, extauth_cache is enabled but mod_last is not enabled in that host", []),
|
||||
check_password_external_cache(User, Server, Password);
|
||||
TimeStamp ->
|
||||
%% If last access exists, compare last access with cache refresh time
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
%% If no need to refresh, check password against Mnesia
|
||||
true ->
|
||||
case check_password_storage(User, Server, Password) of
|
||||
%% If password valid in Mnesia, accept it
|
||||
true ->
|
||||
true;
|
||||
%% Else (password nonvalid in Mnesia), check in extauth and cache result
|
||||
false ->
|
||||
check_password_external_cache(User, Server, Password)
|
||||
end;
|
||||
%% Else (need to refresh), check in extauth and cache result
|
||||
false ->
|
||||
check_password_external_cache(User, Server, Password)
|
||||
end
|
||||
end.
|
||||
|
||||
get_password_storage(User, Server) ->
|
||||
ejabberd_auth_storage:get_password(User, Server).
|
||||
|
||||
%% @spec (User, Server, CacheTime) -> Password::string() | false
|
||||
get_password_cache(User, Server, CacheTime) ->
|
||||
case get_last_access(User, Server) of
|
||||
online ->
|
||||
get_password_storage(User, Server);
|
||||
never ->
|
||||
false;
|
||||
mod_last_required ->
|
||||
?ERROR_MSG("extauth is used, extauth_cache is enabled but mod_last is not enabled in that host", []),
|
||||
false;
|
||||
TimeStamp ->
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
true ->
|
||||
get_password_storage(User, Server);
|
||||
false ->
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
%% Check the password using extauth; if success then cache it
|
||||
check_password_external_cache(User, Server, Password) ->
|
||||
case check_password_extauth(User, Server, Password) of
|
||||
true ->
|
||||
set_password_storage(User, Server, Password), true;
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% Try to register using extauth; if success then cache it
|
||||
try_register_external_cache(User, Server, Password) ->
|
||||
case try_register_extauth(User, Server, Password) of
|
||||
{atomic, ok} = R ->
|
||||
set_password_storage(User, Server, Password),
|
||||
R;
|
||||
_ -> {error, not_allowed}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false
|
||||
check_password_storage(User, Server, Password) ->
|
||||
ejabberd_auth_storage:check_password(User, Server, Password).
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
|
||||
set_password_storage(User, Server, Password) ->
|
||||
ejabberd_auth_storage:set_password(User, Server, Password).
|
||||
|
||||
%% @spec (TimeLast, CacheTime) -> true | false
|
||||
%% TimeLast = online | never | integer()
|
||||
%% CacheTime = integer() | false
|
||||
is_fresh_enough(online, _CacheTime) ->
|
||||
true;
|
||||
is_fresh_enough(never, _CacheTime) ->
|
||||
false;
|
||||
is_fresh_enough(TimeStampLast, CacheTime) ->
|
||||
{MegaSecs, Secs, _MicroSecs} = now(),
|
||||
Now = MegaSecs * 1000000 + Secs,
|
||||
(TimeStampLast + CacheTime > Now).
|
||||
|
||||
%% @spec (User, Server) -> TimeStamp::integer() | online | never | mod_last_required
|
||||
get_last_access(UserS, ServerS) ->
|
||||
%% Code copied from mod_configure.erl
|
||||
%% Code copied from web/ejabberd_web_admin.erl
|
||||
%% TODO: Update time format to XEP-0202: Entity Time
|
||||
User = list_to_binary(UserS),
|
||||
Server = list_to_binary(ServerS),
|
||||
case ejabberd_sm:get_user_resources(User, Server) of
|
||||
[] ->
|
||||
_US = {User, Server},
|
||||
case get_last_info(User, Server) of
|
||||
mod_last_required ->
|
||||
mod_last_required;
|
||||
not_found ->
|
||||
never;
|
||||
{ok, Timestamp, _Status} ->
|
||||
Timestamp
|
||||
end;
|
||||
_ ->
|
||||
online
|
||||
end.
|
||||
%% @spec (User, Server) -> {ok, Timestamp, Status} | not_found | mod_last_required
|
||||
get_last_info(User, Server) ->
|
||||
case get_mod_last_enabled(Server) of
|
||||
mod_last -> mod_last:get_last_info(User, Server);
|
||||
no_mod_last -> mod_last_required
|
||||
end.
|
||||
|
||||
%% @spec (Server) -> mod_last | no_mod_last
|
||||
get_mod_last_enabled(ServerB) when is_binary(ServerB)->
|
||||
Server = binary_to_list(ServerB),
|
||||
get_mod_last_enabled(Server);
|
||||
get_mod_last_enabled(Server) ->
|
||||
case gen_mod:is_loaded(Server, mod_last) of
|
||||
true -> mod_last;
|
||||
false -> no_mod_last
|
||||
end.
|
||||
|
||||
get_mod_last_configured(Server) ->
|
||||
case is_configured(Server, mod_last) of
|
||||
true -> mod_last;
|
||||
false -> no_mod_last
|
||||
end.
|
||||
|
||||
is_configured(Host, Module) ->
|
||||
lists:keymember(Module, 1, ejabberd_config:get_local_option({modules, Host})).
|
||||
|
||||
@@ -1,382 +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-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_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,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(passwd, {us, password}).
|
||||
-record(reg_users_counter, {vhost, count}).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% 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),
|
||||
ok.
|
||||
|
||||
update_reg_users_counter_table(Server) ->
|
||||
Set = get_vh_registered_users(Server),
|
||||
Size = length(Set),
|
||||
LServer = jlib:nameprep(Server),
|
||||
set_vh_registered_users_counter(LServer, Size).
|
||||
|
||||
plain_password_required() ->
|
||||
false.
|
||||
|
||||
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}] ->
|
||||
Password /= "";
|
||||
_ ->
|
||||
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}] ->
|
||||
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() ->
|
||||
mnesia:write(#passwd{us = US,
|
||||
password = Password})
|
||||
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
|
||||
[] ->
|
||||
mnesia:write(#passwd{us = US,
|
||||
password = Password}),
|
||||
inc_vh_registered_users_counter(LServer),
|
||||
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).
|
||||
|
||||
inc_vh_registered_users_counter(LServer) ->
|
||||
F = fun() ->
|
||||
case mnesia:wread({reg_users_counter, LServer}) of
|
||||
[C] ->
|
||||
Count = C#reg_users_counter.count + 1,
|
||||
C2 = C#reg_users_counter{count = Count},
|
||||
mnesia:write(C2);
|
||||
_ ->
|
||||
mnesia:write(#reg_users_counter{vhost = LServer,
|
||||
count = 1})
|
||||
end
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
dec_vh_registered_users_counter(LServer) ->
|
||||
F = fun() ->
|
||||
case mnesia:wread({reg_users_counter, LServer}) of
|
||||
[C] ->
|
||||
Count = C#reg_users_counter.count - 1,
|
||||
C2 = C#reg_users_counter{count = Count},
|
||||
mnesia:write(C2);
|
||||
_ ->
|
||||
error
|
||||
end
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
set_vh_registered_users_counter(LServer, Count) ->
|
||||
F = fun() ->
|
||||
mnesia:write(#reg_users_counter{vhost = LServer,
|
||||
count = Count})
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
get_password(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read(passwd, US) of
|
||||
[#passwd{password = Password}] ->
|
||||
Password;
|
||||
_ ->
|
||||
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}] ->
|
||||
Password;
|
||||
_ ->
|
||||
[]
|
||||
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}),
|
||||
dec_vh_registered_users_counter(LServer)
|
||||
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}] ->
|
||||
mnesia:delete({passwd, US}),
|
||||
dec_vh_registered_users_counter(LServer),
|
||||
ok;
|
||||
[_] ->
|
||||
not_allowed;
|
||||
_ ->
|
||||
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 ->
|
||||
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.
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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
|
||||
@@ -66,13 +66,12 @@
|
||||
servers,
|
||||
backups,
|
||||
port,
|
||||
encrypt,
|
||||
tls_options,
|
||||
dn,
|
||||
password,
|
||||
base,
|
||||
uids,
|
||||
ufilter,
|
||||
sfilter,
|
||||
lfilter, %% Local filter (performed by ejabberd, not LDAP)
|
||||
dn_filter,
|
||||
dn_filter_attrs
|
||||
@@ -95,27 +94,62 @@ 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]),
|
||||
case ejabberd_config:get_host_option(Host, ldap_servers) of
|
||||
undefined -> check_bad_config(Host);
|
||||
{host, _Host} -> ok;
|
||||
_ ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
ChildSpec = {
|
||||
Proc, {?MODULE, start_link, [Host]},
|
||||
transient, 1000, worker, [?MODULE]
|
||||
},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec)
|
||||
end.
|
||||
|
||||
check_bad_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};
|
||||
_ -> ok
|
||||
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,
|
||||
@@ -124,19 +158,26 @@ init(Host) ->
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password,
|
||||
State#state.encrypt),
|
||||
State#state.tls_options),
|
||||
eldap_pool:start_link(State#state.bind_eldap_id,
|
||||
State#state.servers,
|
||||
State#state.backups,
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password,
|
||||
State#state.encrypt),
|
||||
State#state.tls_options),
|
||||
{ok, State}.
|
||||
|
||||
%% @spec () -> true
|
||||
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
%% @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
|
||||
@@ -150,16 +191,43 @@ 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).
|
||||
|
||||
set_password(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
%% @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, Server, State) of
|
||||
false ->
|
||||
{error, user_not_found};
|
||||
DN ->
|
||||
eldap_pool:modify_passwd(State#state.eldap_id, DN, Password)
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> {error, not_allowed}
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
%% Password = string()
|
||||
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
%% @spec () -> [{LUser, LServer}]
|
||||
%% LUser = string()
|
||||
%% LServer = string()
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
Servers = ejabberd_config:get_vh_by_auth_method(ldap),
|
||||
lists:flatmap(
|
||||
@@ -167,22 +235,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} ->
|
||||
@@ -191,18 +279,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 ->
|
||||
@@ -210,25 +313,44 @@ 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,
|
||||
SortedDNAttrs = eldap_utils:usort_attrs(State#state.dn_filter_attrs),
|
||||
case eldap_filter:parse(State#state.sfilter) of
|
||||
SearchFilter = build_sfilter(State, UIDs),
|
||||
ResAttrs = result_attrs(State),
|
||||
case eldap_filter:parse(SearchFilter) of
|
||||
{ok, EldapFilter} ->
|
||||
case eldap_pool:search(Eldap_ID, [{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{timeout, ?LDAP_SEARCH_TIMEOUT},
|
||||
{attributes, SortedDNAttrs}]) of
|
||||
{attributes, ResAttrs}]) of
|
||||
#eldap_search_result{entries = Entries} ->
|
||||
lists:flatmap(
|
||||
fun(#eldap_entry{attributes = Attrs,
|
||||
object_name = DN}) ->
|
||||
case is_valid_dn(DN, Attrs, State) of
|
||||
case is_valid_dn(DN, Server, Attrs, State) of
|
||||
false -> [];
|
||||
_ ->
|
||||
case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
|
||||
@@ -236,9 +358,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
|
||||
@@ -252,9 +376,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.
|
||||
@@ -268,16 +396,17 @@ handle_call(stop, _From, State) ->
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, bad_request, State}.
|
||||
|
||||
find_user_dn(User, State) ->
|
||||
DNAttrs = eldap_utils:usort_attrs(State#state.dn_filter_attrs),
|
||||
case eldap_filter:parse(State#state.ufilter, [{"%u", User}]) of
|
||||
find_user_dn(User, Server, State) ->
|
||||
ResAttrs = result_attrs(State),
|
||||
UserFilter = build_ufilter(State, Server),
|
||||
case eldap_filter:parse(UserFilter, [{"%u", User}]) of
|
||||
{ok, Filter} ->
|
||||
case eldap_pool:search(State#state.eldap_id, [{base, State#state.base},
|
||||
{filter, Filter},
|
||||
{attributes, DNAttrs}]) of
|
||||
{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;
|
||||
@@ -286,20 +415,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;
|
||||
@@ -311,10 +440,10 @@ is_valid_dn(DN, Attrs, State) ->
|
||||
end ++ [{"%d", State#state.host}, {"%D", DN}],
|
||||
case eldap_filter:parse(State#state.dn_filter, SubstValues) of
|
||||
{ok, EldapFilter} ->
|
||||
case eldap_pool:search(State#state.eldap_id, [
|
||||
{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{attributes, ["dn"]}]) of
|
||||
case eldap_pool:search(State#state.eldap_id,
|
||||
[{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{attributes, ["dn"]}]) of
|
||||
#eldap_search_result{entries = [_|_]} ->
|
||||
DN;
|
||||
_ ->
|
||||
@@ -346,6 +475,30 @@ local_filter(equal, Attrs, FilterMatch) ->
|
||||
local_filter(notequal, Attrs, FilterMatch) ->
|
||||
not local_filter(equal, Attrs, FilterMatch).
|
||||
|
||||
result_attrs(#state{uids = UIDs, dn_filter_attrs = DNFilterAttrs}) ->
|
||||
lists:foldl(
|
||||
fun({UID}, Acc) ->
|
||||
[UID | Acc];
|
||||
({UID, _}, Acc) ->
|
||||
[UID | Acc]
|
||||
end, DNFilterAttrs, UIDs).
|
||||
|
||||
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
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -358,6 +511,7 @@ parse_options(Host) ->
|
||||
Backups -> Backups
|
||||
end,
|
||||
LDAPEncrypt = ejabberd_config:get_local_option({ldap_encrypt, Host}),
|
||||
LDAPTLSVerify = ejabberd_config:get_local_option({ldap_tls_verify, Host}),
|
||||
LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of
|
||||
undefined -> case LDAPEncrypt of
|
||||
tls -> ?LDAPS_PORT;
|
||||
@@ -376,20 +530,21 @@ parse_options(Host) ->
|
||||
end,
|
||||
UIDs = case ejabberd_config:get_local_option({ldap_uids, Host}) of
|
||||
undefined -> [{"uid", "%u"}];
|
||||
UI -> eldap_utils:uids_domain_subst(Host, UI)
|
||||
UI -> UI
|
||||
end,
|
||||
SubFilter = lists:flatten(eldap_utils:generate_subfilter(UIDs)),
|
||||
UserFilter = case ejabberd_config:get_local_option({ldap_filter, Host}) of
|
||||
undefined -> SubFilter;
|
||||
"" -> SubFilter;
|
||||
F -> "(&" ++ SubFilter ++ F ++ ")"
|
||||
undefined -> "";
|
||||
F -> F
|
||||
end,
|
||||
SearchFilter = eldap_filter:do_sub(UserFilter, [{"%u", "*"}]),
|
||||
LDAPBase = ejabberd_config:get_local_option({ldap_base, Host}),
|
||||
{DNFilter, DNFilterAttrs} =
|
||||
case ejabberd_config:get_local_option({ldap_dn_filter, Host}) of
|
||||
undefined -> {undefined, undefined};
|
||||
{DNF, DNFA} -> {DNF, DNFA}
|
||||
undefined ->
|
||||
{undefined, []};
|
||||
{DNF, undefined} ->
|
||||
{DNF, []};
|
||||
{DNF, DNFA} ->
|
||||
{DNF, DNFA}
|
||||
end,
|
||||
LocalFilter = ejabberd_config:get_local_option({ldap_local_filter, Host}),
|
||||
#state{host = Host,
|
||||
@@ -398,13 +553,13 @@ parse_options(Host) ->
|
||||
servers = LDAPServers,
|
||||
backups = LDAPBackups,
|
||||
port = LDAPPort,
|
||||
encrypt = LDAPEncrypt,
|
||||
tls_options = [{encrypt, LDAPEncrypt},
|
||||
{tls_verify, LDAPTLSVerify}],
|
||||
dn = RootDN,
|
||||
password = Password,
|
||||
base = LDAPBase,
|
||||
uids = UIDs,
|
||||
ufilter = UserFilter,
|
||||
sfilter = SearchFilter,
|
||||
lfilter = LocalFilter,
|
||||
dn_filter = DNFilter,
|
||||
dn_filter_attrs = DNFilterAttrs
|
||||
|
||||
@@ -1,281 +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-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_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,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(_Host) ->
|
||||
ok.
|
||||
|
||||
plain_password_required() ->
|
||||
false.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false | {error, Error}
|
||||
check_password(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
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.
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 5 Jul 2007 by Evgeniy Khramtsov <xram@jabber.ru>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 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,
|
||||
@@ -45,6 +46,10 @@
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
|
||||
%% @spec (Host) -> ok | term()
|
||||
%% Host = string()
|
||||
|
||||
start(_Host) ->
|
||||
case epam:start() of
|
||||
{ok, _} -> ok;
|
||||
@@ -52,57 +57,134 @@ 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),
|
||||
case catch epam:authenticate(Service, User, Password) 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++"@"++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),
|
||||
case catch epam:acct_mgmt(Service, User) 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++"@"++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.
|
||||
|
||||
%%====================================================================
|
||||
%% 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.
|
||||
get_pam_userinfotype(Host) ->
|
||||
case ejabberd_config:get_local_option({pam_userinfotype, Host}) of
|
||||
undefined -> username;
|
||||
Type -> Type
|
||||
end.
|
||||
|
||||
@@ -0,0 +1,477 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% 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-alpha / odbc / passwd
|
||||
%%% user = varchar150
|
||||
%%% host = varchar150
|
||||
%%% password = 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,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(passwd, {user_host, password}).
|
||||
-record(reg_users_counter, {vhost, count}).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% 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}}]}
|
||||
]),
|
||||
update_table(Host, Backend),
|
||||
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() ->
|
||||
false.
|
||||
|
||||
%% @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 = 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}] ->
|
||||
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() ->
|
||||
gen_storage:write(LServer,
|
||||
#passwd{user_host = US,
|
||||
password = Password})
|
||||
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
|
||||
[] ->
|
||||
gen_storage:write(LServer,
|
||||
#passwd{user_host = US,
|
||||
password = Password}),
|
||||
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() ->
|
||||
%% TODO:
|
||||
exit(not_implemented).
|
||||
|
||||
%% @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 = 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 = 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_allowed;
|
||||
_ ->
|
||||
not_exists
|
||||
end
|
||||
end,
|
||||
case gen_storage:transaction(LServer, passwd, F) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{atomic, Res} ->
|
||||
Res
|
||||
end
|
||||
catch
|
||||
_ ->
|
||||
bad_request
|
||||
end.
|
||||
|
||||
update_table(Host, mnesia) ->
|
||||
gen_storage_migration:migrate_mnesia(
|
||||
Host, passwd,
|
||||
[{passwd, [us, password],
|
||||
fun({passwd, {User, _Host}, Password}) ->
|
||||
#passwd{user_host = {User, Host},
|
||||
password = Password}
|
||||
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}]).
|
||||
@@ -6,7 +6,7 @@
|
||||
%%% Created : 2 Nov 2007 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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 : 26 Apr 2008 by Evgeniy Khramtsov <xramtsov@gmail.com>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 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,32 +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,
|
||||
process_reply/1, process/2, is_feature_available/0]).
|
||||
-export([create_captcha/5, build_captcha_html/2, check_captcha/2,
|
||||
process_reply/1, process/2, is_feature_available/0,
|
||||
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(RPC_TIMEOUT, 5000).
|
||||
|
||||
-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
|
||||
%%====================================================================
|
||||
@@ -71,47 +94,228 @@
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
create_captcha(Id, SID, From, To, Lang, Args)
|
||||
when is_list(Id), is_list(Lang), is_list(SID),
|
||||
is_record(From, jid), is_record(To, jid) ->
|
||||
create_captcha(SID, From, To, Lang, Args)
|
||||
when is_binary(Lang), is_binary(SID) ->
|
||||
case create_image() of
|
||||
{ok, Type, Key, Image} ->
|
||||
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, "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, [Body, OOB, Captcha, Data]};
|
||||
_Err ->
|
||||
error
|
||||
end;
|
||||
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, HeadEls) ->
|
||||
create_captcha_x(SID, To, Lang, HeadEls, []).
|
||||
|
||||
create_captcha_x(SID, To, Lang, HeadEls, TailEls) ->
|
||||
case create_image() of
|
||||
{ok, Type, Key, Image} ->
|
||||
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 = list_to_binary(get_url(Id ++ "/image")),
|
||||
Captcha =
|
||||
#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}),
|
||||
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
|
||||
%% where FormEl = xmlelement()
|
||||
%% ImgEl = xmlelement()
|
||||
@@ -119,30 +323,106 @@ create_captcha(Id, SID, From, To, Lang, Args)
|
||||
%% 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
|
||||
@@ -150,67 +430,71 @@ 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 ->
|
||||
Pid ! {captcha_succeed, Args},
|
||||
captcha_valid;
|
||||
true ->
|
||||
Pid ! {captcha_failed, Args},
|
||||
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, "captcha", _, _} = 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 {proplists:get_value("challenge", Fields),
|
||||
proplists:get_value("ocr", Fields)} of
|
||||
case catch {proplists:get_value("challenge", Fields),
|
||||
proplists:get_value("ocr", Fields)} of
|
||||
{[Id|_], [OCR|_]} ->
|
||||
?T(case mnesia:read(captcha, Id, write) of
|
||||
[#captcha{pid=Pid, args=Args, key=Key, tref=Tref}] ->
|
||||
mnesia:delete({captcha, Id}),
|
||||
erlang:cancel_timer(Tref),
|
||||
if OCR == Key ->
|
||||
Pid ! {captcha_succeed, Args},
|
||||
ok;
|
||||
true ->
|
||||
Pid ! {captcha_failed, Args},
|
||||
{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"]}) ->
|
||||
case mnesia:dirty_read(captcha, Id) of
|
||||
[#captcha{key=Key}] ->
|
||||
case lookup_captcha(Id) of
|
||||
{ok, #captcha{key=Key}} ->
|
||||
case create_image(Key) of
|
||||
{ok, Type, _, Img} ->
|
||||
{200,
|
||||
@@ -230,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);
|
||||
@@ -244,15 +533,12 @@ 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{}}.
|
||||
|
||||
@@ -264,13 +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}] ->
|
||||
Pid ! {captcha_failed, Args},
|
||||
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) ->
|
||||
@@ -288,7 +578,7 @@ 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()
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -302,11 +592,11 @@ create_image(Key) ->
|
||||
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.",
|
||||
@@ -328,17 +618,59 @@ get_prog_name() ->
|
||||
FileName when is_list(FileName) ->
|
||||
FileName;
|
||||
_ ->
|
||||
""
|
||||
?DEBUG("The option captcha_cmd is not configured, but some "
|
||||
"module wants to use the CAPTCHA feature.", []),
|
||||
throw({error, option_not_configured_captcha_cmd})
|
||||
end.
|
||||
|
||||
%% @doc (Str::string()) -> string()
|
||||
get_url(Str) ->
|
||||
case ejabberd_config:get_local_option(captcha_host) of
|
||||
Host when is_list(Host) ->
|
||||
"http://" ++ Host ++ "/captcha/" ++ Str;
|
||||
CaptchaHost = ejabberd_config:get_local_option(captcha_host),
|
||||
case string:tokens(CaptchaHost, ":") of
|
||||
[TransferProt, Host, PortString] ->
|
||||
TransferProt ++ ":" ++ Host ++ ":" ++ PortString ++ "/captcha/" ++ Str;
|
||||
[Host, PortString] ->
|
||||
TransferProt = atom_to_list(get_transfer_protocol(PortString)),
|
||||
TransferProt ++ "://" ++ Host ++ ":" ++ PortString ++ "/captcha/" ++ Str;
|
||||
_ ->
|
||||
"http://" ++ ?MYNAME ++ "/captcha/" ++ Str
|
||||
"http://" ++ ?MYNAME ++ ":5280/captcha/" ++ Str
|
||||
end.
|
||||
|
||||
get_transfer_protocol(PortString) ->
|
||||
PortNumber = list_to_integer(PortString),
|
||||
PortListeners = get_port_listeners(PortNumber),
|
||||
get_captcha_transfer_protocol(PortListeners).
|
||||
|
||||
get_port_listeners(PortNumber) ->
|
||||
AllListeners = ejabberd_config:get_local_option(listen),
|
||||
lists:filter(
|
||||
fun({{Port, _Ip, _Netp}, _Module1, _Opts1}) when Port == PortNumber ->
|
||||
true;
|
||||
(_) ->
|
||||
false
|
||||
end,
|
||||
AllListeners).
|
||||
|
||||
get_captcha_transfer_protocol([]) ->
|
||||
throw("The port number mentioned in captcha_host is not "
|
||||
"a ejabberd_http listener with 'captcha' option. "
|
||||
"Change the port number or specify http:// in that option.");
|
||||
get_captcha_transfer_protocol([{{_Port, _Ip, tcp}, ejabberd_http, Opts}
|
||||
| Listeners]) ->
|
||||
case lists:member(captcha, Opts) of
|
||||
true ->
|
||||
case lists:member(tls, Opts) of
|
||||
true ->
|
||||
https;
|
||||
false ->
|
||||
http
|
||||
end;
|
||||
false ->
|
||||
get_captcha_transfer_protocol(Listeners)
|
||||
end;
|
||||
get_captcha_transfer_protocol([_ | Listeners]) ->
|
||||
get_captcha_transfer_protocol(Listeners).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: cmd(Cmd) -> Data | {error, Reason}
|
||||
%% Cmd = string()
|
||||
@@ -388,9 +720,10 @@ return(Port, TRef, Result) ->
|
||||
Result.
|
||||
|
||||
is_feature_enabled() ->
|
||||
case get_prog_name() of
|
||||
"" -> false;
|
||||
try get_prog_name() of
|
||||
Prog when is_list(Prog) -> true
|
||||
catch
|
||||
_:_ -> false
|
||||
end.
|
||||
|
||||
is_feature_available() ->
|
||||
@@ -411,3 +744,51 @@ check_captcha_setup() ->
|
||||
false ->
|
||||
ok
|
||||
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.
|
||||
|
||||
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-2009 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-2009 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
|
||||
@@ -356,15 +356,14 @@ get_tags_commands() ->
|
||||
%% @doc Check access is allowed to that command.
|
||||
%% At least one AccessCommand must be satisfied.
|
||||
%% It may throw {error, Error} where:
|
||||
%% Error = account_unprivileged | invalid_account_data | no_auth_provided
|
||||
%% Error = account_unprivileged | invalid_account_data
|
||||
check_access_commands([], _Auth, _Method, _Command, _Arguments) ->
|
||||
ok;
|
||||
check_access_commands(AccessCommands, Auth, Method, Command, Arguments) ->
|
||||
{ok, User, Server} = check_auth(Auth),
|
||||
AccessCommandsAllowed =
|
||||
lists:filter(
|
||||
fun({Access, Commands, ArgumentRestrictions}) ->
|
||||
case check_access(Access, User, Server) of
|
||||
case check_access(Access, Auth) of
|
||||
true ->
|
||||
check_access_command(Commands, Command, ArgumentRestrictions,
|
||||
Method, Arguments);
|
||||
@@ -379,7 +378,7 @@ check_access_commands(AccessCommands, Auth, Method, Command, Arguments) ->
|
||||
end.
|
||||
|
||||
check_auth(noauth) ->
|
||||
throw({error, no_auth_provided});
|
||||
no_auth_provided;
|
||||
check_auth({User, Server, Password}) ->
|
||||
%% Check the account exists and password is valid
|
||||
AccountPass = ejabberd_auth:get_password_s(User, Server),
|
||||
@@ -394,9 +393,12 @@ get_md5(AccountPass) ->
|
||||
lists:flatten([io_lib:format("~.16B", [X])
|
||||
|| X <- binary_to_list(crypto:md5(AccountPass))]).
|
||||
|
||||
check_access(Access, User, Server) ->
|
||||
check_access(all, _) ->
|
||||
true;
|
||||
check_access(Access, Auth) ->
|
||||
{ok, User, Server} = check_auth(Auth),
|
||||
%% Check this user has access permission
|
||||
case acl:match_rule(Server, Access, jlib:make_jid(User, Server, "")) of
|
||||
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-2009 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 : 14 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 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()}
|
||||
|
||||
@@ -101,15 +117,14 @@ get_plain_terms_file(File1) ->
|
||||
case file:consult(File) of
|
||||
{ok, Terms} ->
|
||||
include_config_files(Terms);
|
||||
{error, {_LineNumber, erl_parse, _ParseMessage} = Reason} ->
|
||||
ExitText = lists:flatten(File ++ " approximately in the line "
|
||||
++ file:format_error(Reason)),
|
||||
?ERROR_MSG("Problem loading ejabberd config file ~n~s", [ExitText]),
|
||||
exit(ExitText);
|
||||
{error, {LineNumber, erl_parse, _ParseMessage} = Reason} ->
|
||||
ExitText = describe_config_problem(File, Reason, LineNumber),
|
||||
?ERROR_MSG(ExitText, []),
|
||||
exit_or_halt(ExitText);
|
||||
{error, Reason} ->
|
||||
ExitText = lists:flatten(File ++ ": " ++ file:format_error(Reason)),
|
||||
?ERROR_MSG("Problem loading ejabberd config file ~n~s", [ExitText]),
|
||||
exit(ExitText)
|
||||
ExitText = describe_config_problem(File, Reason),
|
||||
?ERROR_MSG(ExitText, []),
|
||||
exit_or_halt(ExitText)
|
||||
end.
|
||||
|
||||
%% @doc Convert configuration filename to absolute path.
|
||||
@@ -152,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) ->
|
||||
@@ -160,15 +184,89 @@ 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
|
||||
|
||||
describe_config_problem(Filename, Reason) ->
|
||||
Text1 = lists:flatten("Problem loading ejabberd config file " ++ Filename),
|
||||
Text2 = lists:flatten(" : " ++ file:format_error(Reason)),
|
||||
ExitText = Text1 ++ Text2,
|
||||
ExitText.
|
||||
|
||||
describe_config_problem(Filename, Reason, LineNumber) ->
|
||||
Text1 = lists:flatten("Problem loading ejabberd config file " ++ Filename),
|
||||
Text2 = lists:flatten(" approximately in the line "
|
||||
++ file:format_error(Reason)),
|
||||
ExitText = Text1 ++ Text2,
|
||||
Lines = get_config_lines(Filename, LineNumber, 10, 3),
|
||||
?ERROR_MSG("The following lines from your configuration file might be"
|
||||
" relevant to the error: ~n~s", [Lines]),
|
||||
ExitText.
|
||||
|
||||
get_config_lines(Filename, TargetNumber, PreContext, PostContext) ->
|
||||
{ok, Fd} = file:open(Filename, [read]),
|
||||
LNumbers = lists:seq(TargetNumber-PreContext, TargetNumber+PostContext),
|
||||
NextL = io:get_line(Fd, no_prompt),
|
||||
R = get_config_lines2(Fd, NextL, 1, LNumbers, []),
|
||||
file:close(Fd),
|
||||
R.
|
||||
|
||||
get_config_lines2(_Fd, eof, _CurrLine, _LNumbers, R) ->
|
||||
lists:reverse(R);
|
||||
get_config_lines2(_Fd, _NewLine, _CurrLine, [], R) ->
|
||||
lists:reverse(R);
|
||||
get_config_lines2(Fd, Data, CurrLine, [NextWanted | LNumbers], R) when is_list(Data) ->
|
||||
NextL = io:get_line(Fd, no_prompt),
|
||||
if
|
||||
CurrLine >= NextWanted ->
|
||||
Line2 = [integer_to_list(CurrLine), ": " | Data],
|
||||
get_config_lines2(Fd, NextL, CurrLine+1, LNumbers, [Line2 | R]);
|
||||
true ->
|
||||
get_config_lines2(Fd, NextL, CurrLine+1, [NextWanted | LNumbers], R)
|
||||
end.
|
||||
|
||||
%% If ejabberd isn't yet running in this node, then halt the node
|
||||
exit_or_halt(ExitText) ->
|
||||
case [Vsn || {ejabberd, _Desc, Vsn} <- application:which_applications()] of
|
||||
[] ->
|
||||
timer:sleep(1000),
|
||||
halt(ExitText);
|
||||
[_] ->
|
||||
exit(ExitText)
|
||||
end.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% Support for 'include_config_file'
|
||||
@@ -327,6 +425,8 @@ process_term(Term, 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(
|
||||
@@ -343,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} ->
|
||||
@@ -377,20 +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);
|
||||
{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) ->
|
||||
@@ -412,6 +513,15 @@ process_host_term(Term, Host, State) ->
|
||||
State;
|
||||
{odbc_server, ODBC_server} ->
|
||||
add_option({odbc_server, Host}, ODBC_server, 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.
|
||||
@@ -515,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}] ->
|
||||
@@ -537,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,
|
||||
@@ -555,3 +703,86 @@ is_file_readable(Path) ->
|
||||
{error, _Reason} ->
|
||||
false
|
||||
end.
|
||||
|
||||
search(Pattern) ->
|
||||
{atomic, Res} = mnesia:transaction(fun mnesia:select/2, [local_config, Pattern]),
|
||||
Res.
|
||||
|
||||
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-2009 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}).
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 11 Jan 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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
|
||||
@@ -189,16 +189,20 @@ process(["help" | Mode]) ->
|
||||
["help"] ->
|
||||
print_usage_help(MaxC, ShCode),
|
||||
?STATUS_SUCCESS;
|
||||
[CommandString | _] ->
|
||||
print_usage_commands(CommandString, MaxC, ShCode),
|
||||
[CmdString | _] ->
|
||||
CmdStringU = re:replace(CmdString, "-", "_", [global, {return, list}]),
|
||||
print_usage_commands(CmdStringU, MaxC, ShCode),
|
||||
?STATUS_SUCCESS
|
||||
end;
|
||||
|
||||
process(Args) ->
|
||||
AccessCommands = get_accesscommands(),
|
||||
{String, Code} = process2(Args, AccessCommands),
|
||||
io:format(String),
|
||||
io:format("\n"),
|
||||
case String of
|
||||
[] -> ok;
|
||||
_ ->
|
||||
io:format("~s~n", [String])
|
||||
end,
|
||||
Code.
|
||||
|
||||
%% @spec (Args::[string()], AccessCommands) -> {String::string(), Code::integer()}
|
||||
@@ -263,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_number_parameters} ->
|
||||
{"Error: wrong number of parameters", ?STATUS_ERROR};
|
||||
{error, String} ->
|
||||
{String, ?STATUS_ERROR};
|
||||
Res ->
|
||||
Res
|
||||
catch
|
||||
@@ -275,13 +277,13 @@ 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) ->
|
||||
{ok, CmdStringU, _} = regexp:gsub(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) ->
|
||||
@@ -294,9 +296,8 @@ call_command([CmdString | Args], Auth, AccessCommands) ->
|
||||
{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.
|
||||
|
||||
@@ -314,18 +315,19 @@ format_args(Args, ArgsFormat) ->
|
||||
[],
|
||||
lists:zip(ArgsFormat, Args)).
|
||||
|
||||
format_arg(Arg, Format) ->
|
||||
Parse = case Format of
|
||||
integer ->
|
||||
"~d";
|
||||
string ->
|
||||
NumChars = integer_to_list(string:len(Arg)),
|
||||
"~" ++ NumChars ++ "c"
|
||||
end,
|
||||
format_arg(Arg, integer) ->
|
||||
format_arg2(Arg, "~d");
|
||||
format_arg("", string) ->
|
||||
"";
|
||||
format_arg(Arg, string) ->
|
||||
NumChars = integer_to_list(string:len(Arg)),
|
||||
Parse = "~" ++ NumChars ++ "c",
|
||||
format_arg2(Arg, Parse).
|
||||
|
||||
format_arg2(Arg, Parse)->
|
||||
{ok, [Arg2], _RemainingArguments} = io_lib:fread(Parse, Arg),
|
||||
Arg2.
|
||||
|
||||
|
||||
%%-----------------------------
|
||||
%% Format result
|
||||
%%-----------------------------
|
||||
@@ -436,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)"},
|
||||
@@ -609,26 +610,26 @@ 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",
|
||||
@@ -673,13 +674,13 @@ filter_commands(All, SubString) ->
|
||||
end.
|
||||
|
||||
filter_commands_regexp(All, Glob) ->
|
||||
RegExp = regexp:sh_to_awk(Glob),
|
||||
RegExp = xmerl_regexp:sh_to_awk(Glob),
|
||||
lists:filter(
|
||||
fun(Command) ->
|
||||
case regexp:first_match(Command, RegExp) of
|
||||
{match, _, _} ->
|
||||
case re:run(Command, RegExp, [{capture, none}]) of
|
||||
match ->
|
||||
true;
|
||||
_ ->
|
||||
nomatch ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
@@ -695,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,
|
||||
@@ -736,6 +738,10 @@ print_usage_command(Cmd, C, MaxC, ShCode) ->
|
||||
|
||||
?PRINT(["\n", NameFmt, "\n", ArgsFmt, "\n", ReturnsFmt, "\n\n", XmlrpcFmt, TagsFmt, "\n\n", DescFmt, "\n\n", LongDescFmt, NoteEjabberdctl], []).
|
||||
|
||||
format_usage_ctype(Type, _Indentation)
|
||||
when (Type==atom) or (Type==integer) or (Type==string) or (Type==rescode) or (Type==restuple)->
|
||||
io_lib:format("~p", [Type]);
|
||||
|
||||
format_usage_ctype({Name, Type}, _Indentation)
|
||||
when (Type==atom) or (Type==integer) or (Type==string) or (Type==rescode) or (Type==restuple)->
|
||||
io_lib:format("~p::~p", [Name, Type]);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_debug.erl
|
||||
%%% Author : Mickael Remond
|
||||
%%% Purpose : ejabberd's application callback module
|
||||
%%% Created : 6 may 2009 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_debug).
|
||||
|
||||
-export([eprof_start/0, fprof_start/0, stop/0]).
|
||||
-export([pids/0]).
|
||||
|
||||
eprof_start() ->
|
||||
eprof:start(),
|
||||
eprof:profile(pids()).
|
||||
|
||||
fprof_start() ->
|
||||
fprof:trace([start, {file, "/tmp/fprof"}, {procs, pids()}]).
|
||||
|
||||
%% Stop all profilers
|
||||
stop() ->
|
||||
catch eprof:stop(),
|
||||
catch fprof:stop(),
|
||||
ok.
|
||||
|
||||
pids() ->
|
||||
lists:zf(
|
||||
fun(Pid) ->
|
||||
case process_info(Pid) of
|
||||
ProcessInfo when is_list(ProcessInfo) ->
|
||||
CurrentFunction = current_function(ProcessInfo),
|
||||
InitialCall = initial_call(ProcessInfo),
|
||||
RegisteredName = registered_name(ProcessInfo),
|
||||
Ancestor = ancestor(ProcessInfo),
|
||||
filter_pid(Pid, CurrentFunction, InitialCall, RegisteredName, Ancestor);
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
processes()).
|
||||
|
||||
current_function(ProcessInfo) ->
|
||||
{value, {_, {CurrentFunction, _,_}}} =
|
||||
lists:keysearch(current_function, 1, ProcessInfo),
|
||||
atom_to_list(CurrentFunction).
|
||||
|
||||
initial_call(ProcessInfo) ->
|
||||
{value, {_, {InitialCall, _,_}}} =
|
||||
lists:keysearch(initial_call, 1, ProcessInfo),
|
||||
atom_to_list(InitialCall).
|
||||
|
||||
registered_name(ProcessInfo) ->
|
||||
case lists:keysearch(registered_name, 1, ProcessInfo) of
|
||||
{value, {_, Name}} when is_atom(Name) -> atom_to_list(Name);
|
||||
_ -> ""
|
||||
end.
|
||||
|
||||
ancestor(ProcessInfo) ->
|
||||
{value, {_, Dictionary}} = lists:keysearch(dictionary, 1, ProcessInfo),
|
||||
case lists:keysearch('$ancestors', 1, Dictionary) of
|
||||
{value, {_, [Ancestor|_T]}} when is_atom(Ancestor) ->
|
||||
atom_to_list(Ancestor);
|
||||
_ ->
|
||||
""
|
||||
end.
|
||||
|
||||
filter_pid(Pid, "ejabberd" ++ _, _InitialCall, _RegisteredName, _Ancestor) ->
|
||||
{true, Pid};
|
||||
filter_pid(Pid, _CurrentFunction, "ejabberd" ++ _, _RegisteredName, _Ancestor) ->
|
||||
{true, Pid};
|
||||
filter_pid(Pid, _CurrentFunction, _InitialCall, "ejabberd"++_, _Ancestor) ->
|
||||
{true, Pid};
|
||||
filter_pid(Pid, _CurrentFunction, _InitialCall, "stringprep"++_, _Ancestor) ->
|
||||
{true, Pid};
|
||||
filter_pid(Pid, _CurrentFunction, _InitialCall, _RegisteredName, "ejabberd"++_) ->
|
||||
{true, Pid};
|
||||
filter_pid(_Pid, _CurrentFunction, _InitialCall, _RegisteredName, _Ancestor) ->
|
||||
false.
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 23 Aug 2006 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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}).
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 8 Aug 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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) ->
|
||||
@@ -117,14 +119,27 @@ delete_dist(Hook, Host, Node, Module, Function, Seq) ->
|
||||
%% @doc Run the calls of this hook in order, don't care about function results.
|
||||
%% If a call returns stop, no more calls are performed.
|
||||
run(Hook, Args) ->
|
||||
run(Hook, global, Args).
|
||||
runx(Hook, global, Args).
|
||||
|
||||
run(Hook, Host, Args) ->
|
||||
case ets:lookup(hooks, {Hook, Host}) of
|
||||
run(Hook, Host, Args) when is_binary(Host) ->
|
||||
case runx(Hook, Host, Args) of
|
||||
stop -> stop;
|
||||
_ -> runx(Hook, global, Args)
|
||||
end;
|
||||
run(Hook, Host, Args) when Host == global ->
|
||||
runx(Hook, Host, Args).
|
||||
|
||||
runx(Hook, Host, Args) when is_binary(Host) orelse is_atom(Host) ->
|
||||
case ets:lookup(hooks, {Hook, ejabberd:normalize_host(Host)}) of
|
||||
[{_, Ls}] ->
|
||||
run1(Ls, Hook, Args);
|
||||
[] ->
|
||||
ok
|
||||
case ets:lookup(hooks, {Hook, global}) of
|
||||
[{_, Ls}] ->
|
||||
run1(Ls, Hook, Args);
|
||||
[] ->
|
||||
ok
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (Hook::atom(), Val, Args) -> Val | stopped | NewVal
|
||||
@@ -134,14 +149,29 @@ run(Hook, Host, Args) ->
|
||||
%% If a call returns 'stop', no more calls are performed and 'stopped' is returned.
|
||||
%% If a call returns {stopped, NewVal}, no more calls are performed and NewVal is returned.
|
||||
run_fold(Hook, Val, Args) ->
|
||||
run_fold(Hook, global, Val, Args).
|
||||
run_foldx(Hook, global, Val, Args).
|
||||
|
||||
run_fold(Hook, Host, Val, Args) ->
|
||||
case ets:lookup(hooks, {Hook, Host}) of
|
||||
%% @spec (Hook::atom(), Host, Val, Args) -> Val | stopped | NewVal
|
||||
%% Host = global | binary()
|
||||
run_fold(Hook, Host, Val, Args) when is_binary(Host) ->
|
||||
case run_foldx(Hook, Host, Val, Args) of
|
||||
stopped -> stopped;
|
||||
Val2 -> run_foldx(Hook, global, Val2, Args)
|
||||
end;
|
||||
run_fold(Hook, Host, Val, Args) when Host == global ->
|
||||
run_foldx(Hook, Host, Val, Args).
|
||||
|
||||
run_foldx(Hook, Host, Val, Args) ->
|
||||
case ets:lookup(hooks, {Hook, ejabberd:normalize_host(Host)}) of
|
||||
[{_, Ls}] ->
|
||||
run_fold1(Ls, Hook, Val, Args);
|
||||
[] ->
|
||||
Val
|
||||
case ets:lookup(hooks, {Hook, global}) of
|
||||
[{_, Ls}] ->
|
||||
run_fold1(Ls, Hook, Val, Args);
|
||||
[] ->
|
||||
Val
|
||||
end
|
||||
end.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -169,7 +199,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 +208,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 +236,11 @@ 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}),
|
||||
ets:insert(hooks, {{Hook, NHost}, NewLs}),
|
||||
ok;
|
||||
[] ->
|
||||
ok
|
||||
@@ -295,7 +327,7 @@ run1([{_Seq, Module, Function} | Ls], Hook, Args) ->
|
||||
[Reason, {Hook, Args}]),
|
||||
run1(Ls, Hook, Args);
|
||||
stop ->
|
||||
ok;
|
||||
stop;
|
||||
_ ->
|
||||
run1(Ls, Hook, Args)
|
||||
end.
|
||||
|
||||
@@ -0,0 +1,492 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% 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)
|
||||
%%%
|
||||
%%% 3.0.0-alpha-x / mnesia / hosts
|
||||
%%% host = string()
|
||||
%%% clusterid = integer()
|
||||
%%% config = string()
|
||||
%%%
|
||||
%%% 3.0.0-alpha-x / odbc / hosts
|
||||
%%% host = varchar150
|
||||
%%% clusterid = integer
|
||||
%%% config = text
|
||||
|
||||
-module(ejabberd_hosts).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% External API
|
||||
-export([start_link/0
|
||||
,reload/0
|
||||
]).
|
||||
|
||||
%% Host Registration API
|
||||
-export([register/1
|
||||
,register/2
|
||||
,registered/1
|
||||
,running/1
|
||||
,registered/0
|
||||
,remove/1
|
||||
,update_host_conf/2
|
||||
]).
|
||||
|
||||
%% Host control API
|
||||
-export([start_host/1
|
||||
,start_hosts/1
|
||||
,stop_host/1
|
||||
,stop_hosts/1
|
||||
,load_host_cert/2
|
||||
]).
|
||||
|
||||
%% Private utility functions
|
||||
-export([get_hosts/1
|
||||
,config_from_string/2
|
||||
,get_host_config/2
|
||||
,diff_hosts/0
|
||||
,diff_hosts/1
|
||||
,diff_hosts/2
|
||||
,reload_hosts/0
|
||||
,delete_host_config/1
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("ejabberd_config.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
-record(state, {state=wait_odbc,
|
||||
backend=mnesia,
|
||||
odbc_wait_time=120}).
|
||||
-record(hosts, {host, clusterid, config}).
|
||||
|
||||
-define(RELOAD_INTERVAL, timer:seconds(60)).
|
||||
-define(ODBC_STARTUP_TIME, 120). % 2minute limit for ODBC startup.
|
||||
|
||||
%% The vhost where the table 'hosts' is stored
|
||||
%% The table 'hosts' is defined in gen_storage as being for the "localhost" vhost
|
||||
-define(HOSTS_HOST, list_to_binary(?MYNAME)).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
|
||||
reload() ->
|
||||
?MODULE ! reload.
|
||||
|
||||
%% Creates a vhost in the system.
|
||||
register(Host) when is_list(Host) -> ?MODULE:register(Host, "").
|
||||
register(Host, Config) when is_list(Host), is_list(Config) ->
|
||||
true = exmpp_stringprep:is_node(Host),
|
||||
ID = get_clusterid(),
|
||||
H = #hosts{host = Host, clusterid = ID, config = Config},
|
||||
ok = gen_storage:dirty_write(?HOSTS_HOST, H),
|
||||
reload(),
|
||||
ok.
|
||||
|
||||
%% Updates host configuration
|
||||
update_host_conf(Host, Config) when is_list(Host), is_list(Config) ->
|
||||
true = exmpp_stringprep:is_node(Host),
|
||||
case registered(Host) of
|
||||
false -> {error, host_process_not_registered};
|
||||
true ->
|
||||
remove(Host),
|
||||
?MODULE:register(Host, Config)
|
||||
end.
|
||||
|
||||
%% Removes a vhost from the system,
|
||||
%% XXX deleting all ODBC data.
|
||||
remove(Host) when is_list(Host) ->
|
||||
true = exmpp_stringprep:is_node(Host),
|
||||
ID = get_clusterid(),
|
||||
gen_storage:dirty_delete_where(
|
||||
?HOSTS_HOST, hosts,
|
||||
[{'andalso',
|
||||
{'==', clusterid, ID},
|
||||
{'==', host, Host}}]),
|
||||
reload(),
|
||||
ok.
|
||||
|
||||
registered() ->
|
||||
mnesia:dirty_select(local_config,
|
||||
ets:fun2ms(fun (#local_config{key={Host, host}}) ->
|
||||
Host
|
||||
end)).
|
||||
|
||||
registered(Host) when is_list(Host) ->
|
||||
case mnesia:dirty_read({local_config, {Host, host}}) of
|
||||
[{local_config, {Host, host}, _}] -> true;
|
||||
[] -> false
|
||||
end.
|
||||
|
||||
running(global) -> true;
|
||||
running(HostString) when is_list(HostString) ->
|
||||
Host = list_to_binary(HostString),
|
||||
Routes = [H
|
||||
|| {H, _, {apply, ejabberd_local, route}} <- ejabberd_router:read_route(Host),
|
||||
H =:= Host],
|
||||
Routes =/= [].
|
||||
|
||||
|
||||
load_host_cert(Host, PemData) ->
|
||||
File = cert_filename(Host),
|
||||
ok = file:write_file(File, PemData),
|
||||
configure_host_cert(Host, File).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: init(Args) -> {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
Backend = mnesia, %%+++ TODO: allow to configure this in ejabberd.cfg
|
||||
configure_static_hosts(),
|
||||
get_clusterid(), %% this is to report an error if the option wasn't configured
|
||||
ejabberd_commands:register_commands(commands()),
|
||||
%% Wait up to 120 seconds for odbc to start
|
||||
{ok, #state{state=wait_odbc,backend=Backend,odbc_wait_time=?ODBC_STARTUP_TIME}, timer:seconds(1)}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
%% {reply, Reply, State, Timeout} |
|
||||
%% {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, Reply, State} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
%% Wait for odbc to start.
|
||||
handle_info(timeout, State = #state{state=wait_odbc,backend=Backend,odbc_wait_time=N}) when N > 0 ->
|
||||
case (Backend /= odbc) orelse ejabberd_odbc:running(?MYNAME) of
|
||||
true ->
|
||||
?DEBUG("ejabberd_hosts: odbc now running.",[]),
|
||||
|
||||
%% The table 'hosts' is defined in gen_storage as being for the "localhost" vhost
|
||||
Host = ?MYNAME,
|
||||
HostB = list_to_binary(Host),
|
||||
gen_storage:create_table(Backend, HostB, hosts,
|
||||
[{disc_only_copies, [node()]},
|
||||
{odbc_host, Host},
|
||||
{attributes, record_info(fields, hosts)},
|
||||
{types, [{host, text},
|
||||
{clusterid, int},
|
||||
{config, text}]}]),
|
||||
|
||||
%% Now let's add the default vhost: "localhost"
|
||||
gen_storage:dirty_write(HostB, #hosts{host = Host,
|
||||
clusterid = 1,
|
||||
config = ""}),
|
||||
|
||||
self() ! reload,
|
||||
timer:send_interval(?RELOAD_INTERVAL, reload),
|
||||
{noreply, State#state{state=running,odbc_wait_time=0}};
|
||||
false ->
|
||||
{noreply,State#state{odbc_wait_time=N-1},timer:seconds(1)}
|
||||
end;
|
||||
handle_info(timeout, State=#state{state=running}) ->
|
||||
?WARNING_MSG("Spurious timeout message when odbc is already running.", []),
|
||||
{noreply, State};
|
||||
|
||||
handle_info(reload, State = #state{state=running}) ->
|
||||
try reload_hosts()
|
||||
catch
|
||||
Class:Error ->
|
||||
StackTrace = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("~p while synchonising running vhosts with database: ~p~n~p", [Class, Error, StackTrace])
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info(reload, State = #state{state=wait_odbc}) ->
|
||||
?ERROR_MSG("Tried to reload vhosts while waiting for odbc startup.", []),
|
||||
handle_info(timeout, State);
|
||||
|
||||
handle_info({reload, Host}, State = #state{state=running}) ->
|
||||
try reload_host(Host)
|
||||
catch
|
||||
Class:Error ->
|
||||
StackTrace = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("~p while synchonising running ~p with database: ~p~n~p", [Host, Class, Error, StackTrace])
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info({reload, Host}, State = #state{state=wait_odbc}) ->
|
||||
?ERROR_MSG("Tried to reload ~p while waiting for odbc startup.", [Host]),
|
||||
handle_info(timeout, State);
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
%% Description: This function is called by a gen_server when it is about to
|
||||
%% terminate. It should be the opposite of Module:init/1 and do any necessary
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ejabberd_commands:unregister_commands(commands()),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
reload_hosts() ->
|
||||
reload_hosts(get_hosts(odbc)).
|
||||
|
||||
reload_hosts(NewHosts) ->
|
||||
{AddedHosts,RemovedHosts} = diff_hosts(NewHosts),
|
||||
%% Avoid removing permanent hosts (staticly configured hosts)
|
||||
DeletedHosts = [H || H <- RemovedHosts,
|
||||
ejabberd_config:get_host_option(H, permanent) =/= true],
|
||||
AddHostConfig = lists:map(fun (Host) ->
|
||||
{Host, get_host_config(odbc,Host)}
|
||||
end, AddedHosts),
|
||||
update_config(AddHostConfig,DeletedHosts),
|
||||
RemovedNotDelete = RemovedHosts -- DeletedHosts,
|
||||
ejabberd_config:add_global_option(hosts, NewHosts++RemovedNotDelete), % overwrite hosts list
|
||||
stop_hosts(DeletedHosts),
|
||||
start_hosts(AddedHosts),
|
||||
ejabberd_local:refresh_iq_handlers(),
|
||||
{DeletedHosts, AddedHosts}.
|
||||
|
||||
%% updates the configuration of an existing virtual host
|
||||
reload_host(Host) ->
|
||||
Config = get_host_config(odbc,Host),
|
||||
F = fun() ->
|
||||
mnesia:write_lock_table(local_config),
|
||||
ejabberd_config:configure_host(Host, Config),
|
||||
ok
|
||||
end,
|
||||
{atomic, ok} = mnesia:transaction(F),
|
||||
% restart host
|
||||
stop_host(Host),
|
||||
start_host(Host),
|
||||
ejabberd_local:refresh_iq_handlers(),
|
||||
ok.
|
||||
|
||||
%% Apply the vhost changes (new, removed vhosts, configuration) to mnesia
|
||||
update_config(AddHostConfig, RemoveHosts) ->
|
||||
F = fun() ->
|
||||
mnesia:write_lock_table(local_config),
|
||||
lists:foreach(fun configure_new_host/1, AddHostConfig),
|
||||
lists:foreach(fun delete_host_config/1, RemoveHosts),
|
||||
ok
|
||||
end,
|
||||
{atomic, ok} = mnesia:transaction(F).
|
||||
|
||||
%% Write the configuration data for a new vhost to mnesia (currently only modules)
|
||||
configure_new_host({H, Config}) when is_list(Config) ->
|
||||
reconfigure_host_cert(H),
|
||||
ejabberd_config:configure_host(H, Config).
|
||||
|
||||
%% Delete the mnesia config data for a vhost.
|
||||
%% Needs to mirror any data we insert in configure_new_host (currently only modules)
|
||||
delete_host_config(Host) ->
|
||||
ejabberd_config:delete_host(Host).
|
||||
|
||||
%% Startup a list of vhosts
|
||||
start_hosts([]) -> ok;
|
||||
start_hosts(AddHosts) when is_list(AddHosts) ->
|
||||
?DEBUG("ejabberd_hosts adding hosts: ~P", [AddHosts, 10]),
|
||||
lists:foreach(fun start_host/1, AddHosts).
|
||||
|
||||
%% Start a single vhost (route, modules)
|
||||
start_host(Host) when is_list(Host) ->
|
||||
?DEBUG("Starting host ~p", [Host]),
|
||||
ejabberd_router:register_route(Host, {apply, ejabberd_local, route}),
|
||||
case ejabberd_config:get_local_option({modules, Host}) of
|
||||
undefined -> ok;
|
||||
Modules when is_list(Modules) ->
|
||||
lists:foreach(
|
||||
fun({Module, Args}) ->
|
||||
gen_mod:start_module(Host, Module, Args)
|
||||
end, Modules)
|
||||
end,
|
||||
case auth_method(Host) of
|
||||
{host_method, HostMethod} ->
|
||||
ejabberd_auth:start_method(Host, HostMethod)
|
||||
end,
|
||||
ok.
|
||||
|
||||
|
||||
%% Shut down a list of vhosts.
|
||||
stop_hosts([]) -> ok;
|
||||
stop_hosts(RemoveHosts) when is_list(RemoveHosts)->
|
||||
?DEBUG("ejabberd_hosts removing hosts: ~p", [RemoveHosts]),
|
||||
lists:foreach(fun stop_host/1, RemoveHosts).
|
||||
|
||||
%% Shut down a single vhost. (Routes, modules)
|
||||
stop_host(Host) when is_list(Host) ->
|
||||
?DEBUG("Stopping host ~p", [Host]),
|
||||
ejabberd_router:force_unregister_route(list_to_binary(Host)),
|
||||
lists:foreach(fun(Module) ->
|
||||
gen_mod:stop_module_keep_config(Host, Module)
|
||||
end, gen_mod:loaded_modules(Host)),
|
||||
case auth_method(Host) of
|
||||
{host_method, Method} ->
|
||||
ejabberd_auth:stop_method(Host, Method)
|
||||
end.
|
||||
|
||||
%% Get the current vhost list from a variety of sources (ODBC, internal)
|
||||
get_hosts(ejabberd) -> ?MYHOSTS;
|
||||
get_hosts(odbc) ->
|
||||
ClusterID = get_clusterid(),
|
||||
Hosts = gen_storage:dirty_select(?HOSTS_HOST, hosts, [{'=', clusterid, ClusterID}]),
|
||||
lists:map(fun (#hosts{host = Host}) ->
|
||||
exmpp_stringprep:nameprep(Host)
|
||||
end, Hosts).
|
||||
|
||||
%% Retreive the text format config for host Host from ODBC and covert
|
||||
%% it into a {host, Host, Config} tuple.
|
||||
get_host_config(odbc, Host) ->
|
||||
case gen_storage:dirty_read(?HOSTS_HOST, hosts, Host) of
|
||||
[] ->
|
||||
erlang:error({no_such_host, Host});
|
||||
[H] ->
|
||||
config_from_string(Host, H#hosts.config);
|
||||
E ->
|
||||
erlang:error({host_config_error, E})
|
||||
end.
|
||||
|
||||
%% Convert a plaintext string into a host config tuple.
|
||||
config_from_string(_Host, "") -> [];
|
||||
config_from_string(_Host, Config) ->
|
||||
{ok, Tokens, _} = erl_scan:string(Config),
|
||||
case erl_parse:parse_term(Tokens) of
|
||||
{ok, List} when is_list(List) ->
|
||||
List;
|
||||
E ->
|
||||
erlang:error({bad_host_config, Config, E})
|
||||
end.
|
||||
|
||||
diff_hosts() ->
|
||||
diff_hosts(get_hosts(odbc)).
|
||||
|
||||
diff_hosts(NewHosts) ->
|
||||
diff_hosts(NewHosts, get_hosts(ejabberd)).
|
||||
|
||||
%% Given the new list of vhosts and the old list, return the list of
|
||||
%% hosts added since last time and the list of hosts that have been
|
||||
%% removed.
|
||||
diff_hosts(NewHosts, OldHosts) ->
|
||||
RemoveHosts = OldHosts -- NewHosts,
|
||||
AddHosts = NewHosts -- OldHosts,
|
||||
{AddHosts,RemoveHosts}.
|
||||
|
||||
%% XXX - this should be part of auth, not hosts.
|
||||
auth_method(Host) ->
|
||||
case ejabberd_config:get_host_option(Host, auth_method) of
|
||||
undefined ->
|
||||
[Default] = ejabberd_config:get_host_option(global, auth_method),
|
||||
{host_method, Default};
|
||||
Other ->
|
||||
{host_method, Other}
|
||||
end.
|
||||
|
||||
configure_static_hosts() ->
|
||||
?DEBUG("Node startup - configuring hosts: ~p", [?MYHOSTS]),
|
||||
%% Add a null configuration for all MYHOSTS - this ensures
|
||||
%% the 'I'm a host' term gets written to the config table.
|
||||
%% We don't need any configuration options because these are
|
||||
%% statically configured hosts already configured by ejabberd_config.
|
||||
F = fun () ->
|
||||
lists:foreach(fun (H) -> ejabberd_config:configure_host(H, [{permanent, true}]) end,
|
||||
?MYHOSTS)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
cert_filename(Host) ->
|
||||
Dir = ejabberd_config:get_local_option({domain_certdir, global}),
|
||||
filename:join(Dir, Host ++ ".pem").
|
||||
|
||||
configure_host_cert(Host, File) ->
|
||||
ejabberd_config:add_local_option({domain_certfile, Host}, File),
|
||||
ok.
|
||||
|
||||
reconfigure_host_cert(Host) ->
|
||||
File = cert_filename(Host),
|
||||
case ejabberd_config:is_file_readable(File) of
|
||||
true ->
|
||||
ejabberd_config:mne_add_local_option({domain_certfile, Host}, File),
|
||||
ok;
|
||||
false ->
|
||||
no_cert
|
||||
end.
|
||||
|
||||
get_clusterid() ->
|
||||
case ejabberd_config:get_local_option(clusterid) of
|
||||
ID when is_integer(ID) ->
|
||||
ID;
|
||||
undefined ->
|
||||
?ERROR_MSG("Please add to your ejabberd.cfg the line: ~n {clusterid, 1}.", []),
|
||||
1;
|
||||
Other ->
|
||||
?ERROR_MSG("Change your misconfigured {clusterid, ~p} to the value: ~p", [Other, 1]),
|
||||
1
|
||||
end.
|
||||
|
||||
commands() ->
|
||||
[
|
||||
%% The commands status, stop and restart are implemented also in ejabberd_ctl
|
||||
%% They are defined here so that other interfaces can use them too
|
||||
#ejabberd_commands{name = host_list, tags = [hosts],
|
||||
desc = "Get a list of registered virtual hosts",
|
||||
module = ?MODULE, function = registered,
|
||||
args = [],
|
||||
result = {hosts, {list, {host, string}}}},
|
||||
#ejabberd_commands{name = host_register, tags = [hosts],
|
||||
desc = "Register and start a virtual host",
|
||||
module = ?MODULE, function = register,
|
||||
args = [{host, string}], result = {res, rescode}},
|
||||
#ejabberd_commands{name = host_remove, tags = [hosts],
|
||||
desc = "Stop and remove a virtual host",
|
||||
module = ?MODULE, function = remove,
|
||||
args = [{host, string}], result = {res, rescode}}
|
||||
].
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 16 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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,10 +31,12 @@
|
||||
init/3,
|
||||
start_listeners/0,
|
||||
start_listener/3,
|
||||
stop_listeners/0,
|
||||
stop_listener/2,
|
||||
parse_listener_portip/2,
|
||||
add_listener/3,
|
||||
delete_listener/2
|
||||
delete_listener/2,
|
||||
rate_limit/2
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -47,8 +49,42 @@ start_link() ->
|
||||
|
||||
|
||||
init(_) ->
|
||||
ets:new(listen_sockets, [named_table, public]),
|
||||
bind_tcp_ports(),
|
||||
{ok, {{one_for_one, 10, 1}, []}}.
|
||||
|
||||
bind_tcp_ports() ->
|
||||
case ejabberd_config:get_local_option(listen) of
|
||||
undefined ->
|
||||
ignore;
|
||||
Ls ->
|
||||
lists:foreach(
|
||||
fun({Port, Module, Opts}) ->
|
||||
ModuleRaw = strip_frontend(Module),
|
||||
case ModuleRaw:socket_type() of
|
||||
independent -> ok;
|
||||
_ ->
|
||||
bind_tcp_port(Port, Module, Opts)
|
||||
end
|
||||
end, Ls)
|
||||
end.
|
||||
|
||||
bind_tcp_port(PortIP, Module, RawOpts) ->
|
||||
try check_listener_options(RawOpts) of
|
||||
ok ->
|
||||
{Port, IPT, IPS, IPV, Proto, OptsClean} = parse_listener_portip(PortIP, RawOpts),
|
||||
{_Opts, SockOpts} = prepare_opts(IPT, IPV, OptsClean),
|
||||
case Proto of
|
||||
udp -> ok;
|
||||
_ ->
|
||||
ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS),
|
||||
ets:insert(listen_sockets, {PortIP, ListenSocket})
|
||||
end
|
||||
catch
|
||||
throw:{error, Error} ->
|
||||
?ERROR_MSG(Error, [])
|
||||
end.
|
||||
|
||||
start_listeners() ->
|
||||
case ejabberd_config:get_local_option(listen) of
|
||||
undefined ->
|
||||
@@ -98,15 +134,7 @@ start_dependent(Port, Module, Opts) ->
|
||||
|
||||
init(PortIP, Module, RawOpts) ->
|
||||
{Port, IPT, IPS, IPV, Proto, OptsClean} = parse_listener_portip(PortIP, RawOpts),
|
||||
%% The first inet|inet6 and the last {ip, _} work,
|
||||
%% so overriding those in Opts
|
||||
Opts = [IPV | OptsClean] ++ [{ip, IPT}],
|
||||
SockOpts = lists:filter(fun({ip, _}) -> true;
|
||||
(inet6) -> true;
|
||||
(inet) -> true;
|
||||
({backlog, _}) -> true;
|
||||
(_) -> false
|
||||
end, Opts),
|
||||
{Opts, SockOpts} = prepare_opts(IPT, IPV, OptsClean),
|
||||
if Proto == udp ->
|
||||
init_udp(PortIP, Module, Opts, SockOpts, Port, IPS);
|
||||
true ->
|
||||
@@ -127,26 +155,39 @@ init_udp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
||||
end.
|
||||
|
||||
init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
||||
SockOpts2 = case erlang:system_info(otp_release) >= "R13B" of
|
||||
true -> [{send_timeout_close, true} | SockOpts];
|
||||
false -> []
|
||||
end,
|
||||
Res = gen_tcp:listen(Port, [binary,
|
||||
{packet, 0},
|
||||
{active, false},
|
||||
{reuseaddr, true},
|
||||
{nodelay, true},
|
||||
{send_timeout, ?TCP_SEND_TIMEOUT},
|
||||
{keepalive, true} |
|
||||
SockOpts2]),
|
||||
case Res of
|
||||
{ok, ListenSocket} ->
|
||||
%% Inform my parent that this port was opened succesfully
|
||||
proc_lib:init_ack({ok, self()}),
|
||||
%% And now start accepting connection attempts
|
||||
accept(ListenSocket, Module, Opts);
|
||||
{error, Reason} ->
|
||||
socket_error(Reason, PortIP, Module, SockOpts, Port, IPS)
|
||||
ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS),
|
||||
%% Inform my parent that this port was opened succesfully
|
||||
proc_lib:init_ack({ok, self()}),
|
||||
%% And now start accepting connection attempts
|
||||
accept(ListenSocket, Module, Opts).
|
||||
|
||||
listen_tcp(PortIP, Module, SockOpts, Port, IPS) ->
|
||||
case ets:lookup(listen_sockets, PortIP) of
|
||||
[{PortIP, ListenSocket}] ->
|
||||
?INFO_MSG("Reusing listening port for ~p", [Port]),
|
||||
ets:delete(listen_sockets, Port),
|
||||
ListenSocket;
|
||||
_ ->
|
||||
SockOpts2 = try erlang:system_info(otp_release) >= "R13B" of
|
||||
true -> [{send_timeout_close, true} | SockOpts];
|
||||
false -> SockOpts
|
||||
catch
|
||||
_:_ -> []
|
||||
end,
|
||||
Res = gen_tcp:listen(Port, [binary,
|
||||
{packet, 0},
|
||||
{active, false},
|
||||
{reuseaddr, true},
|
||||
{nodelay, true},
|
||||
{send_timeout, ?TCP_SEND_TIMEOUT},
|
||||
{keepalive, true} |
|
||||
SockOpts2]),
|
||||
case Res of
|
||||
{ok, ListenSocket} ->
|
||||
ListenSocket;
|
||||
{error, Reason} ->
|
||||
socket_error(Reason, PortIP, Module, SockOpts, Port, IPS)
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (PortIP, Opts) -> {Port, IPT, IPS, IPV, OptsClean}
|
||||
@@ -191,6 +232,18 @@ parse_listener_portip(PortIP, Opts) ->
|
||||
end,
|
||||
{Port, IPT, IPS, IPV, Proto, OptsClean}.
|
||||
|
||||
prepare_opts(IPT, IPV, OptsClean) ->
|
||||
%% The first inet|inet6 and the last {ip, _} work,
|
||||
%% so overriding those in Opts
|
||||
Opts = [IPV | OptsClean] ++ [{ip, IPT}],
|
||||
SockOpts = lists:filter(fun({ip, _}) -> true;
|
||||
(inet6) -> true;
|
||||
(inet) -> true;
|
||||
({backlog, _}) -> true;
|
||||
(_) -> false
|
||||
end, Opts),
|
||||
{Opts, SockOpts}.
|
||||
|
||||
add_proto(Port, Opts) when is_integer(Port) ->
|
||||
{Port, get_proto(Opts)};
|
||||
add_proto({Port, Proto}, _Opts) when is_atom(Proto) ->
|
||||
@@ -222,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
|
||||
@@ -236,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} ->
|
||||
?INFO_MSG("(~w) Failed TCP accept: ~w",
|
||||
[ListenSocket, Reason]),
|
||||
accept(ListenSocket, Module, Opts)
|
||||
accept(ListenSocket, Module, Opts, NewInterval)
|
||||
end.
|
||||
|
||||
udp_recv(Socket, Module, Opts) ->
|
||||
@@ -304,6 +360,14 @@ start_listener_sup(Port, Module, Opts) ->
|
||||
[?MODULE]},
|
||||
supervisor:start_child(ejabberd_listeners, ChildSpec).
|
||||
|
||||
stop_listeners() ->
|
||||
Ports = ejabberd_config:get_local_option(listen),
|
||||
lists:foreach(
|
||||
fun({PortIpNetp, Module, _Opts}) ->
|
||||
delete_listener(PortIpNetp, Module)
|
||||
end,
|
||||
Ports).
|
||||
|
||||
%% @spec (PortIP, Module) -> ok
|
||||
%% where
|
||||
%% PortIP = {Port, IPT | IPS}
|
||||
@@ -460,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.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%%% Created : 30 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-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
|
||||
@@ -34,10 +34,12 @@
|
||||
|
||||
-export([route/3,
|
||||
route_iq/4,
|
||||
route_iq/5,
|
||||
process_iq_reply/3,
|
||||
register_iq_handler/4,
|
||||
register_iq_handler/5,
|
||||
register_iq_response_handler/4,
|
||||
register_iq_response_handler/5,
|
||||
unregister_iq_handler/2,
|
||||
unregister_iq_response_handler/2,
|
||||
refresh_iq_handlers/0,
|
||||
@@ -48,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, {}).
|
||||
|
||||
@@ -60,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
|
||||
%%====================================================================
|
||||
@@ -71,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.
|
||||
@@ -113,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} ->
|
||||
@@ -123,23 +148,35 @@ route(From, To, Packet) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
route_iq(From, To, #iq{type = Type} = IQ, F) when is_function(F) ->
|
||||
route_iq(From, To, IQ, F) ->
|
||||
route_iq(From, To, IQ, F, undefined).
|
||||
|
||||
route_iq(From, To, #iq{type = Type} = IQ, F, Timeout) when is_function(F) ->
|
||||
Packet = if Type == set; Type == get ->
|
||||
ID = randoms:get_string(),
|
||||
Host = From#jid.lserver,
|
||||
register_iq_response_handler(Host, ID, undefined, F),
|
||||
jlib:iq_to_xml(IQ#iq{id = ID});
|
||||
ID = list_to_binary(ejabberd_router:make_id()),
|
||||
Host = exmpp_jid:prep_domain(From),
|
||||
register_iq_response_handler(Host, ID, undefined, F, Timeout),
|
||||
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).
|
||||
|
||||
register_iq_response_handler(_Host, ID, Module, Function) ->
|
||||
TRef = erlang:start_timer(?IQ_TIMEOUT, ejabberd_local, ID),
|
||||
mnesia:dirty_write(#iq_response{id = ID,
|
||||
module = Module,
|
||||
function = Function,
|
||||
timer = TRef}).
|
||||
register_iq_response_handler(Host, ID, Module, Function) ->
|
||||
register_iq_response_handler(Host, ID, Module, Function, undefined).
|
||||
|
||||
register_iq_response_handler(_Host, ID, Module, Function, Timeout0) ->
|
||||
Timeout = case Timeout0 of
|
||||
undefined ->
|
||||
?IQ_TIMEOUT;
|
||||
N when is_integer(N), N > 0 ->
|
||||
N
|
||||
end,
|
||||
TRef = erlang:start_timer(Timeout, ejabberd_local, ID),
|
||||
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}.
|
||||
@@ -158,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.
|
||||
|
||||
@@ -176,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{}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -216,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} ->
|
||||
@@ -226,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) ->
|
||||
@@ -257,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}.
|
||||
@@ -285,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-2009 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
|
||||
@@ -151,6 +151,19 @@ write_event(Fd, {Time, {info_msg, _GL, {Pid, Format, Args}}}) ->
|
||||
F = add_node("ERROR: ~p - ~p~n", Pid),
|
||||
file:write(Fd, io_lib:format(T ++ F, [Format,Args]))
|
||||
end;
|
||||
write_event(Fd, {Time, {warning_report, _GL, {Pid, std_warning, Rep}}}) ->
|
||||
T = write_time(Time, "WARNING REPORT"),
|
||||
S = format_report(Rep),
|
||||
file:write(Fd, io_lib:format(T ++ S ++ add_node("", Pid), []));
|
||||
write_event(Fd, {Time, {warning_msg, _GL, {Pid, Format, Args}}}) ->
|
||||
T = write_time(Time, "WARNING REPORT"),
|
||||
case catch io_lib:format(add_node(Format,Pid), Args) of
|
||||
S when is_list(S) ->
|
||||
file:write(Fd, io_lib:format(T ++ S, []));
|
||||
_ ->
|
||||
F = add_node("ERROR: ~p - ~p~n", Pid),
|
||||
file:write(Fd, io_lib:format(T ++ F, [Format,Args]))
|
||||
end;
|
||||
write_event(_, _) ->
|
||||
ok.
|
||||
|
||||
|
||||