Compare commits
706 Commits
Author | SHA1 | Date |
---|---|---|
Jeff Forcier | e811e71583 | |
Jeff Forcier | 951faed80b | |
Jeff Forcier | 6d00c591cd | |
Jeff Forcier | fc99384fc5 | |
Jeff Forcier | ccb01f1981 | |
Jeff Forcier | 5ec984960b | |
Jeff Forcier | 7484a22180 | |
Jeff Forcier | c7f67445f7 | |
Jeff Forcier | 09e9d48db0 | |
Jeff Forcier | ec2460fb3c | |
Jeff Forcier | c0667e1e6a | |
Jeff Forcier | 54613b6885 | |
Jeff Forcier | c1434dbd24 | |
Jeff Forcier | 5837b7c21a | |
Jeff Forcier | c7c1a24e30 | |
Jeff Forcier | 6f4c159b05 | |
Jeff Forcier | cc3f860fc0 | |
Jeff Forcier | 74c612e328 | |
Jeff Forcier | 337f4432d3 | |
Jeff Forcier | 8411db5b7c | |
Jeff Forcier | 24309559bb | |
Jeff Forcier | a1c2a9829a | |
Jeff Forcier | 4fe3ad2220 | |
Jeff Forcier | 7a709498dc | |
Jeff Forcier | 7e84566772 | |
Jeff Forcier | e6f9f03b08 | |
Jeff Forcier | 1321bc41df | |
Jeff Forcier | ae30b31721 | |
Jeff Forcier | ac4c754718 | |
Jeff Forcier | a759a8e8df | |
Jeff Forcier | 5636381591 | |
Antoine Brenner | 3fce8abf68 | |
Jeff Forcier | 417102dbea | |
Jeff Forcier | 4947a726c1 | |
Jeff Forcier | a1d291e047 | |
Jeff Forcier | 9b2388cad5 | |
Jeff Forcier | 30f6f98afd | |
Jeff Forcier | 1b796861aa | |
Jeff Forcier | ba017e9e6c | |
Jeff Forcier | a0b2ae293f | |
Jeff Forcier | 6e9abc39cf | |
Jeff Forcier | c14de1d935 | |
Jeff Forcier | e05276b6ab | |
Jeff Forcier | 6dee34648e | |
Chris Rose | 34d03ae3dc | |
Jeff Forcier | d02ae56601 | |
Jeff Forcier | e96e2653a2 | |
Alex Gaynor | 91ab5f0c75 | |
Jeff Forcier | 9e2e981224 | |
Alex Gaynor | b0876fa013 | |
Alex Gaynor | 191fd465f1 | |
Jeff Forcier | fa86d655dc | |
Jeff Forcier | 1e0e296b05 | |
Jeff Forcier | 59a696cef2 | |
Jeff Forcier | 160e2c08e0 | |
Jeff Forcier | be7c679942 | |
Chris Rose | ed4f077b81 | |
Jeff Forcier | c8cc53940c | |
Jeff Forcier | d31373f0ef | |
Jeff Forcier | 57e647341f | |
Jeff Forcier | 8b9e60f4ce | |
Jeff Forcier | 1103416d83 | |
Jeff Forcier | b85a09673a | |
Jeff Forcier | b81025e3d2 | |
Jeff Forcier | f22fe4b600 | |
Jeff Forcier | dd2e23a23e | |
Jeff Forcier | ab08ef6651 | |
Jeff Forcier | 4781f190cf | |
Jeff Forcier | 658d202cc7 | |
Jeff Forcier | 80aff93d3f | |
Jeff Forcier | cb6c4bec5d | |
Jeff Forcier | e65b627021 | |
Jeff Forcier | 6c4bea5673 | |
Jeff Forcier | 36bd5b2ffb | |
Jeff Forcier | e1d92087fa | |
Alex Gaynor | fded67e712 | |
Jeff Forcier | 619b24738a | |
Jeff Forcier | 9044860b41 | |
Jeff Forcier | 196b3cc110 | |
Jeff Forcier | 4eb7720fae | |
Jeff Forcier | c9aa83b63e | |
Alex Gaynor | 6c6969c188 | |
Alex Gaynor | 6f211115f4 | |
Alex Gaynor | 23528069ec | |
Alex Gaynor | 4d3e0b711a | |
Jeff Forcier | 5a430def22 | |
Jeff Forcier | bd8f96d33a | |
Alex Gaynor | 783b2d7683 | |
Jeff Forcier | ac2075a820 | |
Jeff Forcier | a495da760f | |
Jeff Forcier | 87b57dc0cd | |
Jeff Forcier | 1fa5f8239d | |
Jeff Forcier | 0857a817ff | |
Jeff Forcier | 8922bbe7d3 | |
Jeff Forcier | ea88fad0f1 | |
Jeff Forcier | 4d76a90985 | |
Jeff Forcier | bd81c94825 | |
Jeff Forcier | 341a666351 | |
Jeff Forcier | 57c2ffbbd6 | |
Alex Gaynor | bbb8e8ca4a | |
Jeff Forcier | 7a3eedcee2 | |
Jeff Forcier | 50598d35a1 | |
Jeff Forcier | d4e98d8d77 | |
Jeff Forcier | 270dacaf33 | |
Jeff Forcier | 3003eaacd8 | |
Jeff Forcier | b0ef41c05d | |
Jeff Forcier | ff86ec3492 | |
Jeff Forcier | c5fff0c399 | |
Alex Gaynor | 61e32319fc | |
Jeff Forcier | a8110d8006 | |
Jeff Forcier | a7a0fca1f4 | |
Jeff Forcier | cadb44e79c | |
Jeff Forcier | fb6047df36 | |
Jeff Forcier | 13cadb2a1f | |
Jeff Forcier | 4cc9d9f562 | |
Jeff Forcier | 7feeb272a0 | |
Jeff Forcier | 6fac5df535 | |
Jeff Forcier | a183cd3bb1 | |
Jeff Forcier | 6875bfd795 | |
Jeff Forcier | 3780c0314c | |
Jeff Forcier | 2c0544fc35 | |
Jeff Forcier | e058c6bae0 | |
Jeff Forcier | 5c4dceae39 | |
Jeff Forcier | afff987925 | |
Jeff Forcier | 4ba5ae6135 | |
Jeff Forcier | 2af6081eaf | |
Jeff Forcier | a6b38ff5d2 | |
Jeff Forcier | a749c83aa8 | |
Jeff Forcier | 695527c9ee | |
Jeff Forcier | 233f53bb37 | |
Jeff Forcier | ebc1f38611 | |
Jeff Forcier | fe9171661a | |
Jeff Forcier | a8ad23e61a | |
Jeff Forcier | 9ad2dec32a | |
Jeff Forcier | e032541b78 | |
Jeff Forcier | 9695500303 | |
Jeff Forcier | b94af130b5 | |
Jeff Forcier | 005cfb8b25 | |
Jeff Forcier | 4e9efe4830 | |
Jeff Forcier | 1774a4b9c0 | |
Jeff Forcier | bbd9469d65 | |
Jeff Forcier | edf958b74b | |
Jeff Forcier | c538a00012 | |
Jeff Forcier | 4fdb4b5ae5 | |
Jeff Forcier | bd532e4a69 | |
Jeff Forcier | 0424f2c4c9 | |
Jeff Forcier | d99342d119 | |
Jeff Forcier | 4742f4c1c1 | |
Jeff Forcier | b0b6a827b9 | |
Jeff Forcier | a4645b0c9c | |
Jeff Forcier | ba55eea38d | |
Jeff Forcier | 120071283d | |
Jeff Forcier | 8463053fa5 | |
Scott Maxwell | 386384a498 | |
Scott Maxwell | f0017b8330 | |
Jeff Forcier | 073c71a822 | |
Jeff Forcier | 71e8ddef2b | |
Jeff Forcier | b4cd4bea1d | |
Jeff Forcier | b29d018e36 | |
Jeff Forcier | 1b5332ead1 | |
Jeff Forcier | 85e9405f8a | |
Jeff Forcier | c646e72832 | |
Jeff Forcier | 3579f76dc7 | |
Jeff Forcier | 4c601eb95c | |
Jeff Forcier | 055c4a0fe1 | |
Jeff Forcier | 7688c7aaaf | |
Jeff Forcier | 8f8b0cde91 | |
Jeff Forcier | 1b22497a46 | |
Jeff Forcier | e902bd4f80 | |
Jeff Forcier | 5eac8e8b87 | |
Jeff Forcier | 8f49c15258 | |
Jeff Forcier | 0edffbb040 | |
Jeff Forcier | 77f3b07f12 | |
Jeff Forcier | 8d08cc926a | |
Jeff Forcier | a9434bc626 | |
Jeff Forcier | 7e8623f06f | |
Jeff Forcier | b2be63ec62 | |
Jeff Forcier | bd61c7c0a9 | |
Jeff Forcier | e694fa243c | |
Jeff Forcier | 148f3d2fd4 | |
Jeff Forcier | 6d00e03d75 | |
Jeff Forcier | feb78d8a3f | |
Jeff Forcier | 48ab72508a | |
Jeff Forcier | 7a776b1757 | |
Jeff Forcier | 32975af9a6 | |
Jeff Forcier | 38785d9cd2 | |
Jeff Forcier | d5ed558c21 | |
Jeff Forcier | 798e3689dd | |
Jeff Forcier | f9fef01e76 | |
Jeff Forcier | 247ef7f6f7 | |
Jeff Forcier | 8f22672926 | |
Jeff Forcier | f295840191 | |
Jeff Forcier | 7d2e068382 | |
Jeff Forcier | 440faef033 | |
Jeff Forcier | 9b83214b1e | |
Jeff Forcier | 4747abd6a1 | |
Jeff Forcier | 2900b67b5e | |
Jeff Forcier | 0c72e277df | |
Jeff Forcier | 873b6d4a95 | |
Jeff Forcier | be94ee690e | |
Jeff Forcier | b9cea90581 | |
Jeff Forcier | fac6cde874 | |
Jeff Forcier | 72a73f55fa | |
Jeff Forcier | c0fcd11ea0 | |
Jeff Forcier | 056323979d | |
Jeff Forcier | 1038cfe7dd | |
Jeff Forcier | 83f09e634f | |
Jeff Forcier | c05b065777 | |
Jeff Forcier | d97d28e4e2 | |
Jeff Forcier | dfcd904318 | |
Jeff Forcier | 5ee1fb4781 | |
Jeff Forcier | 9c3f2c2122 | |
Jeff Forcier | de99785ef0 | |
Jeff Forcier | dd9934f2b5 | |
Jeff Forcier | 3f34ea48db | |
Jeff Forcier | 8ddaac24ae | |
Jeff Forcier | 79a69e88c3 | |
Jeff Forcier | 01f365a3e1 | |
Jeff Forcier | f556c8f0ae | |
Jeff Forcier | 7df1ae9602 | |
Jeff Forcier | 33452b2e6c | |
Jeff Forcier | e293dff653 | |
Jeff Forcier | b8fbbb3d32 | |
Jeff Forcier | 6d71fbd9ef | |
Jeff Forcier | 0bf0f08ee4 | |
Jeff Forcier | a00316af15 | |
Jeff Forcier | e2ac82c47c | |
Jeff Forcier | e207a4f704 | |
Jeff Forcier | ad1fbcce0b | |
Jeff Forcier | 999bb4eaaf | |
Jeff Forcier | 6dcf67a9ad | |
Jeff Forcier | c8382442f5 | |
Jeff Forcier | 4fb748ccf8 | |
Jeff Forcier | 9ae46dcbfa | |
Jeff Forcier | b47b578e59 | |
Jeff Forcier | 4bcac18d17 | |
Jeff Forcier | 1556853713 | |
Jeff Forcier | f76485f766 | |
Jeff Forcier | 24cc59b64d | |
Jeff Forcier | 23bb7e5936 | |
Jeff Forcier | 2dc2643b68 | |
Jeff Forcier | 74af60803f | |
Jeff Forcier | c23579526b | |
Jeff Forcier | 9ae62eb47a | |
Jeff Forcier | 6d9b28c56c | |
Jeff Forcier | 333dd249b0 | |
Jeff Forcier | a529e93256 | |
Jeff Forcier | 91c47b1748 | |
Jeff Forcier | 0d08366612 | |
Jeff Forcier | 5681b8c25a | |
Jeff Forcier | 0b2d523665 | |
Jeff Forcier | 8c7eafdfcd | |
Jeff Forcier | 0e9a5a4b46 | |
Jeff Forcier | f836c98e5c | |
Jeff Forcier | f09b562fa8 | |
Jeff Forcier | 3f9270c0be | |
Jeff Forcier | f40bf59ff3 | |
Jeff Forcier | be4007fb89 | |
Jeff Forcier | eb332c781b | |
Jeff Forcier | d4148ab6f3 | |
Jeff Forcier | 635108aab2 | |
Jeff Forcier | 88140996a7 | |
Jeff Forcier | a58cf80139 | |
Jeff Forcier | 0af6df924a | |
Jeff Forcier | 22766e96a6 | |
Jeff Forcier | 6e4829c20f | |
Jeff Forcier | a627ecfb14 | |
Jeff Forcier | 09c0006a40 | |
Jeff Forcier | c8d97d78c4 | |
Jeff Forcier | 2943343665 | |
Jeff Forcier | ac63ed58a1 | |
Jeff Forcier | b9d25a30b6 | |
Jeff Forcier | da6d00dcb7 | |
Jeff Forcier | 960b3c038d | |
Jeff Forcier | 370af89791 | |
Jeff Forcier | 48adf5c646 | |
Jeff Forcier | 6d412b06a1 | |
Jeff Forcier | a67142b1ca | |
Jeff Forcier | ef9c0a655a | |
Jeff Forcier | 6ff6e14978 | |
Jeff Forcier | 34243a4aa2 | |
Jeff Forcier | f855937d10 | |
Jeff Forcier | 70218ff852 | |
Jeff Forcier | e0ff365388 | |
Jeff Forcier | 3b0df60b6a | |
Jeff Forcier | 95b5fd2255 | |
Jeff Forcier | 4a98671bf6 | |
Jeff Forcier | 2346f1a1e9 | |
Jeff Forcier | 6cdb8291b7 | |
Jeff Forcier | b140b29d54 | |
Jeff Forcier | a24ca77636 | |
Jeff Forcier | 0965eaa65d | |
Jeff Forcier | 4e9af2f7ca | |
Jeff Forcier | 30518280f1 | |
Jeff Forcier | 9d7aeff7b1 | |
Jeff Forcier | 3fcde4e7f4 | |
Jeff Forcier | fb786bea9c | |
Jeff Forcier | 33d412a2c6 | |
Jeff Forcier | 4a5f007c02 | |
Jeff Forcier | 7b595f4a8b | |
Jeff Forcier | 457a34f55b | |
Jeff Forcier | 6aada73b0e | |
Jeff Forcier | ddea189319 | |
Jeff Forcier | 6d4b37a17f | |
Jeff Forcier | 73382933a7 | |
Jeff Forcier | 758543f238 | |
Jeff Forcier | fb772a8695 | |
Jeff Forcier | e3a16fac5a | |
Jeff Forcier | d438ff6b64 | |
Jeff Forcier | 4402f67fa6 | |
Jeff Forcier | 244e09f57a | |
Jeff Forcier | 8003c738ca | |
Jeff Forcier | c3eb903ed3 | |
Jeff Forcier | 0c493541eb | |
Marius Gedminas | cfd1efe648 | |
Jeff Forcier | 58489c893e | |
Jeff Forcier | 5b6059c4bd | |
Jeff Forcier | 28d52444fe | |
Jeff Forcier | 27223637ce | |
Jeff Forcier | 769f6fea98 | |
Jeff Forcier | 40708571bc | |
Jeff Forcier | c73616b5b3 | |
Jeff Forcier | 725b96c0ae | |
Jeff Forcier | 3e87f4aa27 | |
Johan Prins | 985c3069fb | |
Jeff Forcier | 2690a90ccb | |
Jeff Forcier | a31dcf1913 | |
Jeff Forcier | 3a7b66a814 | |
Jeff Forcier | 41d7339992 | |
Jeff Forcier | c142454621 | |
Jeff Forcier | 36b937d436 | |
Jeff Forcier | 2d3b13e917 | |
Jeff Forcier | 675e30986e | |
Jeff Forcier | 1d87a0ceb9 | |
Jeff Forcier | 280c240685 | |
Jeff Forcier | aed86f26bf | |
Jeff Forcier | 9fa2cfee9d | |
Jeff Forcier | 1560c4ab8a | |
Jeff Forcier | 0a2887a8cc | |
Jeff Forcier | fe3d3beca4 | |
Jeff Forcier | 9b5a0c840d | |
Jeff Forcier | c7090c52de | |
Jeff Forcier | 36abefdac0 | |
Jeff Forcier | cdf62655cb | |
Jeff Forcier | 0f2fb26287 | |
Jeff Forcier | 04d1580962 | |
Jeff Forcier | 792cfe0cf6 | |
Jeff Forcier | 0a90bef4f4 | |
Jeff Forcier | 4aa9668653 | |
Jeff Forcier | 7b059ea55d | |
Jeff Forcier | 5aa0d401f6 | |
Jeff Forcier | 397cd4ce99 | |
Jeff Forcier | 55722240b5 | |
Jeff Forcier | 52183257d0 | |
Jeff Forcier | 7a17770913 | |
Jeff Forcier | 781617ab47 | |
Jeff Forcier | 9035546e40 | |
Jeff Forcier | c563f06954 | |
Jeff Forcier | 0d6bf1d965 | |
Jeff Forcier | c427667928 | |
Jeff Forcier | 343d0492a7 | |
Jeff Forcier | 70bcde95c1 | |
Jeff Forcier | 5ba3761f21 | |
Jeff Forcier | 14929d6909 | |
Jeff Forcier | 8cc9ae059f | |
Jeff Forcier | a83f0d3038 | |
Jeff Forcier | d0d711ac78 | |
Jeff Forcier | 176493823c | |
Jeff Forcier | a58ee1c89a | |
Jeff Forcier | 6646e36673 | |
Jeff Forcier | 34320dfd61 | |
Jeff Forcier | 2b64ff4cd9 | |
Jeff Forcier | c636b273da | |
Jeff Forcier | b8d1724f57 | |
Jeff Forcier | 2b0f834c16 | |
Jeff Forcier | b60075c7cd | |
Jeff Forcier | d1d65b4ddd | |
Jeff Forcier | fee04a39ec | |
Jeff Forcier | 414fec8f27 | |
Jeff Forcier | 7b690707dd | |
Jeff Forcier | da51cd8b14 | |
Jeff Forcier | 69d40710dc | |
Jeff Forcier | 87cd72c144 | |
Olle Lundberg | 24635609dc | |
Jeff Forcier | dde21a7de0 | |
Jeff Forcier | 03768eadca | |
Jeff Forcier | 5a1f927310 | |
Jeff Forcier | 2da9142520 | |
Jeff Forcier | c224fdecf1 | |
Jeff Forcier | ccb7a6c2cd | |
Jeff Forcier | 7d56ecb1a7 | |
Jeff Forcier | c74ff2a16e | |
Jeff Forcier | 94580cebee | |
Jeff Forcier | 935c55dd1f | |
Jeff Forcier | 473a9cdf5b | |
Olle Lundberg | 5c5bf6e844 | |
Jeff Forcier | 5bf9acb312 | |
Jeff Forcier | 2c7f45c293 | |
Jeff Forcier | c8ebe05ffc | |
Jeff Forcier | 2cf23bf784 | |
Jeff Forcier | a7ea04842e | |
Jeff Forcier | 2621db122d | |
Aarni Koskela | 39809dab31 | |
Aarni Koskela | b0c689d7c8 | |
Aarni Koskela | d32d457775 | |
Aarni Koskela | d8738b1b0f | |
Jeff Forcier | b5f00adbaa | |
Olle Lundberg | 00f84e328f | |
Scott Maxwell | ae078f51d6 | |
Scott Maxwell | b9e62182e5 | |
Scott Maxwell | aa8ea3c4d4 | |
Scott Maxwell | ab8d874064 | |
Jeff Forcier | a08ac06083 | |
Jeff Forcier | 6ecde066fc | |
Jeff Forcier | 69ec7455ee | |
Jeff Forcier | 1ff3db96f6 | |
Jeff Forcier | 9bf65daebe | |
Jeff Forcier | 698adf10fb | |
Jeff Forcier | 6393175f65 | |
Jeff Forcier | 566f37c0f2 | |
Jeff Forcier | 19fedd261e | |
Jeff Forcier | 96ca8d49c1 | |
Jeff Forcier | b352357efb | |
Jeff Forcier | b57e825f77 | |
Nathan Scowcroft | 0fea895cdb | |
Nathan Scowcroft | a1c1f8f29f | |
Jeff Forcier | 6d326fcde2 | |
Jeff Forcier | 74e06aff9e | |
Jeff Forcier | 78d9e4834c | |
Martin Blumenstingl | bfc3953be0 | |
Jeff Forcier | dd3e203e00 | |
Jeff Forcier | b3e53df8b9 | |
Jeff Forcier | ec95e8d943 | |
Jeff Forcier | 302e3bde38 | |
Jeff Forcier | bbcc6c7d8d | |
Jeff Forcier | c695a931ff | |
Jeff Forcier | 4a5e10e15c | |
Jeff Forcier | 7985d08138 | |
Jeff Forcier | b22de7d61b | |
Jeff Forcier | f253612e93 | |
Jeff Forcier | 89e2592ead | |
Jeff Forcier | 91a8066686 | |
Jeff Forcier | effdcd741a | |
Scott Maxwell | 676a30c298 | |
Scott Maxwell | a15d5ba25d | |
Scott Maxwell | aa301506f4 | |
Scott Maxwell | 103d056a77 | |
Scott Maxwell | 6d75c75e64 | |
Scott Maxwell | 981f768a62 | |
Scott Maxwell | dcc78768bf | |
Scott Maxwell | 106f9ea444 | |
Scott Maxwell | 2da5f1fb45 | |
Scott Maxwell | 7471515fff | |
Scott Maxwell | 25dd096da0 | |
Scott Maxwell | 3ce336c88b | |
Scott Maxwell | 01731fa2c3 | |
Scott Maxwell | 7d5fa50ca4 | |
Scott Maxwell | dc58b7bcb2 | |
Scott Maxwell | 7444a99993 | |
Scott Maxwell | 45e65b6e1e | |
Scott Maxwell | 7decda3297 | |
Scott Maxwell | 9662a7f779 | |
Scott Maxwell | 06b866cf40 | |
Scott Maxwell | 201a61d66d | |
Scott Maxwell | fee18142a5 | |
Scott Maxwell | 8a7267beeb | |
Scott Maxwell | d5ce2b43d6 | |
Scott Maxwell | 0677ea76cd | |
Scott Maxwell | 8e1a7ef4d8 | |
Scott Maxwell | d26bf3e63e | |
Scott Maxwell | 951e8cfd3a | |
Scott Maxwell | bc683ac365 | |
Scott Maxwell | 8bda3ab2bb | |
Scott Maxwell | 7a45d3c70f | |
Scott Maxwell | 09a4ffb282 | |
Scott Maxwell | 04097cbb26 | |
Scott Maxwell | 85ade33ae3 | |
Alex Gaynor | 68cf5ab063 | |
Scott Maxwell | 488d85f981 | |
Scott Maxwell | fcf56ff9f8 | |
Scott Maxwell | e4e1dc2002 | |
Scott Maxwell | 7cdbbf4bdc | |
Scott Maxwell | 0b7d0cf0a2 | |
Scott Maxwell | 2d738fa08b | |
Scott Maxwell | 0e4ce3762a | |
Scott Maxwell | 339d73cc13 | |
Scott Maxwell | 2ea352b8ba | |
Scott Maxwell | 6bd1e42b43 | |
Scott Maxwell | 644c52266c | |
Scott Maxwell | 66cfa97cce | |
Scott Maxwell | 3afc76a6b4 | |
Scott Maxwell | 3301473ae7 | |
Scott Maxwell | cd918d0dc6 | |
Scott Maxwell | 8edd2cc75e | |
Scott Maxwell | f73d5f73e5 | |
Scott Maxwell | e5822c9fa1 | |
Scott Maxwell | 7aaf42a7b1 | |
Jeff Forcier | a9a5f69c1a | |
Jeff Forcier | 75c4304fe2 | |
Jeff Forcier | 3232ce84ed | |
Jeff Forcier | 858d3fd07f | |
Mike Gabriel | 6b222528f3 | |
Jeff Forcier | c73764a947 | |
Jeff Forcier | 96fdefbcb9 | |
Jeff Forcier | f2466a3d46 | |
Jeff Forcier | 48c67e24e0 | |
Jeff Forcier | ceb39683dc | |
Jeff Forcier | f88875da3f | |
Jeff Forcier | 8fcccce3d5 | |
Jeff Forcier | 28d78e4e6a | |
Jeff Forcier | 07e61c3552 | |
Jeff Forcier | e25c7c4bdf | |
Jeff Forcier | 2de9c72720 | |
Jeff Forcier | 83f44878ea | |
Jeff Forcier | d4e18e1d1c | |
Jeff Forcier | c3befd18a4 | |
Jeff Forcier | 65271c65d2 | |
Jeff Forcier | 57046f44cb | |
Jeff Forcier | 1627ea6603 | |
Jeff Forcier | 8cdbcfa1ff | |
Jeff Forcier | e0d4fdbc5d | |
Jeff Forcier | 88b568b76d | |
Benjamin Pollack | 5e744d37c7 | |
Jeff Forcier | 3071811c70 | |
Jeff Forcier | 68cad65bb2 | |
Jeff Forcier | 1009b72ed0 | |
Jeff Forcier | be68ca1020 | |
Jeff Forcier | 152f126869 | |
Jeff Forcier | 18d48cc5f7 | |
Jeff Forcier | 91922eb80e | |
Jeff Forcier | e0b401b8d9 | |
Jeff Forcier | 8ab4cbd2a0 | |
Jeff Forcier | 6a6b200c32 | |
Jeff Forcier | e185178876 | |
Jeff Forcier | cba4c68365 | |
Jeff Forcier | 483d7e1462 | |
Jeff Forcier | 7243f8fe90 | |
Jeff Forcier | 8b983d9d3d | |
Jeff Forcier | 05abcc40f5 | |
Jeff Forcier | 2ef0ab9f6a | |
Jeff Forcier | f783d4d818 | |
Jeff Forcier | b9629f0fd6 | |
Jeff Forcier | bea7d9dc8a | |
Jeff Forcier | 723131500a | |
Jeff Forcier | 2de0784d4e | |
Jeff Forcier | 4020b87419 | |
Jeff Forcier | 0ba34035c3 | |
Jeff Forcier | 6eea2b5ce7 | |
Jeff Forcier | b3c0fb463a | |
Jeff Forcier | e2aa7c17b0 | |
Jeff Forcier | db9dfebca8 | |
Jeff Forcier | 02387fc88c | |
Jeff Forcier | fa61f3c398 | |
Jonathan Halcrow | 7ed1e2bccc | |
Jeff Forcier | ea27a8bd87 | |
Jeff Forcier | 081b04116b | |
Jeff Forcier | 0355672721 | |
Jeff Forcier | f7d74d03d9 | |
Jeff Forcier | 565eff8274 | |
Jeff Forcier | db083b6478 | |
Jeff Forcier | e6abab18ee | |
Jeff Forcier | e06b0f597e | |
Jeff Forcier | 2a08a48dd2 | |
Jeff Forcier | d2c71ed999 | |
Jeff Forcier | 965d00dee9 | |
Jeff Forcier | c8b75a489c | |
Jeff Forcier | caf94786ca | |
Jeff Forcier | ba3ce80c14 | |
Jeff Forcier | 993ecb31d2 | |
Jeff Forcier | 4ee577abc5 | |
Emre Yılmaz | 3399d519e0 | |
Jeff Forcier | d77a4d6421 | |
Jeff Forcier | 3c2f01c91f | |
Ethan Glasser-Camp | a733c428cd | |
Jeff Forcier | 777d1576ca | |
Jeff Forcier | d0c7a5d884 | |
Jeff Forcier | b33d9c7bca | |
Jeff Forcier | b2b8d5d0a6 | |
Jeff Forcier | 0b9393b063 | |
Jeff Forcier | f00f87c9be | |
Ethan Glasser-Camp | 8c7f120c2c | |
Jeff Forcier | 675d79d743 | |
Ethan Glasser-Camp | aee2355d24 | |
Jeff Forcier | a1fa1ba9cc | |
Jeff Forcier | 3966ac103c | |
Abhinav Upadhyay | e6c23f23f4 | |
Jeff Forcier | f861c2ff48 | |
Jeff Forcier | 6747d9944a | |
Mike Gabriel | 1b928df15e | |
Mike Gabriel | 81f87f1d5e | |
Mike Gabriel | 080bece258 | |
Mike Gabriel | 4f481a57a2 | |
Steven Noonan | 5c124cb136 | |
Frank Arnold | 068bf63cf0 | |
Ivan Barria | b96e7e4132 | |
Jeff Forcier | b329512636 | |
Jeff Forcier | 02d071be07 | |
Jeff Forcier | 1d494eb0db | |
Kevin Tegtmeier | 2e2a915807 | |
Jeff Forcier | 73a0d03bdc | |
Jeff Forcier | 17ba0d5b61 | |
Jeff Forcier | 2a774d1e8a | |
Jeff Forcier | 9695747875 | |
Jeff Forcier | 5c9aa3dcdc | |
Ethan Glasser-Camp | ebdbfae5b1 | |
Ethan Glasser-Camp | 632129c427 | |
Jeff Forcier | 0392e3df8f | |
Jeff Forcier | a7ee2509e4 | |
Jeff Forcier | d5db603297 | |
Jason R. Coombs | 0cc6bb970f | |
Jason R. Coombs | c305691492 | |
Jason R. Coombs | 9858ccf207 | |
Jason R. Coombs | abe009b149 | |
Jason R. Coombs | 3cd7f585d0 | |
Jason R. Coombs | a3fe422198 | |
Jason R. Coombs | 3a9119d78a | |
Jeff Forcier | 721f74d8c2 | |
Olle Lundberg | bd1a97a045 | |
Jeff Forcier | dd7edd8ec8 | |
Phillip Heller | edc9eaf4f2 | |
Jeff Forcier | 277526315e | |
Olle Lundberg | 1903ee1432 | |
Olle Lundberg | 732417bf98 | |
Olle Lundberg | 06f9704820 | |
Olle Lundberg | 93dce43e86 | |
Olle Lundberg | 38767982cd | |
Olle Lundberg | f41fc8fd28 | |
Olle Lundberg | 109d2b200a | |
Olle Lundberg | ea3c3f53b6 | |
Olle Lundberg | c79e6a3f92 | |
Olle Lundberg | 32424ba109 | |
Olle Lundberg | ac1310c4a1 | |
Olle Lundberg | b3d5156019 | |
Olle Lundberg | 42d77483e8 | |
Olle Lundberg | 85551dffd6 | |
Olle Lundberg | 57d776b318 | |
Olle Lundberg | 21689d9647 | |
Olle Lundberg | 98ae4e975d | |
Jeff Forcier | 3563fca994 | |
Jeff Forcier | b9242c654a | |
John Hensley | 9d2fb82284 | |
Jeff Forcier | bf4b535920 | |
Parantapa Bhattacharya | 2f1daad1b9 | |
Jeff Forcier | e034a24f87 | |
Jeff Forcier | 7e5911a1ff | |
Kent Gibson | adad068b13 | |
Jeff Forcier | 8e697988af | |
Jeff Forcier | a69abd4606 | |
Jeff Forcier | f493a00c11 | |
Jeff Forcier | ac9370d3e0 | |
Jeff Forcier | 37d0247301 | |
Jeff Forcier | e761502e8e | |
Jeff Forcier | 6b5d748358 | |
Jeff Forcier | 0d38f3f1f2 | |
Jeff Forcier | 0c56e2a40b | |
Jeff Forcier | 235050a67c | |
Michael Williamson | 08109136b4 | |
Michael Williamson | 0b6aebb8a9 | |
Michael Williamson | 602250fdf9 | |
Jeff Forcier | 21cb9a2d86 | |
Jason R. Coombs | 5f5137414c | |
Jason R. Coombs | 6c4c00a3f3 | |
Jason R. Coombs | ce86a53a37 | |
Jason R. Coombs | 7bde7840dd | |
Jason R. Coombs | 9f21d36040 | |
Maarten | 3bbcf808d8 | |
Michael van der Kolff | cd51bfc031 | |
Jeff Forcier | 0ae0e9800c | |
Jeff Forcier | 2cbe383080 | |
Eric Buehl | 9c0d467667 | |
Jeff Forcier | 70fce374b4 | |
Jeff Forcier | 531606b0d6 | |
Jeff Forcier | 2223aa10cc | |
Jeff Forcier | 2ae06c70af | |
Jeff Forcier | 962d4a3cec | |
Jeff Forcier | f6ed6a8bbf | |
Jeff Forcier | b9c39fc1d2 | |
Jeff Forcier | 2575b3efc4 | |
Jeff Forcier | d47e6b9e7f | |
Jeff Forcier | 2403504b44 | |
Jeff Forcier | 10c51e2726 | |
Jeff Forcier | c4d4818cdd | |
Jason R. Coombs | 13892788c3 | |
Jason R. Coombs | c0ef3fd493 | |
Jason R. Coombs | 0698254b18 | |
Jason R. Coombs | 64d6734086 | |
Olle Lundberg | a07a339006 | |
Olle Lundberg | 5670e111c9 | |
Olle Lundberg | 78654e82ec | |
Jeff Forcier | 71f8c5c9f5 | |
Lincoln de Sousa | 79dffacf4e | |
Jason R. Coombs | 45aa88b530 | |
Jason R. Coombs | e0d71b5efb | |
Tomer Filiba | 668870aa83 | |
Tomer Filiba | 23f3099b6f | |
James Hiscock | c78a5856e8 | |
Olle Lundberg | 221131fa21 | |
Olle Lundberg | 04cc4d5510 | |
Olle Lundberg | 7ce9875ed7 | |
Olle Lundberg | d66d75f277 | |
Olle Lundberg | b22c11ab1b | |
Olle Lundberg | 2dd74f953d | |
Olle Lundberg | ad587fa0ef | |
Olle Lundberg | f33481cc44 | |
Olle Lundberg | 3174b6c894 | |
Richard Kettlewell | 974294ad7d |
|
@ -1,6 +1,10 @@
|
|||
*.pyc
|
||||
build/
|
||||
dist/
|
||||
.tox/
|
||||
paramiko.egg-info/
|
||||
test.log
|
||||
docs/
|
||||
!sites/docs
|
||||
_build
|
||||
.coverage
|
||||
|
|
22
.travis.yml
22
.travis.yml
|
@ -1,14 +1,32 @@
|
|||
language: python
|
||||
python:
|
||||
- "2.5"
|
||||
- "2.6"
|
||||
- "2.7"
|
||||
- "3.2"
|
||||
- "3.3"
|
||||
install:
|
||||
# Self-install for setup.py-driven deps
|
||||
- pip install -e .
|
||||
script: python test.py
|
||||
# Dev (doc/test running) requirements
|
||||
- pip install coveralls # For coveralls.io specifically
|
||||
- pip install -r dev-requirements.txt
|
||||
script:
|
||||
# Main tests, with coverage!
|
||||
- invoke coverage
|
||||
# Ensure documentation & invoke pipeline run OK.
|
||||
# Run 'docs' first since its objects.inv is referred to by 'www'.
|
||||
# Also force warnings to be errors since most of them tend to be actual
|
||||
# problems.
|
||||
- invoke docs -o -W
|
||||
- invoke www -o -W
|
||||
notifications:
|
||||
irc:
|
||||
channels: "irc.freenode.org#paramiko"
|
||||
template:
|
||||
- "%{repository}@%{branch}: %{message} (%{build_url})"
|
||||
on_success: change
|
||||
on_failure: change
|
||||
use_notice: true
|
||||
email: false
|
||||
after_success:
|
||||
- coveralls
|
||||
|
|
15
Makefile
15
Makefile
|
@ -1,15 +0,0 @@
|
|||
release: docs
|
||||
python setup.py sdist register upload
|
||||
|
||||
docs: paramiko/*
|
||||
epydoc --no-private -o docs/ paramiko
|
||||
|
||||
clean:
|
||||
rm -rf build dist docs
|
||||
rm -f MANIFEST *.log demos/*.log
|
||||
rm -f paramiko/*.pyc
|
||||
rm -f test.log
|
||||
rm -rf paramiko.egg-info
|
||||
|
||||
test:
|
||||
python ./test.py
|
5
NEWS
5
NEWS
|
@ -9,6 +9,11 @@ Issues noted as "'ssh' #NN" can be found at https://github.com/bitprophet/ssh/.
|
|||
|
||||
Issues noted as "Fabric #NN" can be found at https://github.com/fabric/fabric/.
|
||||
|
||||
|
||||
**PLEASE NOTE:** For changes in 1.10.x and newer releases, please see
|
||||
www.paramiko.org's changelog page, or the source file, sites/www/changelog.rst
|
||||
|
||||
|
||||
Releases
|
||||
========
|
||||
|
||||
|
|
14
README
14
README
|
@ -5,16 +5,17 @@ paramiko
|
|||
|
||||
:Paramiko: Python SSH module
|
||||
:Copyright: Copyright (c) 2003-2009 Robey Pointer <robeypointer@gmail.com>
|
||||
:Copyright: Copyright (c) 2013 Jeff Forcier <jeff@bitprophet.org>
|
||||
:Copyright: Copyright (c) 2013-2014 Jeff Forcier <jeff@bitprophet.org>
|
||||
:License: LGPL
|
||||
:Homepage: https://github.com/paramiko/paramiko/
|
||||
:API docs: http://docs.paramiko.org
|
||||
|
||||
|
||||
What
|
||||
----
|
||||
|
||||
"paramiko" is a combination of the esperanto words for "paranoid" and
|
||||
"friend". it's a module for python 2.5+ that implements the SSH2 protocol
|
||||
"friend". it's a module for python 2.6+ that implements the SSH2 protocol
|
||||
for secure (encrypted and authenticated) connections to remote machines.
|
||||
unlike SSL (aka TLS), SSH2 protocol does not require hierarchical
|
||||
certificates signed by a powerful central authority. you may know SSH2 as
|
||||
|
@ -33,8 +34,10 @@ that should have come with this archive.
|
|||
Requirements
|
||||
------------
|
||||
|
||||
- python 2.5 or better <http://www.python.org/>
|
||||
- Python 2.6 or better <http://www.python.org/> - this includes Python
|
||||
3.2 and higher as well.
|
||||
- pycrypto 2.1 or better <https://www.dlitz.net/software/pycrypto/>
|
||||
- ecdsa 0.9 or better <https://pypi.python.org/pypi/ecdsa>
|
||||
|
||||
If you have setuptools, you can build and install paramiko and all its
|
||||
dependencies with this command (as root)::
|
||||
|
@ -122,10 +125,7 @@ Use
|
|||
---
|
||||
|
||||
the demo scripts are probably the best example of how to use this package.
|
||||
there is also a lot of documentation, generated with epydoc, in the doc/
|
||||
folder. point your browser there. seriously, do it. mad props to
|
||||
epydoc, which actually motivated me to write more documentation than i
|
||||
ever would have before.
|
||||
there is also a lot of documentation, generated with Sphinx autodoc, in the doc/ folder.
|
||||
|
||||
there are also unit tests here::
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -26,12 +26,15 @@ import os
|
|||
import select
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
from paramiko.py3compat import input
|
||||
|
||||
import paramiko
|
||||
try:
|
||||
import interactive
|
||||
except ImportError:
|
||||
from . import interactive
|
||||
|
||||
|
||||
def agent_auth(transport, username):
|
||||
|
@ -46,24 +49,24 @@ def agent_auth(transport, username):
|
|||
return
|
||||
|
||||
for key in agent_keys:
|
||||
print 'Trying ssh-agent key %s' % hexlify(key.get_fingerprint()),
|
||||
print('Trying ssh-agent key %s' % hexlify(key.get_fingerprint()))
|
||||
try:
|
||||
transport.auth_publickey(username, key)
|
||||
print '... success!'
|
||||
print('... success!')
|
||||
return
|
||||
except paramiko.SSHException:
|
||||
print '... nope.'
|
||||
print('... nope.')
|
||||
|
||||
|
||||
def manual_auth(username, hostname):
|
||||
default_auth = 'p'
|
||||
auth = raw_input('Auth by (p)assword, (r)sa key, or (d)ss key? [%s] ' % default_auth)
|
||||
auth = input('Auth by (p)assword, (r)sa key, or (d)ss key? [%s] ' % default_auth)
|
||||
if len(auth) == 0:
|
||||
auth = default_auth
|
||||
|
||||
if auth == 'r':
|
||||
default_path = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa')
|
||||
path = raw_input('RSA key [%s]: ' % default_path)
|
||||
path = input('RSA key [%s]: ' % default_path)
|
||||
if len(path) == 0:
|
||||
path = default_path
|
||||
try:
|
||||
|
@ -74,7 +77,7 @@ def manual_auth(username, hostname):
|
|||
t.auth_publickey(username, key)
|
||||
elif auth == 'd':
|
||||
default_path = os.path.join(os.environ['HOME'], '.ssh', 'id_dsa')
|
||||
path = raw_input('DSS key [%s]: ' % default_path)
|
||||
path = input('DSS key [%s]: ' % default_path)
|
||||
if len(path) == 0:
|
||||
path = default_path
|
||||
try:
|
||||
|
@ -97,9 +100,9 @@ if len(sys.argv) > 1:
|
|||
if hostname.find('@') >= 0:
|
||||
username, hostname = hostname.split('@')
|
||||
else:
|
||||
hostname = raw_input('Hostname: ')
|
||||
hostname = input('Hostname: ')
|
||||
if len(hostname) == 0:
|
||||
print '*** Hostname required.'
|
||||
print('*** Hostname required.')
|
||||
sys.exit(1)
|
||||
port = 22
|
||||
if hostname.find(':') >= 0:
|
||||
|
@ -110,8 +113,8 @@ if hostname.find(':') >= 0:
|
|||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect((hostname, port))
|
||||
except Exception, e:
|
||||
print '*** Connect failed: ' + str(e)
|
||||
except Exception as e:
|
||||
print('*** Connect failed: ' + str(e))
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
@ -120,7 +123,7 @@ try:
|
|||
try:
|
||||
t.start_client()
|
||||
except paramiko.SSHException:
|
||||
print '*** SSH negotiation failed.'
|
||||
print('*** SSH negotiation failed.')
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
|
@ -129,25 +132,25 @@ try:
|
|||
try:
|
||||
keys = paramiko.util.load_host_keys(os.path.expanduser('~/ssh/known_hosts'))
|
||||
except IOError:
|
||||
print '*** Unable to open host keys file'
|
||||
print('*** Unable to open host keys file')
|
||||
keys = {}
|
||||
|
||||
# check server's host key -- this is important.
|
||||
key = t.get_remote_server_key()
|
||||
if not keys.has_key(hostname):
|
||||
print '*** WARNING: Unknown host key!'
|
||||
elif not keys[hostname].has_key(key.get_name()):
|
||||
print '*** WARNING: Unknown host key!'
|
||||
if hostname not in keys:
|
||||
print('*** WARNING: Unknown host key!')
|
||||
elif key.get_name() not in keys[hostname]:
|
||||
print('*** WARNING: Unknown host key!')
|
||||
elif keys[hostname][key.get_name()] != key:
|
||||
print '*** WARNING: Host key has changed!!!'
|
||||
print('*** WARNING: Host key has changed!!!')
|
||||
sys.exit(1)
|
||||
else:
|
||||
print '*** Host key OK.'
|
||||
print('*** Host key OK.')
|
||||
|
||||
# get username
|
||||
if username == '':
|
||||
default_username = getpass.getuser()
|
||||
username = raw_input('Username [%s]: ' % default_username)
|
||||
username = input('Username [%s]: ' % default_username)
|
||||
if len(username) == 0:
|
||||
username = default_username
|
||||
|
||||
|
@ -155,21 +158,20 @@ try:
|
|||
if not t.is_authenticated():
|
||||
manual_auth(username, hostname)
|
||||
if not t.is_authenticated():
|
||||
print '*** Authentication failed. :('
|
||||
print('*** Authentication failed. :(')
|
||||
t.close()
|
||||
sys.exit(1)
|
||||
|
||||
chan = t.open_session()
|
||||
chan.get_pty()
|
||||
chan.invoke_shell()
|
||||
print '*** Here we go!'
|
||||
print
|
||||
print('*** Here we go!\n')
|
||||
interactive.interactive_shell(chan)
|
||||
chan.close()
|
||||
t.close()
|
||||
|
||||
except Exception, e:
|
||||
print '*** Caught exception: ' + str(e.__class__) + ': ' + str(e)
|
||||
except Exception as e:
|
||||
print('*** Caught exception: ' + str(e.__class__) + ': ' + str(e))
|
||||
traceback.print_exc()
|
||||
try:
|
||||
t.close()
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -17,9 +17,7 @@
|
|||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
from __future__ import with_statement
|
||||
|
||||
import string
|
||||
import sys
|
||||
|
||||
from binascii import hexlify
|
||||
|
@ -28,6 +26,7 @@ from optparse import OptionParser
|
|||
from paramiko import DSSKey
|
||||
from paramiko import RSAKey
|
||||
from paramiko.ssh_exception import SSHException
|
||||
from paramiko.py3compat import u
|
||||
|
||||
usage="""
|
||||
%prog [-v] [-b bits] -t type [-N new_passphrase] [-f output_keyfile]"""
|
||||
|
@ -47,16 +46,16 @@ key_dispatch_table = {
|
|||
def progress(arg=None):
|
||||
|
||||
if not arg:
|
||||
print '0%\x08\x08\x08',
|
||||
sys.stdout.write('0%\x08\x08\x08 ')
|
||||
sys.stdout.flush()
|
||||
elif arg[0] == 'p':
|
||||
print '25%\x08\x08\x08\x08',
|
||||
sys.stdout.write('25%\x08\x08\x08\x08 ')
|
||||
sys.stdout.flush()
|
||||
elif arg[0] == 'h':
|
||||
print '50%\x08\x08\x08\x08',
|
||||
sys.stdout.write('50%\x08\x08\x08\x08 ')
|
||||
sys.stdout.flush()
|
||||
elif arg[0] == 'x':
|
||||
print '75%\x08\x08\x08\x08',
|
||||
sys.stdout.write('75%\x08\x08\x08\x08 ')
|
||||
sys.stdout.flush()
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -92,8 +91,8 @@ if __name__ == '__main__':
|
|||
parser.print_help()
|
||||
sys.exit(0)
|
||||
|
||||
for o in default_values.keys():
|
||||
globals()[o] = getattr(options, o, default_values[string.lower(o)])
|
||||
for o in list(default_values.keys()):
|
||||
globals()[o] = getattr(options, o, default_values[o.lower()])
|
||||
|
||||
if options.newphrase:
|
||||
phrase = getattr(options, 'newphrase')
|
||||
|
@ -106,7 +105,7 @@ if __name__ == '__main__':
|
|||
if ktype == 'dsa' and bits > 1024:
|
||||
raise SSHException("DSA Keys must be 1024 bits")
|
||||
|
||||
if not key_dispatch_table.has_key(ktype):
|
||||
if ktype not in key_dispatch_table:
|
||||
raise SSHException("Unknown %s algorithm to generate keys pair" % ktype)
|
||||
|
||||
# generating private key
|
||||
|
@ -121,7 +120,7 @@ if __name__ == '__main__':
|
|||
f.write(" %s" % comment)
|
||||
|
||||
if options.verbose:
|
||||
print "done."
|
||||
print("done.")
|
||||
|
||||
hash = hexlify(pub.get_fingerprint())
|
||||
print "Fingerprint: %d %s %s.pub (%s)" % (bits, ":".join([ hash[i:2+i] for i in range(0, len(hash), 2)]), filename, string.upper(ktype))
|
||||
hash = u(hexlify(pub.get_fingerprint()))
|
||||
print("Fingerprint: %d %s %s.pub (%s)" % (bits, ":".join([ hash[i:2+i] for i in range(0, len(hash), 2)]), filename, ktype.upper()))
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -27,6 +27,7 @@ import threading
|
|||
import traceback
|
||||
|
||||
import paramiko
|
||||
from paramiko.py3compat import b, u, decodebytes
|
||||
|
||||
|
||||
# setup logging
|
||||
|
@ -35,17 +36,17 @@ paramiko.util.log_to_file('demo_server.log')
|
|||
host_key = paramiko.RSAKey(filename='test_rsa.key')
|
||||
#host_key = paramiko.DSSKey(filename='test_dss.key')
|
||||
|
||||
print 'Read key: ' + hexlify(host_key.get_fingerprint())
|
||||
print('Read key: ' + u(hexlify(host_key.get_fingerprint())))
|
||||
|
||||
|
||||
class Server (paramiko.ServerInterface):
|
||||
# 'data' is the output of base64.encodestring(str(key))
|
||||
# (using the "user_rsa_key" files)
|
||||
data = 'AAAAB3NzaC1yc2EAAAABIwAAAIEAyO4it3fHlmGZWJaGrfeHOVY7RWO3P9M7hp' + \
|
||||
'fAu7jJ2d7eothvfeuoRFtJwhUmZDluRdFyhFY/hFAh76PJKGAusIqIQKlkJxMC' + \
|
||||
'KDqIexkgHAfID/6mqvmnSJf0b5W8v5h2pI/stOSwTQ+pxVhwJ9ctYDhRSlF0iT' + \
|
||||
'UWT10hcuO4Ks8='
|
||||
good_pub_key = paramiko.RSAKey(data=base64.decodestring(data))
|
||||
data = (b'AAAAB3NzaC1yc2EAAAABIwAAAIEAyO4it3fHlmGZWJaGrfeHOVY7RWO3P9M7hp'
|
||||
b'fAu7jJ2d7eothvfeuoRFtJwhUmZDluRdFyhFY/hFAh76PJKGAusIqIQKlkJxMC'
|
||||
b'KDqIexkgHAfID/6mqvmnSJf0b5W8v5h2pI/stOSwTQ+pxVhwJ9ctYDhRSlF0iT'
|
||||
b'UWT10hcuO4Ks8=')
|
||||
good_pub_key = paramiko.RSAKey(data=decodebytes(data))
|
||||
|
||||
def __init__(self):
|
||||
self.event = threading.Event()
|
||||
|
@ -61,7 +62,7 @@ class Server (paramiko.ServerInterface):
|
|||
return paramiko.AUTH_FAILED
|
||||
|
||||
def check_auth_publickey(self, username, key):
|
||||
print 'Auth attempt with key: ' + hexlify(key.get_fingerprint())
|
||||
print('Auth attempt with key: ' + u(hexlify(key.get_fingerprint())))
|
||||
if (username == 'robey') and (key == self.good_pub_key):
|
||||
return paramiko.AUTH_SUCCESSFUL
|
||||
return paramiko.AUTH_FAILED
|
||||
|
@ -83,47 +84,47 @@ try:
|
|||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind(('', 2200))
|
||||
except Exception, e:
|
||||
print '*** Bind failed: ' + str(e)
|
||||
except Exception as e:
|
||||
print('*** Bind failed: ' + str(e))
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
sock.listen(100)
|
||||
print 'Listening for connection ...'
|
||||
print('Listening for connection ...')
|
||||
client, addr = sock.accept()
|
||||
except Exception, e:
|
||||
print '*** Listen/accept failed: ' + str(e)
|
||||
except Exception as e:
|
||||
print('*** Listen/accept failed: ' + str(e))
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
print 'Got a connection!'
|
||||
print('Got a connection!')
|
||||
|
||||
try:
|
||||
t = paramiko.Transport(client)
|
||||
try:
|
||||
t.load_server_moduli()
|
||||
except:
|
||||
print '(Failed to load moduli -- gex will be unsupported.)'
|
||||
print('(Failed to load moduli -- gex will be unsupported.)')
|
||||
raise
|
||||
t.add_server_key(host_key)
|
||||
server = Server()
|
||||
try:
|
||||
t.start_server(server=server)
|
||||
except paramiko.SSHException, x:
|
||||
print '*** SSH negotiation failed.'
|
||||
except paramiko.SSHException:
|
||||
print('*** SSH negotiation failed.')
|
||||
sys.exit(1)
|
||||
|
||||
# wait for auth
|
||||
chan = t.accept(20)
|
||||
if chan is None:
|
||||
print '*** No channel.'
|
||||
print('*** No channel.')
|
||||
sys.exit(1)
|
||||
print 'Authenticated!'
|
||||
print('Authenticated!')
|
||||
|
||||
server.event.wait(10)
|
||||
if not server.event.isSet():
|
||||
print '*** Client never asked for a shell.'
|
||||
print('*** Client never asked for a shell.')
|
||||
sys.exit(1)
|
||||
|
||||
chan.send('\r\n\r\nWelcome to my dorky little BBS!\r\n\r\n')
|
||||
|
@ -135,8 +136,8 @@ try:
|
|||
chan.send('\r\nI don\'t like you, ' + username + '.\r\n')
|
||||
chan.close()
|
||||
|
||||
except Exception, e:
|
||||
print '*** Caught exception: ' + str(e.__class__) + ': ' + str(e)
|
||||
except Exception as e:
|
||||
print('*** Caught exception: ' + str(e.__class__) + ': ' + str(e))
|
||||
traceback.print_exc()
|
||||
try:
|
||||
t.close()
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -28,6 +28,7 @@ import sys
|
|||
import traceback
|
||||
|
||||
import paramiko
|
||||
from paramiko.py3compat import input
|
||||
|
||||
|
||||
# setup logging
|
||||
|
@ -40,9 +41,9 @@ if len(sys.argv) > 1:
|
|||
if hostname.find('@') >= 0:
|
||||
username, hostname = hostname.split('@')
|
||||
else:
|
||||
hostname = raw_input('Hostname: ')
|
||||
hostname = input('Hostname: ')
|
||||
if len(hostname) == 0:
|
||||
print '*** Hostname required.'
|
||||
print('*** Hostname required.')
|
||||
sys.exit(1)
|
||||
port = 22
|
||||
if hostname.find(':') >= 0:
|
||||
|
@ -53,7 +54,7 @@ if hostname.find(':') >= 0:
|
|||
# get username
|
||||
if username == '':
|
||||
default_username = getpass.getuser()
|
||||
username = raw_input('Username [%s]: ' % default_username)
|
||||
username = input('Username [%s]: ' % default_username)
|
||||
if len(username) == 0:
|
||||
username = default_username
|
||||
password = getpass.getpass('Password for %s@%s: ' % (username, hostname))
|
||||
|
@ -69,13 +70,13 @@ except IOError:
|
|||
# try ~/ssh/ too, because windows can't have a folder named ~/.ssh/
|
||||
host_keys = paramiko.util.load_host_keys(os.path.expanduser('~/ssh/known_hosts'))
|
||||
except IOError:
|
||||
print '*** Unable to open host keys file'
|
||||
print('*** Unable to open host keys file')
|
||||
host_keys = {}
|
||||
|
||||
if host_keys.has_key(hostname):
|
||||
if hostname in host_keys:
|
||||
hostkeytype = host_keys[hostname].keys()[0]
|
||||
hostkey = host_keys[hostname][hostkeytype]
|
||||
print 'Using host key of type %s' % hostkeytype
|
||||
print('Using host key of type %s' % hostkeytype)
|
||||
|
||||
|
||||
# now, connect and use paramiko Transport to negotiate SSH2 across the connection
|
||||
|
@ -86,22 +87,26 @@ try:
|
|||
|
||||
# dirlist on remote host
|
||||
dirlist = sftp.listdir('.')
|
||||
print "Dirlist:", dirlist
|
||||
print("Dirlist: %s" % dirlist)
|
||||
|
||||
# copy this demo onto the server
|
||||
try:
|
||||
sftp.mkdir("demo_sftp_folder")
|
||||
except IOError:
|
||||
print '(assuming demo_sftp_folder/ already exists)'
|
||||
sftp.open('demo_sftp_folder/README', 'w').write('This was created by demo_sftp.py.\n')
|
||||
data = open('demo_sftp.py', 'r').read()
|
||||
print('(assuming demo_sftp_folder/ already exists)')
|
||||
with sftp.open('demo_sftp_folder/README', 'w') as f:
|
||||
f.write('This was created by demo_sftp.py.\n')
|
||||
with open('demo_sftp.py', 'r') as f:
|
||||
data = f.read()
|
||||
sftp.open('demo_sftp_folder/demo_sftp.py', 'w').write(data)
|
||||
print 'created demo_sftp_folder/ on the server'
|
||||
print('created demo_sftp_folder/ on the server')
|
||||
|
||||
# copy the README back here
|
||||
data = sftp.open('demo_sftp_folder/README', 'r').read()
|
||||
open('README_demo_sftp', 'w').write(data)
|
||||
print 'copied README back here'
|
||||
with sftp.open('demo_sftp_folder/README', 'r') as f:
|
||||
data = f.read()
|
||||
with open('README_demo_sftp', 'w') as f:
|
||||
f.write(data)
|
||||
print('copied README back here')
|
||||
|
||||
# BETTER: use the get() and put() methods
|
||||
sftp.put('demo_sftp.py', 'demo_sftp_folder/demo_sftp.py')
|
||||
|
@ -109,8 +114,8 @@ try:
|
|||
|
||||
t.close()
|
||||
|
||||
except Exception, e:
|
||||
print '*** Caught exception: %s: %s' % (e.__class__, e)
|
||||
except Exception as e:
|
||||
print('*** Caught exception: %s: %s' % (e.__class__, e))
|
||||
traceback.print_exc()
|
||||
try:
|
||||
t.close()
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -25,9 +25,13 @@ import os
|
|||
import socket
|
||||
import sys
|
||||
import traceback
|
||||
from paramiko.py3compat import input
|
||||
|
||||
import paramiko
|
||||
try:
|
||||
import interactive
|
||||
except ImportError:
|
||||
from . import interactive
|
||||
|
||||
|
||||
# setup logging
|
||||
|
@ -40,9 +44,9 @@ if len(sys.argv) > 1:
|
|||
if hostname.find('@') >= 0:
|
||||
username, hostname = hostname.split('@')
|
||||
else:
|
||||
hostname = raw_input('Hostname: ')
|
||||
hostname = input('Hostname: ')
|
||||
if len(hostname) == 0:
|
||||
print '*** Hostname required.'
|
||||
print('*** Hostname required.')
|
||||
sys.exit(1)
|
||||
port = 22
|
||||
if hostname.find(':') >= 0:
|
||||
|
@ -53,7 +57,7 @@ if hostname.find(':') >= 0:
|
|||
# get username
|
||||
if username == '':
|
||||
default_username = getpass.getuser()
|
||||
username = raw_input('Username [%s]: ' % default_username)
|
||||
username = input('Username [%s]: ' % default_username)
|
||||
if len(username) == 0:
|
||||
username = default_username
|
||||
password = getpass.getpass('Password for %s@%s: ' % (username, hostname))
|
||||
|
@ -63,19 +67,18 @@ password = getpass.getpass('Password for %s@%s: ' % (username, hostname))
|
|||
try:
|
||||
client = paramiko.SSHClient()
|
||||
client.load_system_host_keys()
|
||||
client.set_missing_host_key_policy(paramiko.WarningPolicy)
|
||||
print '*** Connecting...'
|
||||
client.set_missing_host_key_policy(paramiko.WarningPolicy())
|
||||
print('*** Connecting...')
|
||||
client.connect(hostname, port, username, password)
|
||||
chan = client.invoke_shell()
|
||||
print repr(client.get_transport())
|
||||
print '*** Here we go!'
|
||||
print
|
||||
print(repr(client.get_transport()))
|
||||
print('*** Here we go!\n')
|
||||
interactive.interactive_shell(chan)
|
||||
chan.close()
|
||||
client.close()
|
||||
|
||||
except Exception, e:
|
||||
print '*** Caught exception: %s: %s' % (e.__class__, e)
|
||||
except Exception as e:
|
||||
print('*** Caught exception: %s: %s' % (e.__class__, e))
|
||||
traceback.print_exc()
|
||||
try:
|
||||
client.close()
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -30,7 +30,11 @@ import getpass
|
|||
import os
|
||||
import socket
|
||||
import select
|
||||
try:
|
||||
import SocketServer
|
||||
except ImportError:
|
||||
import socketserver as SocketServer
|
||||
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
|
||||
|
@ -54,7 +58,7 @@ class Handler (SocketServer.BaseRequestHandler):
|
|||
chan = self.ssh_transport.open_channel('direct-tcpip',
|
||||
(self.chain_host, self.chain_port),
|
||||
self.request.getpeername())
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
verbose('Incoming request to %s:%d failed: %s' % (self.chain_host,
|
||||
self.chain_port,
|
||||
repr(e)))
|
||||
|
@ -78,9 +82,11 @@ class Handler (SocketServer.BaseRequestHandler):
|
|||
if len(data) == 0:
|
||||
break
|
||||
self.request.send(data)
|
||||
|
||||
peername = self.request.getpeername()
|
||||
chan.close()
|
||||
self.request.close()
|
||||
verbose('Tunnel closed from %r' % (self.request.getpeername(),))
|
||||
verbose('Tunnel closed from %r' % (peername,))
|
||||
|
||||
|
||||
def forward_tunnel(local_port, remote_host, remote_port, transport):
|
||||
|
@ -96,7 +102,7 @@ def forward_tunnel(local_port, remote_host, remote_port, transport):
|
|||
|
||||
def verbose(s):
|
||||
if g_verbose:
|
||||
print s
|
||||
print(s)
|
||||
|
||||
|
||||
HELP = """\
|
||||
|
@ -163,8 +169,8 @@ def main():
|
|||
try:
|
||||
client.connect(server[0], server[1], username=options.user, key_filename=options.keyfile,
|
||||
look_for_keys=options.look_for_keys, password=password)
|
||||
except Exception, e:
|
||||
print '*** Failed to connect to %s:%d: %r' % (server[0], server[1], e)
|
||||
except Exception as e:
|
||||
print('*** Failed to connect to %s:%d: %r' % (server[0], server[1], e))
|
||||
sys.exit(1)
|
||||
|
||||
verbose('Now forwarding port %d to %s:%d ...' % (options.port, remote[0], remote[1]))
|
||||
|
@ -172,7 +178,7 @@ def main():
|
|||
try:
|
||||
forward_tunnel(options.port, remote[0], remote[1], client.get_transport())
|
||||
except KeyboardInterrupt:
|
||||
print 'C-c: Port forwarding stopped.'
|
||||
print('C-c: Port forwarding stopped.')
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -19,6 +19,7 @@
|
|||
|
||||
import socket
|
||||
import sys
|
||||
from paramiko.py3compat import u
|
||||
|
||||
# windows does not have termios...
|
||||
try:
|
||||
|
@ -49,9 +50,9 @@ def posix_shell(chan):
|
|||
r, w, e = select.select([chan, sys.stdin], [], [])
|
||||
if chan in r:
|
||||
try:
|
||||
x = chan.recv(1024)
|
||||
x = u(chan.recv(1024))
|
||||
if len(x) == 0:
|
||||
print '\r\n*** EOF\r\n',
|
||||
sys.stdout.write('\r\n*** EOF\r\n')
|
||||
break
|
||||
sys.stdout.write(x)
|
||||
sys.stdout.flush()
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -46,7 +46,7 @@ def handler(chan, host, port):
|
|||
sock = socket.socket()
|
||||
try:
|
||||
sock.connect((host, port))
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
verbose('Forwarding request to %s:%d failed: %r' % (host, port, e))
|
||||
return
|
||||
|
||||
|
@ -82,7 +82,7 @@ def reverse_forward_tunnel(server_port, remote_host, remote_port, transport):
|
|||
|
||||
def verbose(s):
|
||||
if g_verbose:
|
||||
print s
|
||||
print(s)
|
||||
|
||||
|
||||
HELP = """\
|
||||
|
@ -150,8 +150,8 @@ def main():
|
|||
try:
|
||||
client.connect(server[0], server[1], username=options.user, key_filename=options.keyfile,
|
||||
look_for_keys=options.look_for_keys, password=password)
|
||||
except Exception, e:
|
||||
print '*** Failed to connect to %s:%d: %r' % (server[0], server[1], e)
|
||||
except Exception as e:
|
||||
print('*** Failed to connect to %s:%d: %r' % (server[0], server[1], e))
|
||||
sys.exit(1)
|
||||
|
||||
verbose('Now forwarding remote port %d to %s:%d ...' % (options.port, remote[0], remote[1]))
|
||||
|
@ -159,7 +159,7 @@ def main():
|
|||
try:
|
||||
reverse_forward_tunnel(options.port, remote[0], remote[1], client.get_transport())
|
||||
except KeyboardInterrupt:
|
||||
print 'C-c: Port forwarding stopped.'
|
||||
print('C-c: Port forwarding stopped.')
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# Older junk
|
||||
tox>=1.4,<1.5
|
||||
# For newer tasks like building Sphinx docs.
|
||||
# NOTE: Requires Python >=2.6
|
||||
invoke>=0.7.0
|
||||
invocations>=0.5.0
|
||||
sphinx>=1.1.3
|
||||
alabaster>=0.6.0
|
||||
releases>=0.5.2
|
||||
wheel==0.23.0
|
|
@ -1,13 +0,0 @@
|
|||
from fabric.api import task, sudo, env
|
||||
from fabric.contrib.project import rsync_project
|
||||
|
||||
|
||||
@task
|
||||
def upload_docs():
|
||||
target = "/var/www/paramiko.org"
|
||||
staging = "/tmp/paramiko_docs"
|
||||
sudo("mkdir -p %s" % staging)
|
||||
sudo("chown -R %s %s" % (env.user, staging))
|
||||
sudo("rm -rf %s/*" % target)
|
||||
rsync_project(local_dir='docs/', remote_dir=staging, delete=True)
|
||||
sudo("cp -R %s/* %s/" % (staging, target))
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -16,90 +16,53 @@
|
|||
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
I{Paramiko} (a combination of the esperanto words for "paranoid" and "friend")
|
||||
is a module for python 2.5 or greater that implements the SSH2 protocol for
|
||||
secure (encrypted and authenticated) connections to remote machines. Unlike
|
||||
SSL (aka TLS), the SSH2 protocol does not require hierarchical certificates
|
||||
signed by a powerful central authority. You may know SSH2 as the protocol that
|
||||
replaced C{telnet} and C{rsh} for secure access to remote shells, but the
|
||||
protocol also includes the ability to open arbitrary channels to remote
|
||||
services across an encrypted tunnel. (This is how C{sftp} works, for example.)
|
||||
|
||||
The high-level client API starts with creation of an L{SSHClient} object.
|
||||
For more direct control, pass a socket (or socket-like object) to a
|
||||
L{Transport}, and use L{start_server <Transport.start_server>} or
|
||||
L{start_client <Transport.start_client>} to negoatite
|
||||
with the remote host as either a server or client. As a client, you are
|
||||
responsible for authenticating using a password or private key, and checking
|
||||
the server's host key. I{(Key signature and verification is done by paramiko,
|
||||
but you will need to provide private keys and check that the content of a
|
||||
public key matches what you expected to see.)} As a server, you are
|
||||
responsible for deciding which users, passwords, and keys to allow, and what
|
||||
kind of channels to allow.
|
||||
|
||||
Once you have finished, either side may request flow-controlled L{Channel}s to
|
||||
the other side, which are python objects that act like sockets, but send and
|
||||
receive data over the encrypted session.
|
||||
|
||||
Paramiko is written entirely in python (no C or platform-dependent code) and is
|
||||
released under the GNU Lesser General Public License (LGPL).
|
||||
|
||||
Website: U{https://github.com/paramiko/paramiko/}
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
if sys.version_info < (2, 5):
|
||||
raise RuntimeError('You need python 2.5+ for this module.')
|
||||
if sys.version_info < (2, 6):
|
||||
raise RuntimeError('You need Python 2.6+ for this module.')
|
||||
|
||||
|
||||
__author__ = "Jeff Forcier <jeff@bitprophet.org>"
|
||||
__version__ = "1.9.0"
|
||||
__version__ = "1.14.0"
|
||||
__version_info__ = tuple([ int(d) for d in __version__.split(".") ])
|
||||
__license__ = "GNU Lesser General Public License (LGPL)"
|
||||
|
||||
|
||||
from transport import SecurityOptions, Transport
|
||||
from client import SSHClient, MissingHostKeyPolicy, AutoAddPolicy, RejectPolicy, WarningPolicy
|
||||
from auth_handler import AuthHandler
|
||||
from channel import Channel, ChannelFile
|
||||
from ssh_exception import SSHException, PasswordRequiredException, \
|
||||
from paramiko.transport import SecurityOptions, Transport
|
||||
from paramiko.client import SSHClient, MissingHostKeyPolicy, AutoAddPolicy, RejectPolicy, WarningPolicy
|
||||
from paramiko.auth_handler import AuthHandler
|
||||
from paramiko.channel import Channel, ChannelFile
|
||||
from paramiko.ssh_exception import SSHException, PasswordRequiredException, \
|
||||
BadAuthenticationType, ChannelException, BadHostKeyException, \
|
||||
AuthenticationException, ProxyCommandFailure
|
||||
from server import ServerInterface, SubsystemHandler, InteractiveQuery
|
||||
from rsakey import RSAKey
|
||||
from dsskey import DSSKey
|
||||
from sftp import SFTPError, BaseSFTP
|
||||
from sftp_client import SFTP, SFTPClient
|
||||
from sftp_server import SFTPServer
|
||||
from sftp_attr import SFTPAttributes
|
||||
from sftp_handle import SFTPHandle
|
||||
from sftp_si import SFTPServerInterface
|
||||
from sftp_file import SFTPFile
|
||||
from message import Message
|
||||
from packet import Packetizer
|
||||
from file import BufferedFile
|
||||
from agent import Agent, AgentKey
|
||||
from pkey import PKey
|
||||
from hostkeys import HostKeys
|
||||
from config import SSHConfig
|
||||
from proxy import ProxyCommand
|
||||
from paramiko.server import ServerInterface, SubsystemHandler, InteractiveQuery
|
||||
from paramiko.rsakey import RSAKey
|
||||
from paramiko.dsskey import DSSKey
|
||||
from paramiko.ecdsakey import ECDSAKey
|
||||
from paramiko.sftp import SFTPError, BaseSFTP
|
||||
from paramiko.sftp_client import SFTP, SFTPClient
|
||||
from paramiko.sftp_server import SFTPServer
|
||||
from paramiko.sftp_attr import SFTPAttributes
|
||||
from paramiko.sftp_handle import SFTPHandle
|
||||
from paramiko.sftp_si import SFTPServerInterface
|
||||
from paramiko.sftp_file import SFTPFile
|
||||
from paramiko.message import Message
|
||||
from paramiko.packet import Packetizer
|
||||
from paramiko.file import BufferedFile
|
||||
from paramiko.agent import Agent, AgentKey
|
||||
from paramiko.pkey import PKey
|
||||
from paramiko.hostkeys import HostKeys
|
||||
from paramiko.config import SSHConfig
|
||||
from paramiko.proxy import ProxyCommand
|
||||
|
||||
# fix module names for epydoc
|
||||
for c in locals().values():
|
||||
if issubclass(type(c), type) or type(c).__name__ == 'classobj':
|
||||
# classobj for exceptions :/
|
||||
c.__module__ = __name__
|
||||
del c
|
||||
|
||||
from common import AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED, \
|
||||
from paramiko.common import AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED, \
|
||||
OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, OPEN_FAILED_CONNECT_FAILED, \
|
||||
OPEN_FAILED_UNKNOWN_CHANNEL_TYPE, OPEN_FAILED_RESOURCE_SHORTAGE
|
||||
|
||||
from sftp import SFTP_OK, SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED, SFTP_FAILURE, \
|
||||
from paramiko.sftp import SFTP_OK, SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED, SFTP_FAILURE, \
|
||||
SFTP_BAD_MESSAGE, SFTP_NO_CONNECTION, SFTP_CONNECTION_LOST, SFTP_OP_UNSUPPORTED
|
||||
|
||||
from common import io_sleep
|
||||
from paramiko.common import io_sleep
|
||||
|
||||
__all__ = [ 'Transport',
|
||||
'SSHClient',
|
||||
|
|
|
@ -0,0 +1,278 @@
|
|||
"""
|
||||
Windows API functions implemented as ctypes functions and classes as found
|
||||
in jaraco.windows (2.10).
|
||||
|
||||
If you encounter issues with this module, please consider reporting the issues
|
||||
in jaraco.windows and asking the author to port the fixes back here.
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import ctypes.wintypes
|
||||
from paramiko.py3compat import u
|
||||
try:
|
||||
import builtins
|
||||
except ImportError:
|
||||
import __builtin__ as builtins
|
||||
|
||||
try:
|
||||
USHORT = ctypes.wintypes.USHORT
|
||||
except AttributeError:
|
||||
USHORT = ctypes.c_ushort
|
||||
|
||||
######################
|
||||
# jaraco.windows.error
|
||||
|
||||
def format_system_message(errno):
|
||||
"""
|
||||
Call FormatMessage with a system error number to retrieve
|
||||
the descriptive error message.
|
||||
"""
|
||||
# first some flags used by FormatMessageW
|
||||
ALLOCATE_BUFFER = 0x100
|
||||
ARGUMENT_ARRAY = 0x2000
|
||||
FROM_HMODULE = 0x800
|
||||
FROM_STRING = 0x400
|
||||
FROM_SYSTEM = 0x1000
|
||||
IGNORE_INSERTS = 0x200
|
||||
|
||||
# Let FormatMessageW allocate the buffer (we'll free it below)
|
||||
# Also, let it know we want a system error message.
|
||||
flags = ALLOCATE_BUFFER | FROM_SYSTEM
|
||||
source = None
|
||||
message_id = errno
|
||||
language_id = 0
|
||||
result_buffer = ctypes.wintypes.LPWSTR()
|
||||
buffer_size = 0
|
||||
arguments = None
|
||||
format_bytes = ctypes.windll.kernel32.FormatMessageW(
|
||||
flags,
|
||||
source,
|
||||
message_id,
|
||||
language_id,
|
||||
ctypes.byref(result_buffer),
|
||||
buffer_size,
|
||||
arguments,
|
||||
)
|
||||
# note the following will cause an infinite loop if GetLastError
|
||||
# repeatedly returns an error that cannot be formatted, although
|
||||
# this should not happen.
|
||||
handle_nonzero_success(format_bytes)
|
||||
message = result_buffer.value
|
||||
ctypes.windll.kernel32.LocalFree(result_buffer)
|
||||
return message
|
||||
|
||||
|
||||
class WindowsError(builtins.WindowsError):
|
||||
"more info about errors at http://msdn.microsoft.com/en-us/library/ms681381(VS.85).aspx"
|
||||
|
||||
def __init__(self, value=None):
|
||||
if value is None:
|
||||
value = ctypes.windll.kernel32.GetLastError()
|
||||
strerror = format_system_message(value)
|
||||
super(WindowsError, self).__init__(value, strerror)
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
return self.strerror
|
||||
|
||||
@property
|
||||
def code(self):
|
||||
return self.winerror
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
def __repr__(self):
|
||||
return '{self.__class__.__name__}({self.winerror})'.format(**vars())
|
||||
|
||||
def handle_nonzero_success(result):
|
||||
if result == 0:
|
||||
raise WindowsError()
|
||||
|
||||
|
||||
CreateFileMapping = ctypes.windll.kernel32.CreateFileMappingW
|
||||
CreateFileMapping.argtypes = [
|
||||
ctypes.wintypes.HANDLE,
|
||||
ctypes.c_void_p,
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.LPWSTR,
|
||||
]
|
||||
CreateFileMapping.restype = ctypes.wintypes.HANDLE
|
||||
|
||||
MapViewOfFile = ctypes.windll.kernel32.MapViewOfFile
|
||||
MapViewOfFile.restype = ctypes.wintypes.HANDLE
|
||||
|
||||
class MemoryMap(object):
|
||||
"""
|
||||
A memory map object which can have security attributes overrideden.
|
||||
"""
|
||||
def __init__(self, name, length, security_attributes=None):
|
||||
self.name = name
|
||||
self.length = length
|
||||
self.security_attributes = security_attributes
|
||||
self.pos = 0
|
||||
|
||||
def __enter__(self):
|
||||
p_SA = (
|
||||
ctypes.byref(self.security_attributes)
|
||||
if self.security_attributes else None
|
||||
)
|
||||
INVALID_HANDLE_VALUE = -1
|
||||
PAGE_READWRITE = 0x4
|
||||
FILE_MAP_WRITE = 0x2
|
||||
filemap = ctypes.windll.kernel32.CreateFileMappingW(
|
||||
INVALID_HANDLE_VALUE, p_SA, PAGE_READWRITE, 0, self.length,
|
||||
u(self.name))
|
||||
handle_nonzero_success(filemap)
|
||||
if filemap == INVALID_HANDLE_VALUE:
|
||||
raise Exception("Failed to create file mapping")
|
||||
self.filemap = filemap
|
||||
self.view = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0)
|
||||
return self
|
||||
|
||||
def seek(self, pos):
|
||||
self.pos = pos
|
||||
|
||||
def write(self, msg):
|
||||
n = len(msg)
|
||||
if self.pos + n >= self.length: # A little safety.
|
||||
raise ValueError("Refusing to write %d bytes" % n)
|
||||
ctypes.windll.kernel32.RtlMoveMemory(self.view + self.pos, msg, n)
|
||||
self.pos += n
|
||||
|
||||
def read(self, n):
|
||||
"""
|
||||
Read n bytes from mapped view.
|
||||
"""
|
||||
out = ctypes.create_string_buffer(n)
|
||||
ctypes.windll.kernel32.RtlMoveMemory(out, self.view + self.pos, n)
|
||||
self.pos += n
|
||||
return out.raw
|
||||
|
||||
def __exit__(self, exc_type, exc_val, tb):
|
||||
ctypes.windll.kernel32.UnmapViewOfFile(self.view)
|
||||
ctypes.windll.kernel32.CloseHandle(self.filemap)
|
||||
|
||||
#########################
|
||||
# jaraco.windows.security
|
||||
|
||||
class TokenInformationClass:
|
||||
TokenUser = 1
|
||||
|
||||
class TOKEN_USER(ctypes.Structure):
|
||||
num = 1
|
||||
_fields_ = [
|
||||
('SID', ctypes.c_void_p),
|
||||
('ATTRIBUTES', ctypes.wintypes.DWORD),
|
||||
]
|
||||
|
||||
|
||||
class SECURITY_DESCRIPTOR(ctypes.Structure):
|
||||
"""
|
||||
typedef struct _SECURITY_DESCRIPTOR
|
||||
{
|
||||
UCHAR Revision;
|
||||
UCHAR Sbz1;
|
||||
SECURITY_DESCRIPTOR_CONTROL Control;
|
||||
PSID Owner;
|
||||
PSID Group;
|
||||
PACL Sacl;
|
||||
PACL Dacl;
|
||||
} SECURITY_DESCRIPTOR;
|
||||
"""
|
||||
SECURITY_DESCRIPTOR_CONTROL = USHORT
|
||||
REVISION = 1
|
||||
|
||||
_fields_ = [
|
||||
('Revision', ctypes.c_ubyte),
|
||||
('Sbz1', ctypes.c_ubyte),
|
||||
('Control', SECURITY_DESCRIPTOR_CONTROL),
|
||||
('Owner', ctypes.c_void_p),
|
||||
('Group', ctypes.c_void_p),
|
||||
('Sacl', ctypes.c_void_p),
|
||||
('Dacl', ctypes.c_void_p),
|
||||
]
|
||||
|
||||
class SECURITY_ATTRIBUTES(ctypes.Structure):
|
||||
"""
|
||||
typedef struct _SECURITY_ATTRIBUTES {
|
||||
DWORD nLength;
|
||||
LPVOID lpSecurityDescriptor;
|
||||
BOOL bInheritHandle;
|
||||
} SECURITY_ATTRIBUTES;
|
||||
"""
|
||||
_fields_ = [
|
||||
('nLength', ctypes.wintypes.DWORD),
|
||||
('lpSecurityDescriptor', ctypes.c_void_p),
|
||||
('bInheritHandle', ctypes.wintypes.BOOL),
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SECURITY_ATTRIBUTES, self).__init__(*args, **kwargs)
|
||||
self.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES)
|
||||
|
||||
def _get_descriptor(self):
|
||||
return self._descriptor
|
||||
def _set_descriptor(self, descriptor):
|
||||
self._descriptor = descriptor
|
||||
self.lpSecurityDescriptor = ctypes.addressof(descriptor)
|
||||
descriptor = property(_get_descriptor, _set_descriptor)
|
||||
|
||||
def GetTokenInformation(token, information_class):
|
||||
"""
|
||||
Given a token, get the token information for it.
|
||||
"""
|
||||
data_size = ctypes.wintypes.DWORD()
|
||||
ctypes.windll.advapi32.GetTokenInformation(token, information_class.num,
|
||||
0, 0, ctypes.byref(data_size))
|
||||
data = ctypes.create_string_buffer(data_size.value)
|
||||
handle_nonzero_success(ctypes.windll.advapi32.GetTokenInformation(token,
|
||||
information_class.num,
|
||||
ctypes.byref(data), ctypes.sizeof(data),
|
||||
ctypes.byref(data_size)))
|
||||
return ctypes.cast(data, ctypes.POINTER(TOKEN_USER)).contents
|
||||
|
||||
class TokenAccess:
|
||||
TOKEN_QUERY = 0x8
|
||||
|
||||
def OpenProcessToken(proc_handle, access):
|
||||
result = ctypes.wintypes.HANDLE()
|
||||
proc_handle = ctypes.wintypes.HANDLE(proc_handle)
|
||||
handle_nonzero_success(ctypes.windll.advapi32.OpenProcessToken(
|
||||
proc_handle, access, ctypes.byref(result)))
|
||||
return result
|
||||
|
||||
def get_current_user():
|
||||
"""
|
||||
Return a TOKEN_USER for the owner of this process.
|
||||
"""
|
||||
process = OpenProcessToken(
|
||||
ctypes.windll.kernel32.GetCurrentProcess(),
|
||||
TokenAccess.TOKEN_QUERY,
|
||||
)
|
||||
return GetTokenInformation(process, TOKEN_USER)
|
||||
|
||||
def get_security_attributes_for_user(user=None):
|
||||
"""
|
||||
Return a SECURITY_ATTRIBUTES structure with the SID set to the
|
||||
specified user (uses current user if none is specified).
|
||||
"""
|
||||
if user is None:
|
||||
user = get_current_user()
|
||||
|
||||
assert isinstance(user, TOKEN_USER), "user must be TOKEN_USER instance"
|
||||
|
||||
SD = SECURITY_DESCRIPTOR()
|
||||
SA = SECURITY_ATTRIBUTES()
|
||||
# by attaching the actual security descriptor, it will be garbage-
|
||||
# collected with the security attributes
|
||||
SA.descriptor = SD
|
||||
SA.bInheritHandle = 1
|
||||
|
||||
ctypes.windll.advapi32.InitializeSecurityDescriptor(ctypes.byref(SD),
|
||||
SECURITY_DESCRIPTOR.REVISION)
|
||||
ctypes.windll.advapi32.SetSecurityDescriptorOwner(ctypes.byref(SD),
|
||||
user.SID, 0)
|
||||
return SA
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -17,7 +17,7 @@
|
|||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
SSH Agent interface for Unix clients.
|
||||
SSH Agent interface
|
||||
"""
|
||||
|
||||
import os
|
||||
|
@ -29,28 +29,21 @@ import time
|
|||
import tempfile
|
||||
import stat
|
||||
from select import select
|
||||
from paramiko.common import asbytes, io_sleep
|
||||
from paramiko.py3compat import byte_chr
|
||||
|
||||
from paramiko.ssh_exception import SSHException
|
||||
from paramiko.message import Message
|
||||
from paramiko.pkey import PKey
|
||||
from paramiko.channel import Channel
|
||||
from paramiko.common import io_sleep
|
||||
from paramiko.util import retry_on_signal
|
||||
|
||||
SSH2_AGENTC_REQUEST_IDENTITIES, SSH2_AGENT_IDENTITIES_ANSWER, \
|
||||
SSH2_AGENTC_SIGN_REQUEST, SSH2_AGENT_SIGN_RESPONSE = range(11, 15)
|
||||
cSSH2_AGENTC_REQUEST_IDENTITIES = byte_chr(11)
|
||||
SSH2_AGENT_IDENTITIES_ANSWER = 12
|
||||
cSSH2_AGENTC_SIGN_REQUEST = byte_chr(13)
|
||||
SSH2_AGENT_SIGN_RESPONSE = 14
|
||||
|
||||
|
||||
class AgentSSH(object):
|
||||
"""
|
||||
Client interface for using private keys from an SSH agent running on the
|
||||
local machine. If an SSH agent is running, this class can be used to
|
||||
connect to it and retreive L{PKey} objects which can be used when
|
||||
attempting to authenticate to remote SSH servers.
|
||||
|
||||
Because the SSH agent protocol uses environment variables and unix-domain
|
||||
sockets, this probably doesn't work on Windows. It does work on most
|
||||
posix platforms though (Linux and MacOS X, for example).
|
||||
"""
|
||||
def __init__(self):
|
||||
self._conn = None
|
||||
self._keys = ()
|
||||
|
@ -61,19 +54,20 @@ class AgentSSH(object):
|
|||
no SSH agent was running (or it couldn't be contacted), an empty list
|
||||
will be returned.
|
||||
|
||||
@return: a list of keys available on the SSH agent
|
||||
@rtype: tuple of L{AgentKey}
|
||||
:return:
|
||||
a tuple of `.AgentKey` objects representing keys available on the
|
||||
SSH agent
|
||||
"""
|
||||
return self._keys
|
||||
|
||||
def _connect(self, conn):
|
||||
self._conn = conn
|
||||
ptype, result = self._send_message(chr(SSH2_AGENTC_REQUEST_IDENTITIES))
|
||||
ptype, result = self._send_message(cSSH2_AGENTC_REQUEST_IDENTITIES)
|
||||
if ptype != SSH2_AGENT_IDENTITIES_ANSWER:
|
||||
raise SSHException('could not get keys from ssh-agent')
|
||||
keys = []
|
||||
for i in range(result.get_int()):
|
||||
keys.append(AgentKey(self, result.get_string()))
|
||||
keys.append(AgentKey(self, result.get_binary()))
|
||||
result.get_string()
|
||||
self._keys = tuple(keys)
|
||||
|
||||
|
@ -83,7 +77,7 @@ class AgentSSH(object):
|
|||
self._keys = ()
|
||||
|
||||
def _send_message(self, msg):
|
||||
msg = str(msg)
|
||||
msg = asbytes(msg)
|
||||
self._conn.send(struct.pack('>I', len(msg)) + msg)
|
||||
l = self._read_all(4)
|
||||
msg = Message(self._read_all(struct.unpack('>I', l)[0]))
|
||||
|
@ -100,8 +94,11 @@ class AgentSSH(object):
|
|||
result += extra
|
||||
return result
|
||||
|
||||
|
||||
class AgentProxyThread(threading.Thread):
|
||||
""" Class in charge of communication between two chan """
|
||||
"""
|
||||
Class in charge of communication between two channels.
|
||||
"""
|
||||
def __init__(self, agent):
|
||||
threading.Thread.__init__(self, target=self.run)
|
||||
self._agent = agent
|
||||
|
@ -130,15 +127,23 @@ class AgentProxyThread(threading.Thread):
|
|||
if len(data) != 0:
|
||||
self.__inr.send(data)
|
||||
else:
|
||||
self._close()
|
||||
break
|
||||
elif self.__inr == fd:
|
||||
data = self.__inr.recv(512)
|
||||
if len(data) != 0:
|
||||
self._agent._conn.send(data)
|
||||
else:
|
||||
self._close()
|
||||
break
|
||||
time.sleep(io_sleep)
|
||||
|
||||
def _close(self):
|
||||
self._exit = True
|
||||
self.__inr.close()
|
||||
self._agent._conn.close()
|
||||
|
||||
|
||||
class AgentLocalProxy(AgentProxyThread):
|
||||
"""
|
||||
Class to be used when wanting to ask a local SSH Agent being
|
||||
|
@ -148,18 +153,20 @@ class AgentLocalProxy(AgentProxyThread):
|
|||
AgentProxyThread.__init__(self, agent)
|
||||
|
||||
def get_connection(self):
|
||||
""" Return a pair of socket object and string address
|
||||
May Block !
|
||||
"""
|
||||
Return a pair of socket object and string address.
|
||||
|
||||
May block!
|
||||
"""
|
||||
conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
try:
|
||||
conn.bind(self._agent._get_filename())
|
||||
conn.listen(1)
|
||||
(r, addr) = conn.accept()
|
||||
return (r, addr)
|
||||
return r, addr
|
||||
except:
|
||||
raise
|
||||
return None
|
||||
|
||||
|
||||
class AgentRemoteProxy(AgentProxyThread):
|
||||
"""
|
||||
|
@ -170,22 +177,20 @@ class AgentRemoteProxy(AgentProxyThread):
|
|||
self.__chan = chan
|
||||
|
||||
def get_connection(self):
|
||||
"""
|
||||
Class to be used when wanting to ask a local SSH Agent being
|
||||
asked from a remote fake agent (so use a unix socket for ex.)
|
||||
"""
|
||||
return (self.__chan, None)
|
||||
return self.__chan, None
|
||||
|
||||
|
||||
class AgentClientProxy(object):
|
||||
"""
|
||||
Class proxying request as a client:
|
||||
-> client ask for a request_forward_agent()
|
||||
-> server creates a proxy and a fake SSH Agent
|
||||
-> server ask for establishing a connection when needed,
|
||||
|
||||
#. client ask for a request_forward_agent()
|
||||
#. server creates a proxy and a fake SSH Agent
|
||||
#. server ask for establishing a connection when needed,
|
||||
calling the forward_agent_handler at client side.
|
||||
-> the forward_agent_handler launch a thread for connecting
|
||||
#. the forward_agent_handler launch a thread for connecting
|
||||
the remote fake agent and the local agent
|
||||
-> Communication occurs ...
|
||||
#. Communication occurs ...
|
||||
"""
|
||||
def __init__(self, chanRemote):
|
||||
self._conn = None
|
||||
|
@ -198,7 +203,7 @@ class AgentClientProxy(object):
|
|||
|
||||
def connect(self):
|
||||
"""
|
||||
Method automatically called by the run() method of the AgentProxyThread
|
||||
Method automatically called by ``AgentProxyThread.run``.
|
||||
"""
|
||||
if ('SSH_AUTH_SOCK' in os.environ) and (sys.platform != 'win32'):
|
||||
conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
|
@ -208,7 +213,7 @@ class AgentClientProxy(object):
|
|||
# probably a dangling env var: the ssh agent is gone
|
||||
return
|
||||
elif sys.platform == 'win32':
|
||||
import win_pageant
|
||||
import paramiko.win_pageant as win_pageant
|
||||
if win_pageant.can_talk_to_agent():
|
||||
conn = win_pageant.PageantConnection()
|
||||
else:
|
||||
|
@ -229,11 +234,12 @@ class AgentClientProxy(object):
|
|||
if self._conn is not None:
|
||||
self._conn.close()
|
||||
|
||||
|
||||
class AgentServerProxy(AgentSSH):
|
||||
"""
|
||||
@param t : transport used for the Forward for SSH Agent communication
|
||||
:param .Transport t: Transport used for SSH Agent communication forwarding
|
||||
|
||||
@raise SSHException: mostly if we lost the agent
|
||||
:raises SSHException: mostly if we lost the agent
|
||||
"""
|
||||
def __init__(self, t):
|
||||
AgentSSH.__init__(self)
|
||||
|
@ -269,16 +275,15 @@ class AgentServerProxy(AgentSSH):
|
|||
"""
|
||||
Helper for the environnement under unix
|
||||
|
||||
@return: the SSH_AUTH_SOCK Environnement variables
|
||||
@rtype: dict
|
||||
:return:
|
||||
a dict containing the ``SSH_AUTH_SOCK`` environnement variables
|
||||
"""
|
||||
env = {}
|
||||
env['SSH_AUTH_SOCK'] = self._get_filename()
|
||||
return env
|
||||
return {'SSH_AUTH_SOCK': self._get_filename()}
|
||||
|
||||
def _get_filename(self):
|
||||
return self._file
|
||||
|
||||
|
||||
class AgentRequestHandler(object):
|
||||
def __init__(self, chanClient):
|
||||
self._conn = None
|
||||
|
@ -296,27 +301,22 @@ class AgentRequestHandler(object):
|
|||
for p in self.__clientProxys:
|
||||
p.close()
|
||||
|
||||
|
||||
class Agent(AgentSSH):
|
||||
"""
|
||||
Client interface for using private keys from an SSH agent running on the
|
||||
local machine. If an SSH agent is running, this class can be used to
|
||||
connect to it and retreive L{PKey} objects which can be used when
|
||||
connect to it and retreive `.PKey` objects which can be used when
|
||||
attempting to authenticate to remote SSH servers.
|
||||
|
||||
Because the SSH agent protocol uses environment variables and unix-domain
|
||||
sockets, this probably doesn't work on Windows. It does work on most
|
||||
posix platforms though (Linux and MacOS X, for example).
|
||||
"""
|
||||
Upon initialization, a session with the local machine's SSH agent is
|
||||
opened, if one is running. If no agent is running, initialization will
|
||||
succeed, but `get_keys` will return an empty tuple.
|
||||
|
||||
:raises SSHException:
|
||||
if an SSH agent is found, but speaks an incompatible protocol
|
||||
"""
|
||||
def __init__(self):
|
||||
"""
|
||||
Open a session with the local machine's SSH agent, if one is running.
|
||||
If no agent is running, initialization will succeed, but L{get_keys}
|
||||
will return an empty tuple.
|
||||
|
||||
@raise SSHException: if an SSH agent is found, but speaks an
|
||||
incompatible protocol
|
||||
"""
|
||||
AgentSSH.__init__(self)
|
||||
|
||||
if ('SSH_AUTH_SOCK' in os.environ) and (sys.platform != 'win32'):
|
||||
|
@ -327,7 +327,7 @@ class Agent(AgentSSH):
|
|||
# probably a dangling env var: the ssh agent is gone
|
||||
return
|
||||
elif sys.platform == 'win32':
|
||||
import win_pageant
|
||||
from . import win_pageant
|
||||
if win_pageant.can_talk_to_agent():
|
||||
conn = win_pageant.PageantConnection()
|
||||
else:
|
||||
|
@ -343,31 +343,34 @@ class Agent(AgentSSH):
|
|||
"""
|
||||
self._close()
|
||||
|
||||
|
||||
class AgentKey(PKey):
|
||||
"""
|
||||
Private key held in a local SSH agent. This type of key can be used for
|
||||
authenticating to a remote server (signing). Most other key operations
|
||||
work as expected.
|
||||
"""
|
||||
|
||||
def __init__(self, agent, blob):
|
||||
self.agent = agent
|
||||
self.blob = blob
|
||||
self.name = Message(blob).get_string()
|
||||
self.name = Message(blob).get_text()
|
||||
|
||||
def asbytes(self):
|
||||
return self.blob
|
||||
|
||||
def __str__(self):
|
||||
return self.blob
|
||||
return self.asbytes()
|
||||
|
||||
def get_name(self):
|
||||
return self.name
|
||||
|
||||
def sign_ssh_data(self, rng, data):
|
||||
def sign_ssh_data(self, data):
|
||||
msg = Message()
|
||||
msg.add_byte(chr(SSH2_AGENTC_SIGN_REQUEST))
|
||||
msg.add_byte(cSSH2_AGENTC_SIGN_REQUEST)
|
||||
msg.add_string(self.blob)
|
||||
msg.add_string(data)
|
||||
msg.add_int(0)
|
||||
ptype, result = self.agent._send_message(msg)
|
||||
if ptype != SSH2_AGENT_SIGN_RESPONSE:
|
||||
raise SSHException('key cannot be used for signing')
|
||||
return result.get_string()
|
||||
return result.get_binary()
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -17,18 +17,21 @@
|
|||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{AuthHandler}
|
||||
`.AuthHandler`
|
||||
"""
|
||||
|
||||
import threading
|
||||
import weakref
|
||||
from paramiko.common import cMSG_SERVICE_REQUEST, cMSG_DISCONNECT, \
|
||||
DISCONNECT_SERVICE_NOT_AVAILABLE, DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, \
|
||||
cMSG_USERAUTH_REQUEST, cMSG_SERVICE_ACCEPT, DEBUG, AUTH_SUCCESSFUL, INFO, \
|
||||
cMSG_USERAUTH_SUCCESS, cMSG_USERAUTH_FAILURE, AUTH_PARTIALLY_SUCCESSFUL, \
|
||||
cMSG_USERAUTH_INFO_REQUEST, WARNING, AUTH_FAILED, cMSG_USERAUTH_PK_OK, \
|
||||
cMSG_USERAUTH_INFO_RESPONSE, MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT, \
|
||||
MSG_USERAUTH_REQUEST, MSG_USERAUTH_SUCCESS, MSG_USERAUTH_FAILURE, \
|
||||
MSG_USERAUTH_BANNER, MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE
|
||||
|
||||
# this helps freezing utils
|
||||
import encodings.utf_8
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.message import Message
|
||||
from paramiko.py3compat import bytestring
|
||||
from paramiko.ssh_exception import SSHException, AuthenticationException, \
|
||||
BadAuthenticationType, PartialAuthentication
|
||||
from paramiko.server import InteractiveQuery
|
||||
|
@ -45,6 +48,7 @@ class AuthHandler (object):
|
|||
self.authenticated = False
|
||||
self.auth_event = None
|
||||
self.auth_method = ''
|
||||
self.banner = None
|
||||
self.password = None
|
||||
self.private_key = None
|
||||
self.interactive_handler = None
|
||||
|
@ -113,19 +117,17 @@ class AuthHandler (object):
|
|||
if self.auth_event is not None:
|
||||
self.auth_event.set()
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _request_auth(self):
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_SERVICE_REQUEST))
|
||||
m.add_byte(cMSG_SERVICE_REQUEST)
|
||||
m.add_string('ssh-userauth')
|
||||
self.transport._send_message(m)
|
||||
|
||||
def _disconnect_service_not_available(self):
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_DISCONNECT))
|
||||
m.add_byte(cMSG_DISCONNECT)
|
||||
m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE)
|
||||
m.add_string('Service not available')
|
||||
m.add_string('en')
|
||||
|
@ -134,7 +136,7 @@ class AuthHandler (object):
|
|||
|
||||
def _disconnect_no_more_auth(self):
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_DISCONNECT))
|
||||
m.add_byte(cMSG_DISCONNECT)
|
||||
m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE)
|
||||
m.add_string('No more auth methods available')
|
||||
m.add_string('en')
|
||||
|
@ -144,14 +146,14 @@ class AuthHandler (object):
|
|||
def _get_session_blob(self, key, service, username):
|
||||
m = Message()
|
||||
m.add_string(self.transport.session_id)
|
||||
m.add_byte(chr(MSG_USERAUTH_REQUEST))
|
||||
m.add_byte(cMSG_USERAUTH_REQUEST)
|
||||
m.add_string(username)
|
||||
m.add_string(service)
|
||||
m.add_string('publickey')
|
||||
m.add_boolean(1)
|
||||
m.add_boolean(True)
|
||||
m.add_string(key.get_name())
|
||||
m.add_string(str(key))
|
||||
return str(m)
|
||||
m.add_string(key)
|
||||
return m.asbytes()
|
||||
|
||||
def wait_for_response(self, event):
|
||||
while True:
|
||||
|
@ -167,7 +169,7 @@ class AuthHandler (object):
|
|||
e = self.transport.get_exception()
|
||||
if e is None:
|
||||
e = AuthenticationException('Authentication failed.')
|
||||
# this is horrible. python Exception isn't yet descended from
|
||||
# this is horrible. Python Exception isn't yet descended from
|
||||
# object, so type(e) won't work. :(
|
||||
if issubclass(e.__class__, PartialAuthentication):
|
||||
return e.allowed_types
|
||||
|
@ -175,11 +177,11 @@ class AuthHandler (object):
|
|||
return []
|
||||
|
||||
def _parse_service_request(self, m):
|
||||
service = m.get_string()
|
||||
service = m.get_text()
|
||||
if self.transport.server_mode and (service == 'ssh-userauth'):
|
||||
# accepted
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_SERVICE_ACCEPT))
|
||||
m.add_byte(cMSG_SERVICE_ACCEPT)
|
||||
m.add_string(service)
|
||||
self.transport._send_message(m)
|
||||
return
|
||||
|
@ -187,27 +189,25 @@ class AuthHandler (object):
|
|||
self._disconnect_service_not_available()
|
||||
|
||||
def _parse_service_accept(self, m):
|
||||
service = m.get_string()
|
||||
service = m.get_text()
|
||||
if service == 'ssh-userauth':
|
||||
self.transport._log(DEBUG, 'userauth is OK')
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_USERAUTH_REQUEST))
|
||||
m.add_byte(cMSG_USERAUTH_REQUEST)
|
||||
m.add_string(self.username)
|
||||
m.add_string('ssh-connection')
|
||||
m.add_string(self.auth_method)
|
||||
if self.auth_method == 'password':
|
||||
m.add_boolean(False)
|
||||
password = self.password
|
||||
if isinstance(password, unicode):
|
||||
password = password.encode('UTF-8')
|
||||
password = bytestring(self.password)
|
||||
m.add_string(password)
|
||||
elif self.auth_method == 'publickey':
|
||||
m.add_boolean(True)
|
||||
m.add_string(self.private_key.get_name())
|
||||
m.add_string(str(self.private_key))
|
||||
m.add_string(self.private_key)
|
||||
blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username)
|
||||
sig = self.private_key.sign_ssh_data(self.transport.rng, blob)
|
||||
m.add_string(str(sig))
|
||||
sig = self.private_key.sign_ssh_data(blob)
|
||||
m.add_string(sig)
|
||||
elif self.auth_method == 'keyboard-interactive':
|
||||
m.add_string('')
|
||||
m.add_string(self.submethods)
|
||||
|
@ -224,16 +224,16 @@ class AuthHandler (object):
|
|||
m = Message()
|
||||
if result == AUTH_SUCCESSFUL:
|
||||
self.transport._log(INFO, 'Auth granted (%s).' % method)
|
||||
m.add_byte(chr(MSG_USERAUTH_SUCCESS))
|
||||
m.add_byte(cMSG_USERAUTH_SUCCESS)
|
||||
self.authenticated = True
|
||||
else:
|
||||
self.transport._log(INFO, 'Auth rejected (%s).' % method)
|
||||
m.add_byte(chr(MSG_USERAUTH_FAILURE))
|
||||
m.add_byte(cMSG_USERAUTH_FAILURE)
|
||||
m.add_string(self.transport.server_object.get_allowed_auths(username))
|
||||
if result == AUTH_PARTIALLY_SUCCESSFUL:
|
||||
m.add_boolean(1)
|
||||
m.add_boolean(True)
|
||||
else:
|
||||
m.add_boolean(0)
|
||||
m.add_boolean(False)
|
||||
self.auth_fail_count += 1
|
||||
self.transport._send_message(m)
|
||||
if self.auth_fail_count >= 10:
|
||||
|
@ -244,10 +244,10 @@ class AuthHandler (object):
|
|||
def _interactive_query(self, q):
|
||||
# make interactive query instead of response
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_USERAUTH_INFO_REQUEST))
|
||||
m.add_byte(cMSG_USERAUTH_INFO_REQUEST)
|
||||
m.add_string(q.name)
|
||||
m.add_string(q.instructions)
|
||||
m.add_string('')
|
||||
m.add_string(bytes())
|
||||
m.add_int(len(q.prompts))
|
||||
for p in q.prompts:
|
||||
m.add_string(p[0])
|
||||
|
@ -258,17 +258,17 @@ class AuthHandler (object):
|
|||
if not self.transport.server_mode:
|
||||
# er, uh... what?
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_USERAUTH_FAILURE))
|
||||
m.add_byte(cMSG_USERAUTH_FAILURE)
|
||||
m.add_string('none')
|
||||
m.add_boolean(0)
|
||||
m.add_boolean(False)
|
||||
self.transport._send_message(m)
|
||||
return
|
||||
if self.authenticated:
|
||||
# ignore
|
||||
return
|
||||
username = m.get_string()
|
||||
service = m.get_string()
|
||||
method = m.get_string()
|
||||
username = m.get_text()
|
||||
service = m.get_text()
|
||||
method = m.get_text()
|
||||
self.transport._log(DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username))
|
||||
if service != 'ssh-connection':
|
||||
self._disconnect_service_not_available()
|
||||
|
@ -283,7 +283,7 @@ class AuthHandler (object):
|
|||
result = self.transport.server_object.check_auth_none(username)
|
||||
elif method == 'password':
|
||||
changereq = m.get_boolean()
|
||||
password = m.get_string()
|
||||
password = m.get_binary()
|
||||
try:
|
||||
password = password.decode('UTF-8')
|
||||
except UnicodeError:
|
||||
|
@ -294,7 +294,7 @@ class AuthHandler (object):
|
|||
# always treated as failure, since we don't support changing passwords, but collect
|
||||
# the list of valid auth types from the callback anyway
|
||||
self.transport._log(DEBUG, 'Auth request to change passwords (rejected)')
|
||||
newpassword = m.get_string()
|
||||
newpassword = m.get_binary()
|
||||
try:
|
||||
newpassword = newpassword.decode('UTF-8', 'replace')
|
||||
except UnicodeError:
|
||||
|
@ -304,11 +304,11 @@ class AuthHandler (object):
|
|||
result = self.transport.server_object.check_auth_password(username, password)
|
||||
elif method == 'publickey':
|
||||
sig_attached = m.get_boolean()
|
||||
keytype = m.get_string()
|
||||
keyblob = m.get_string()
|
||||
keytype = m.get_text()
|
||||
keyblob = m.get_binary()
|
||||
try:
|
||||
key = self.transport._key_info[keytype](Message(keyblob))
|
||||
except SSHException, e:
|
||||
except SSHException as e:
|
||||
self.transport._log(INFO, 'Auth rejected: public key: %s' % str(e))
|
||||
key = None
|
||||
except:
|
||||
|
@ -325,12 +325,12 @@ class AuthHandler (object):
|
|||
# client wants to know if this key is acceptable, before it
|
||||
# signs anything... send special "ok" message
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_USERAUTH_PK_OK))
|
||||
m.add_byte(cMSG_USERAUTH_PK_OK)
|
||||
m.add_string(keytype)
|
||||
m.add_string(keyblob)
|
||||
self.transport._send_message(m)
|
||||
return
|
||||
sig = Message(m.get_string())
|
||||
sig = Message(m.get_binary())
|
||||
blob = self._get_session_blob(key, service, username)
|
||||
if not key.verify_ssh_sig(blob, sig):
|
||||
self.transport._log(INFO, 'Auth rejected: invalid signature')
|
||||
|
@ -352,7 +352,7 @@ class AuthHandler (object):
|
|||
self.transport._log(INFO, 'Authentication (%s) successful!' % self.auth_method)
|
||||
self.authenticated = True
|
||||
self.transport._auth_trigger()
|
||||
if self.auth_event != None:
|
||||
if self.auth_event is not None:
|
||||
self.auth_event.set()
|
||||
|
||||
def _parse_userauth_failure(self, m):
|
||||
|
@ -370,29 +370,30 @@ class AuthHandler (object):
|
|||
self.transport._log(INFO, 'Authentication (%s) failed.' % self.auth_method)
|
||||
self.authenticated = False
|
||||
self.username = None
|
||||
if self.auth_event != None:
|
||||
if self.auth_event is not None:
|
||||
self.auth_event.set()
|
||||
|
||||
def _parse_userauth_banner(self, m):
|
||||
banner = m.get_string()
|
||||
self.banner = banner
|
||||
lang = m.get_string()
|
||||
self.transport._log(INFO, 'Auth banner: ' + banner)
|
||||
self.transport._log(INFO, 'Auth banner: %s' % banner)
|
||||
# who cares.
|
||||
|
||||
def _parse_userauth_info_request(self, m):
|
||||
if self.auth_method != 'keyboard-interactive':
|
||||
raise SSHException('Illegal info request from server')
|
||||
title = m.get_string()
|
||||
instructions = m.get_string()
|
||||
m.get_string() # lang
|
||||
title = m.get_text()
|
||||
instructions = m.get_text()
|
||||
m.get_binary() # lang
|
||||
prompts = m.get_int()
|
||||
prompt_list = []
|
||||
for i in range(prompts):
|
||||
prompt_list.append((m.get_string(), m.get_boolean()))
|
||||
prompt_list.append((m.get_text(), m.get_boolean()))
|
||||
response_list = self.interactive_handler(title, instructions, prompt_list)
|
||||
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_USERAUTH_INFO_RESPONSE))
|
||||
m.add_byte(cMSG_USERAUTH_INFO_RESPONSE)
|
||||
m.add_int(len(response_list))
|
||||
for r in response_list:
|
||||
m.add_string(r)
|
||||
|
@ -404,7 +405,7 @@ class AuthHandler (object):
|
|||
n = m.get_int()
|
||||
responses = []
|
||||
for i in range(n):
|
||||
responses.append(m.get_string())
|
||||
responses.append(m.get_text())
|
||||
result = self.transport.server_object.check_auth_interactive_response(responses)
|
||||
if isinstance(type(result), InteractiveQuery):
|
||||
# make interactive query instead of response
|
||||
|
@ -412,7 +413,6 @@ class AuthHandler (object):
|
|||
return
|
||||
self._send_auth_result(self.auth_username, 'keyboard-interactive', result)
|
||||
|
||||
|
||||
_handler_table = {
|
||||
MSG_SERVICE_REQUEST: _parse_service_request,
|
||||
MSG_SERVICE_ACCEPT: _parse_service_accept,
|
||||
|
@ -423,4 +423,3 @@ class AuthHandler (object):
|
|||
MSG_USERAUTH_INFO_REQUEST: _parse_userauth_info_request,
|
||||
MSG_USERAUTH_INFO_RESPONSE: _parse_userauth_info_response,
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -15,9 +15,10 @@
|
|||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
from paramiko.common import max_byte, zero_byte
|
||||
from paramiko.py3compat import b, byte_ord, byte_chr, long
|
||||
|
||||
|
||||
import util
|
||||
import paramiko.util as util
|
||||
|
||||
|
||||
class BERException (Exception):
|
||||
|
@ -29,13 +30,16 @@ class BER(object):
|
|||
Robey's tiny little attempt at a BER decoder.
|
||||
"""
|
||||
|
||||
def __init__(self, content=''):
|
||||
self.content = content
|
||||
def __init__(self, content=bytes()):
|
||||
self.content = b(content)
|
||||
self.idx = 0
|
||||
|
||||
def __str__(self):
|
||||
def asbytes(self):
|
||||
return self.content
|
||||
|
||||
def __str__(self):
|
||||
return self.asbytes()
|
||||
|
||||
def __repr__(self):
|
||||
return 'BER(\'' + repr(self.content) + '\')'
|
||||
|
||||
|
@ -45,13 +49,13 @@ class BER(object):
|
|||
def decode_next(self):
|
||||
if self.idx >= len(self.content):
|
||||
return None
|
||||
ident = ord(self.content[self.idx])
|
||||
ident = byte_ord(self.content[self.idx])
|
||||
self.idx += 1
|
||||
if (ident & 31) == 31:
|
||||
# identifier > 30
|
||||
ident = 0
|
||||
while self.idx < len(self.content):
|
||||
t = ord(self.content[self.idx])
|
||||
t = byte_ord(self.content[self.idx])
|
||||
self.idx += 1
|
||||
ident = (ident << 7) | (t & 0x7f)
|
||||
if not (t & 0x80):
|
||||
|
@ -59,7 +63,7 @@ class BER(object):
|
|||
if self.idx >= len(self.content):
|
||||
return None
|
||||
# now fetch length
|
||||
size = ord(self.content[self.idx])
|
||||
size = byte_ord(self.content[self.idx])
|
||||
self.idx += 1
|
||||
if size & 0x80:
|
||||
# more complimicated...
|
||||
|
@ -87,9 +91,9 @@ class BER(object):
|
|||
|
||||
def decode_sequence(data):
|
||||
out = []
|
||||
b = BER(data)
|
||||
ber = BER(data)
|
||||
while True:
|
||||
x = b.decode_next()
|
||||
x = ber.decode_next()
|
||||
if x is None:
|
||||
break
|
||||
out.append(x)
|
||||
|
@ -98,20 +102,20 @@ class BER(object):
|
|||
|
||||
def encode_tlv(self, ident, val):
|
||||
# no need to support ident > 31 here
|
||||
self.content += chr(ident)
|
||||
self.content += byte_chr(ident)
|
||||
if len(val) > 0x7f:
|
||||
lenstr = util.deflate_long(len(val))
|
||||
self.content += chr(0x80 + len(lenstr)) + lenstr
|
||||
self.content += byte_chr(0x80 + len(lenstr)) + lenstr
|
||||
else:
|
||||
self.content += chr(len(val))
|
||||
self.content += byte_chr(len(val))
|
||||
self.content += val
|
||||
|
||||
def encode(self, x):
|
||||
if type(x) is bool:
|
||||
if x:
|
||||
self.encode_tlv(1, '\xff')
|
||||
self.encode_tlv(1, max_byte)
|
||||
else:
|
||||
self.encode_tlv(1, '\x00')
|
||||
self.encode_tlv(1, zero_byte)
|
||||
elif (type(x) is int) or (type(x) is long):
|
||||
self.encode_tlv(2, util.deflate_long(x))
|
||||
elif type(x) is str:
|
||||
|
@ -122,8 +126,8 @@ class BER(object):
|
|||
raise BERException('Unknown type for encoding: %s' % repr(type(x)))
|
||||
|
||||
def encode_sequence(data):
|
||||
b = BER()
|
||||
ber = BER()
|
||||
for item in data:
|
||||
b.encode(item)
|
||||
return str(b)
|
||||
ber.encode(item)
|
||||
return ber.asbytes()
|
||||
encode_sequence = staticmethod(encode_sequence)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -17,7 +17,7 @@
|
|||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Attempt to generalize the "feeder" part of a Channel: an object which can be
|
||||
Attempt to generalize the "feeder" part of a `.Channel`: an object which can be
|
||||
read from and closed, but is reading from a buffer fed by another thread. The
|
||||
read operations are blocking and can have a timeout set.
|
||||
"""
|
||||
|
@ -25,11 +25,12 @@ read operations are blocking and can have a timeout set.
|
|||
import array
|
||||
import threading
|
||||
import time
|
||||
from paramiko.py3compat import PY2, b
|
||||
|
||||
|
||||
class PipeTimeout (IOError):
|
||||
"""
|
||||
Indicates that a timeout was reached on a read from a L{BufferedPipe}.
|
||||
Indicates that a timeout was reached on a read from a `.BufferedPipe`.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -38,7 +39,7 @@ class BufferedPipe (object):
|
|||
"""
|
||||
A buffer that obeys normal read (with timeout) & close semantics for a
|
||||
file or socket, but is fed data from another thread. This is used by
|
||||
L{Channel}.
|
||||
`.Channel`.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
@ -48,14 +49,26 @@ class BufferedPipe (object):
|
|||
self._buffer = array.array('B')
|
||||
self._closed = False
|
||||
|
||||
if PY2:
|
||||
def _buffer_frombytes(self, data):
|
||||
self._buffer.fromstring(data)
|
||||
|
||||
def _buffer_tobytes(self, limit=None):
|
||||
return self._buffer[:limit].tostring()
|
||||
else:
|
||||
def _buffer_frombytes(self, data):
|
||||
self._buffer.frombytes(data)
|
||||
|
||||
def _buffer_tobytes(self, limit=None):
|
||||
return self._buffer[:limit].tobytes()
|
||||
|
||||
def set_event(self, event):
|
||||
"""
|
||||
Set an event on this buffer. When data is ready to be read (or the
|
||||
buffer has been closed), the event will be set. When no data is
|
||||
ready, the event will be cleared.
|
||||
|
||||
@param event: the event to set/clear
|
||||
@type event: Event
|
||||
:param threading.Event event: the event to set/clear
|
||||
"""
|
||||
self._event = event
|
||||
if len(self._buffer) > 0:
|
||||
|
@ -68,14 +81,13 @@ class BufferedPipe (object):
|
|||
Feed new data into this pipe. This method is assumed to be called
|
||||
from a separate thread, so synchronization is done.
|
||||
|
||||
@param data: the data to add
|
||||
@type data: str
|
||||
:param data: the data to add, as a `str`
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
if self._event is not None:
|
||||
self._event.set()
|
||||
self._buffer.fromstring(data)
|
||||
self._buffer_frombytes(b(data))
|
||||
self._cv.notifyAll()
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
@ -83,12 +95,12 @@ class BufferedPipe (object):
|
|||
def read_ready(self):
|
||||
"""
|
||||
Returns true if data is buffered and ready to be read from this
|
||||
feeder. A C{False} result does not mean that the feeder has closed;
|
||||
feeder. A ``False`` result does not mean that the feeder has closed;
|
||||
it means you may need to wait before more data arrives.
|
||||
|
||||
@return: C{True} if a L{read} call would immediately return at least
|
||||
one byte; C{False} otherwise.
|
||||
@rtype: bool
|
||||
:return:
|
||||
``True`` if a `read` call would immediately return at least one
|
||||
byte; ``False`` otherwise.
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
|
@ -102,26 +114,24 @@ class BufferedPipe (object):
|
|||
"""
|
||||
Read data from the pipe. The return value is a string representing
|
||||
the data received. The maximum amount of data to be received at once
|
||||
is specified by C{nbytes}. If a string of length zero is returned,
|
||||
is specified by ``nbytes``. If a string of length zero is returned,
|
||||
the pipe has been closed.
|
||||
|
||||
The optional C{timeout} argument can be a nonnegative float expressing
|
||||
seconds, or C{None} for no timeout. If a float is given, a
|
||||
C{PipeTimeout} will be raised if the timeout period value has
|
||||
elapsed before any data arrives.
|
||||
The optional ``timeout`` argument can be a nonnegative float expressing
|
||||
seconds, or ``None`` for no timeout. If a float is given, a
|
||||
`.PipeTimeout` will be raised if the timeout period value has elapsed
|
||||
before any data arrives.
|
||||
|
||||
@param nbytes: maximum number of bytes to read
|
||||
@type nbytes: int
|
||||
@param timeout: maximum seconds to wait (or C{None}, the default, to
|
||||
wait forever)
|
||||
@type timeout: float
|
||||
@return: data
|
||||
@rtype: str
|
||||
:param int nbytes: maximum number of bytes to read
|
||||
:param float timeout:
|
||||
maximum seconds to wait (or ``None``, the default, to wait forever)
|
||||
:return: the read data, as a `str`
|
||||
|
||||
@raise PipeTimeout: if a timeout was specified and no data was ready
|
||||
before that timeout
|
||||
:raises PipeTimeout:
|
||||
if a timeout was specified and no data was ready before that
|
||||
timeout
|
||||
"""
|
||||
out = ''
|
||||
out = bytes()
|
||||
self._lock.acquire()
|
||||
try:
|
||||
if len(self._buffer) == 0:
|
||||
|
@ -142,12 +152,12 @@ class BufferedPipe (object):
|
|||
|
||||
# something's in the buffer and we have the lock!
|
||||
if len(self._buffer) <= nbytes:
|
||||
out = self._buffer.tostring()
|
||||
out = self._buffer_tobytes()
|
||||
del self._buffer[:]
|
||||
if (self._event is not None) and not self._closed:
|
||||
self._event.clear()
|
||||
else:
|
||||
out = self._buffer[:nbytes].tostring()
|
||||
out = self._buffer_tobytes(nbytes)
|
||||
del self._buffer[:nbytes]
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
@ -158,12 +168,13 @@ class BufferedPipe (object):
|
|||
"""
|
||||
Clear out the buffer and return all data that was in it.
|
||||
|
||||
@return: any data that was in the buffer prior to clearing it out
|
||||
@rtype: str
|
||||
:return:
|
||||
any data that was in the buffer prior to clearing it out, as a
|
||||
`str`
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
out = self._buffer.tostring()
|
||||
out = self._buffer_tobytes()
|
||||
del self._buffer[:]
|
||||
if (self._event is not None) and not self._closed:
|
||||
self._event.clear()
|
||||
|
@ -173,7 +184,7 @@ class BufferedPipe (object):
|
|||
|
||||
def close(self):
|
||||
"""
|
||||
Close this pipe object. Future calls to L{read} after the buffer
|
||||
Close this pipe object. Future calls to `read` after the buffer
|
||||
has been emptied will return immediately with an empty string.
|
||||
"""
|
||||
self._lock.acquire()
|
||||
|
@ -189,12 +200,10 @@ class BufferedPipe (object):
|
|||
"""
|
||||
Return the number of bytes buffered.
|
||||
|
||||
@return: number of bytes bufferes
|
||||
@rtype: int
|
||||
:return: number (`int`) of bytes buffered
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
return len(self._buffer)
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -17,7 +17,7 @@
|
|||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{SSHClient}.
|
||||
SSH client & key policies
|
||||
"""
|
||||
|
||||
from binascii import hexlify
|
||||
|
@ -27,9 +27,11 @@ import socket
|
|||
import warnings
|
||||
|
||||
from paramiko.agent import Agent
|
||||
from paramiko.common import *
|
||||
from paramiko.common import DEBUG
|
||||
from paramiko.config import SSH_PORT
|
||||
from paramiko.dsskey import DSSKey
|
||||
from paramiko.hostkeys import HostKeys
|
||||
from paramiko.py3compat import string_types
|
||||
from paramiko.resource import ResourceManager
|
||||
from paramiko.rsakey import RSAKey
|
||||
from paramiko.ssh_exception import SSHException, BadHostKeyException
|
||||
|
@ -37,69 +39,10 @@ from paramiko.transport import Transport
|
|||
from paramiko.util import retry_on_signal
|
||||
|
||||
|
||||
SSH_PORT = 22
|
||||
|
||||
class MissingHostKeyPolicy (object):
|
||||
"""
|
||||
Interface for defining the policy that L{SSHClient} should use when the
|
||||
SSH server's hostname is not in either the system host keys or the
|
||||
application's keys. Pre-made classes implement policies for automatically
|
||||
adding the key to the application's L{HostKeys} object (L{AutoAddPolicy}),
|
||||
and for automatically rejecting the key (L{RejectPolicy}).
|
||||
|
||||
This function may be used to ask the user to verify the key, for example.
|
||||
"""
|
||||
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
"""
|
||||
Called when an L{SSHClient} receives a server key for a server that
|
||||
isn't in either the system or local L{HostKeys} object. To accept
|
||||
the key, simply return. To reject, raised an exception (which will
|
||||
be passed to the calling application).
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class AutoAddPolicy (MissingHostKeyPolicy):
|
||||
"""
|
||||
Policy for automatically adding the hostname and new host key to the
|
||||
local L{HostKeys} object, and saving it. This is used by L{SSHClient}.
|
||||
"""
|
||||
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
client._host_keys.add(hostname, key.get_name(), key)
|
||||
if client._host_keys_filename is not None:
|
||||
client.save_host_keys(client._host_keys_filename)
|
||||
client._log(DEBUG, 'Adding %s host key for %s: %s' %
|
||||
(key.get_name(), hostname, hexlify(key.get_fingerprint())))
|
||||
|
||||
|
||||
class RejectPolicy (MissingHostKeyPolicy):
|
||||
"""
|
||||
Policy for automatically rejecting the unknown hostname & key. This is
|
||||
used by L{SSHClient}.
|
||||
"""
|
||||
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
client._log(DEBUG, 'Rejecting %s host key for %s: %s' %
|
||||
(key.get_name(), hostname, hexlify(key.get_fingerprint())))
|
||||
raise SSHException('Server %r not found in known_hosts' % hostname)
|
||||
|
||||
|
||||
class WarningPolicy (MissingHostKeyPolicy):
|
||||
"""
|
||||
Policy for logging a python-style warning for an unknown host key, but
|
||||
accepting it. This is used by L{SSHClient}.
|
||||
"""
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
warnings.warn('Unknown %s host key for %s: %s' %
|
||||
(key.get_name(), hostname, hexlify(key.get_fingerprint())))
|
||||
|
||||
|
||||
class SSHClient (object):
|
||||
"""
|
||||
A high-level representation of a session with an SSH server. This class
|
||||
wraps L{Transport}, L{Channel}, and L{SFTPClient} to take care of most
|
||||
wraps `.Transport`, `.Channel`, and `.SFTPClient` to take care of most
|
||||
aspects of authenticating and opening channels. A typical use case is::
|
||||
|
||||
client = SSHClient()
|
||||
|
@ -111,7 +54,7 @@ class SSHClient (object):
|
|||
checking. The default mechanism is to try to use local key files or an
|
||||
SSH agent (if one is running).
|
||||
|
||||
@since: 1.6
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
@ -129,22 +72,21 @@ class SSHClient (object):
|
|||
def load_system_host_keys(self, filename=None):
|
||||
"""
|
||||
Load host keys from a system (read-only) file. Host keys read with
|
||||
this method will not be saved back by L{save_host_keys}.
|
||||
this method will not be saved back by `save_host_keys`.
|
||||
|
||||
This method can be called multiple times. Each new set of host keys
|
||||
will be merged with the existing set (new replacing old if there are
|
||||
conflicts).
|
||||
|
||||
If C{filename} is left as C{None}, an attempt will be made to read
|
||||
If ``filename`` is left as ``None``, an attempt will be made to read
|
||||
keys from the user's local "known hosts" file, as used by OpenSSH,
|
||||
and no exception will be raised if the file can't be read. This is
|
||||
probably only useful on posix.
|
||||
|
||||
@param filename: the filename to read, or C{None}
|
||||
@type filename: str
|
||||
:param str filename: the filename to read, or ``None``
|
||||
|
||||
@raise IOError: if a filename was provided and the file could not be
|
||||
read
|
||||
:raises IOError:
|
||||
if a filename was provided and the file could not be read
|
||||
"""
|
||||
if filename is None:
|
||||
# try the user's .ssh key file, and mask exceptions
|
||||
|
@ -159,19 +101,18 @@ class SSHClient (object):
|
|||
def load_host_keys(self, filename):
|
||||
"""
|
||||
Load host keys from a local host-key file. Host keys read with this
|
||||
method will be checked I{after} keys loaded via L{load_system_host_keys},
|
||||
but will be saved back by L{save_host_keys} (so they can be modified).
|
||||
The missing host key policy L{AutoAddPolicy} adds keys to this set and
|
||||
method will be checked after keys loaded via `load_system_host_keys`,
|
||||
but will be saved back by `save_host_keys` (so they can be modified).
|
||||
The missing host key policy `.AutoAddPolicy` adds keys to this set and
|
||||
saves them, when connecting to a previously-unknown server.
|
||||
|
||||
This method can be called multiple times. Each new set of host keys
|
||||
will be merged with the existing set (new replacing old if there are
|
||||
conflicts). When automatically saving, the last hostname is used.
|
||||
|
||||
@param filename: the filename to read
|
||||
@type filename: str
|
||||
:param str filename: the filename to read
|
||||
|
||||
@raise IOError: if the filename could not be read
|
||||
:raises IOError: if the filename could not be read
|
||||
"""
|
||||
self._host_keys_filename = filename
|
||||
self._host_keys.load(filename)
|
||||
|
@ -179,51 +120,52 @@ class SSHClient (object):
|
|||
def save_host_keys(self, filename):
|
||||
"""
|
||||
Save the host keys back to a file. Only the host keys loaded with
|
||||
L{load_host_keys} (plus any added directly) will be saved -- not any
|
||||
host keys loaded with L{load_system_host_keys}.
|
||||
`load_host_keys` (plus any added directly) will be saved -- not any
|
||||
host keys loaded with `load_system_host_keys`.
|
||||
|
||||
@param filename: the filename to save to
|
||||
@type filename: str
|
||||
:param str filename: the filename to save to
|
||||
|
||||
@raise IOError: if the file could not be written
|
||||
:raises IOError: if the file could not be written
|
||||
"""
|
||||
f = open(filename, 'w')
|
||||
f.write('# SSH host keys collected by paramiko\n')
|
||||
for hostname, keys in self._host_keys.iteritems():
|
||||
for keytype, key in keys.iteritems():
|
||||
|
||||
# update local host keys from file (in case other SSH clients
|
||||
# have written to the known_hosts file meanwhile.
|
||||
if self._host_keys_filename is not None:
|
||||
self.load_host_keys(self._host_keys_filename)
|
||||
|
||||
with open(filename, 'w') as f:
|
||||
for hostname, keys in self._host_keys.items():
|
||||
for keytype, key in keys.items():
|
||||
f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
|
||||
f.close()
|
||||
|
||||
def get_host_keys(self):
|
||||
"""
|
||||
Get the local L{HostKeys} object. This can be used to examine the
|
||||
Get the local `.HostKeys` object. This can be used to examine the
|
||||
local host keys or change them.
|
||||
|
||||
@return: the local host keys
|
||||
@rtype: L{HostKeys}
|
||||
:return: the local host keys as a `.HostKeys` object.
|
||||
"""
|
||||
return self._host_keys
|
||||
|
||||
def set_log_channel(self, name):
|
||||
"""
|
||||
Set the channel for logging. The default is C{"paramiko.transport"}
|
||||
Set the channel for logging. The default is ``"paramiko.transport"``
|
||||
but it can be set to anything you want.
|
||||
|
||||
@param name: new channel name for logging
|
||||
@type name: str
|
||||
:param str name: new channel name for logging
|
||||
"""
|
||||
self._log_channel = name
|
||||
|
||||
def set_missing_host_key_policy(self, policy):
|
||||
"""
|
||||
Set the policy to use when connecting to a server that doesn't have a
|
||||
host key in either the system or local L{HostKeys} objects. The
|
||||
default policy is to reject all unknown servers (using L{RejectPolicy}).
|
||||
You may substitute L{AutoAddPolicy} or write your own policy class.
|
||||
host key in either the system or local `.HostKeys` objects. The
|
||||
default policy is to reject all unknown servers (using `.RejectPolicy`).
|
||||
You may substitute `.AutoAddPolicy` or write your own policy class.
|
||||
|
||||
@param policy: the policy to use when receiving a host key from a
|
||||
:param .MissingHostKeyPolicy policy:
|
||||
the policy to use when receiving a host key from a
|
||||
previously-unknown server
|
||||
@type policy: L{MissingHostKeyPolicy}
|
||||
"""
|
||||
self._policy = policy
|
||||
|
||||
|
@ -232,56 +174,49 @@ class SSHClient (object):
|
|||
compress=False, sock=None):
|
||||
"""
|
||||
Connect to an SSH server and authenticate to it. The server's host key
|
||||
is checked against the system host keys (see L{load_system_host_keys})
|
||||
and any local host keys (L{load_host_keys}). If the server's hostname
|
||||
is checked against the system host keys (see `load_system_host_keys`)
|
||||
and any local host keys (`load_host_keys`). If the server's hostname
|
||||
is not found in either set of host keys, the missing host key policy
|
||||
is used (see L{set_missing_host_key_policy}). The default policy is
|
||||
to reject the key and raise an L{SSHException}.
|
||||
is used (see `set_missing_host_key_policy`). The default policy is
|
||||
to reject the key and raise an `.SSHException`.
|
||||
|
||||
Authentication is attempted in the following order of priority:
|
||||
|
||||
- The C{pkey} or C{key_filename} passed in (if any)
|
||||
- The ``pkey`` or ``key_filename`` passed in (if any)
|
||||
- Any key we can find through an SSH agent
|
||||
- Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/}
|
||||
- Any "id_rsa" or "id_dsa" key discoverable in ``~/.ssh/``
|
||||
- Plain username/password auth, if a password was given
|
||||
|
||||
If a private key requires a password to unlock it, and a password is
|
||||
passed in, that password will be used to attempt to unlock the key.
|
||||
|
||||
@param hostname: the server to connect to
|
||||
@type hostname: str
|
||||
@param port: the server port to connect to
|
||||
@type port: int
|
||||
@param username: the username to authenticate as (defaults to the
|
||||
current local username)
|
||||
@type username: str
|
||||
@param password: a password to use for authentication or for unlocking
|
||||
a private key
|
||||
@type password: str
|
||||
@param pkey: an optional private key to use for authentication
|
||||
@type pkey: L{PKey}
|
||||
@param key_filename: the filename, or list of filenames, of optional
|
||||
private key(s) to try for authentication
|
||||
@type key_filename: str or list(str)
|
||||
@param timeout: an optional timeout (in seconds) for the TCP connect
|
||||
@type timeout: float
|
||||
@param allow_agent: set to False to disable connecting to the SSH agent
|
||||
@type allow_agent: bool
|
||||
@param look_for_keys: set to False to disable searching for discoverable
|
||||
private key files in C{~/.ssh/}
|
||||
@type look_for_keys: bool
|
||||
@param compress: set to True to turn on compression
|
||||
@type compress: bool
|
||||
@param sock: an open socket or socket-like object (such as a
|
||||
L{Channel}) to use for communication to the target host
|
||||
@type sock: socket
|
||||
:param str hostname: the server to connect to
|
||||
:param int port: the server port to connect to
|
||||
:param str username:
|
||||
the username to authenticate as (defaults to the current local
|
||||
username)
|
||||
:param str password:
|
||||
a password to use for authentication or for unlocking a private key
|
||||
:param .PKey pkey: an optional private key to use for authentication
|
||||
:param str key_filename:
|
||||
the filename, or list of filenames, of optional private key(s) to
|
||||
try for authentication
|
||||
:param float timeout: an optional timeout (in seconds) for the TCP connect
|
||||
:param bool allow_agent: set to False to disable connecting to the SSH agent
|
||||
:param bool look_for_keys:
|
||||
set to False to disable searching for discoverable private key
|
||||
files in ``~/.ssh/``
|
||||
:param bool compress: set to True to turn on compression
|
||||
:param socket sock:
|
||||
an open socket or socket-like object (such as a `.Channel`) to use
|
||||
for communication to the target host
|
||||
|
||||
@raise BadHostKeyException: if the server's host key could not be
|
||||
:raises BadHostKeyException: if the server's host key could not be
|
||||
verified
|
||||
@raise AuthenticationException: if authentication failed
|
||||
@raise SSHException: if there was any other error connecting or
|
||||
:raises AuthenticationException: if authentication failed
|
||||
:raises SSHException: if there was any other error connecting or
|
||||
establishing an SSH session
|
||||
@raise socket.error: if a socket error occurred while connecting
|
||||
:raises socket.error: if a socket error occurred while connecting
|
||||
"""
|
||||
if not sock:
|
||||
for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
|
||||
|
@ -331,7 +266,7 @@ class SSHClient (object):
|
|||
|
||||
if key_filename is None:
|
||||
key_filenames = []
|
||||
elif isinstance(key_filename, (str, unicode)):
|
||||
elif isinstance(key_filename, string_types):
|
||||
key_filenames = [key_filename]
|
||||
else:
|
||||
key_filenames = key_filename
|
||||
|
@ -339,59 +274,65 @@ class SSHClient (object):
|
|||
|
||||
def close(self):
|
||||
"""
|
||||
Close this SSHClient and its underlying L{Transport}.
|
||||
Close this SSHClient and its underlying `.Transport`.
|
||||
"""
|
||||
if self._transport is None:
|
||||
return
|
||||
self._transport.close()
|
||||
self._transport = None
|
||||
|
||||
if self._agent != None:
|
||||
if self._agent is not None:
|
||||
self._agent.close()
|
||||
self._agent = None
|
||||
|
||||
def exec_command(self, command, bufsize=-1):
|
||||
def exec_command(self, command, bufsize=-1, timeout=None, get_pty=False):
|
||||
"""
|
||||
Execute a command on the SSH server. A new L{Channel} is opened and
|
||||
Execute a command on the SSH server. A new `.Channel` is opened and
|
||||
the requested command is executed. The command's input and output
|
||||
streams are returned as python C{file}-like objects representing
|
||||
streams are returned as Python ``file``-like objects representing
|
||||
stdin, stdout, and stderr.
|
||||
|
||||
@param command: the command to execute
|
||||
@type command: str
|
||||
@param bufsize: interpreted the same way as by the built-in C{file()} function in python
|
||||
@type bufsize: int
|
||||
@return: the stdin, stdout, and stderr of the executing command
|
||||
@rtype: tuple(L{ChannelFile}, L{ChannelFile}, L{ChannelFile})
|
||||
:param str command: the command to execute
|
||||
:param int bufsize:
|
||||
interpreted the same way as by the built-in ``file()`` function in
|
||||
Python
|
||||
:param int timeout:
|
||||
set command's channel timeout. See `Channel.settimeout`.settimeout
|
||||
:return:
|
||||
the stdin, stdout, and stderr of the executing command, as a
|
||||
3-tuple
|
||||
|
||||
@raise SSHException: if the server fails to execute the command
|
||||
:raises SSHException: if the server fails to execute the command
|
||||
"""
|
||||
chan = self._transport.open_session()
|
||||
if get_pty:
|
||||
chan.get_pty()
|
||||
chan.settimeout(timeout)
|
||||
chan.exec_command(command)
|
||||
stdin = chan.makefile('wb', bufsize)
|
||||
stdout = chan.makefile('rb', bufsize)
|
||||
stderr = chan.makefile_stderr('rb', bufsize)
|
||||
stdout = chan.makefile('r', bufsize)
|
||||
stderr = chan.makefile_stderr('r', bufsize)
|
||||
return stdin, stdout, stderr
|
||||
|
||||
def invoke_shell(self, term='vt100', width=80, height=24):
|
||||
def invoke_shell(self, term='vt100', width=80, height=24, width_pixels=0,
|
||||
height_pixels=0):
|
||||
"""
|
||||
Start an interactive shell session on the SSH server. A new L{Channel}
|
||||
Start an interactive shell session on the SSH server. A new `.Channel`
|
||||
is opened and connected to a pseudo-terminal using the requested
|
||||
terminal type and size.
|
||||
|
||||
@param term: the terminal type to emulate (for example, C{"vt100"})
|
||||
@type term: str
|
||||
@param width: the width (in characters) of the terminal window
|
||||
@type width: int
|
||||
@param height: the height (in characters) of the terminal window
|
||||
@type height: int
|
||||
@return: a new channel connected to the remote shell
|
||||
@rtype: L{Channel}
|
||||
:param str term:
|
||||
the terminal type to emulate (for example, ``"vt100"``)
|
||||
:param int width: the width (in characters) of the terminal window
|
||||
:param int height: the height (in characters) of the terminal window
|
||||
:param int width_pixels: the width (in pixels) of the terminal window
|
||||
:param int height_pixels: the height (in pixels) of the terminal window
|
||||
:return: a new `.Channel` connected to the remote shell
|
||||
|
||||
@raise SSHException: if the server fails to invoke a shell
|
||||
:raises SSHException: if the server fails to invoke a shell
|
||||
"""
|
||||
chan = self._transport.open_session()
|
||||
chan.get_pty(term, width, height)
|
||||
chan.get_pty(term, width, height, width_pixels, height_pixels)
|
||||
chan.invoke_shell()
|
||||
return chan
|
||||
|
||||
|
@ -399,19 +340,17 @@ class SSHClient (object):
|
|||
"""
|
||||
Open an SFTP session on the SSH server.
|
||||
|
||||
@return: a new SFTP session object
|
||||
@rtype: L{SFTPClient}
|
||||
:return: a new `.SFTPClient` session object
|
||||
"""
|
||||
return self._transport.open_sftp_client()
|
||||
|
||||
def get_transport(self):
|
||||
"""
|
||||
Return the underlying L{Transport} object for this SSH connection.
|
||||
Return the underlying `.Transport` object for this SSH connection.
|
||||
This can be used to perform lower-level tasks, like opening specific
|
||||
kinds of channels.
|
||||
|
||||
@return: the Transport for this connection
|
||||
@rtype: L{Transport}
|
||||
:return: the `.Transport` for this connection
|
||||
"""
|
||||
return self._transport
|
||||
|
||||
|
@ -438,7 +377,7 @@ class SSHClient (object):
|
|||
two_factor = (allowed_types == ['password'])
|
||||
if not two_factor:
|
||||
return
|
||||
except SSHException, e:
|
||||
except SSHException as e:
|
||||
saved_exception = e
|
||||
|
||||
if not two_factor:
|
||||
|
@ -452,11 +391,11 @@ class SSHClient (object):
|
|||
if not two_factor:
|
||||
return
|
||||
break
|
||||
except SSHException, e:
|
||||
except SSHException as e:
|
||||
saved_exception = e
|
||||
|
||||
if not two_factor and allow_agent:
|
||||
if self._agent == None:
|
||||
if self._agent is None:
|
||||
self._agent = Agent()
|
||||
|
||||
for key in self._agent.get_keys():
|
||||
|
@ -468,7 +407,7 @@ class SSHClient (object):
|
|||
if not two_factor:
|
||||
return
|
||||
break
|
||||
except SSHException, e:
|
||||
except SSHException as e:
|
||||
saved_exception = e
|
||||
|
||||
if not two_factor:
|
||||
|
@ -500,16 +439,14 @@ class SSHClient (object):
|
|||
if not two_factor:
|
||||
return
|
||||
break
|
||||
except SSHException, e:
|
||||
saved_exception = e
|
||||
except IOError, e:
|
||||
except (SSHException, IOError) as e:
|
||||
saved_exception = e
|
||||
|
||||
if password is not None:
|
||||
try:
|
||||
self._transport.auth_password(username, password)
|
||||
return
|
||||
except SSHException, e:
|
||||
except SSHException as e:
|
||||
saved_exception = e
|
||||
elif two_factor:
|
||||
raise SSHException('Two-factor authentication requires a password')
|
||||
|
@ -522,3 +459,59 @@ class SSHClient (object):
|
|||
def _log(self, level, msg):
|
||||
self._transport._log(level, msg)
|
||||
|
||||
|
||||
class MissingHostKeyPolicy (object):
|
||||
"""
|
||||
Interface for defining the policy that `.SSHClient` should use when the
|
||||
SSH server's hostname is not in either the system host keys or the
|
||||
application's keys. Pre-made classes implement policies for automatically
|
||||
adding the key to the application's `.HostKeys` object (`.AutoAddPolicy`),
|
||||
and for automatically rejecting the key (`.RejectPolicy`).
|
||||
|
||||
This function may be used to ask the user to verify the key, for example.
|
||||
"""
|
||||
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
"""
|
||||
Called when an `.SSHClient` receives a server key for a server that
|
||||
isn't in either the system or local `.HostKeys` object. To accept
|
||||
the key, simply return. To reject, raised an exception (which will
|
||||
be passed to the calling application).
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class AutoAddPolicy (MissingHostKeyPolicy):
|
||||
"""
|
||||
Policy for automatically adding the hostname and new host key to the
|
||||
local `.HostKeys` object, and saving it. This is used by `.SSHClient`.
|
||||
"""
|
||||
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
client._host_keys.add(hostname, key.get_name(), key)
|
||||
if client._host_keys_filename is not None:
|
||||
client.save_host_keys(client._host_keys_filename)
|
||||
client._log(DEBUG, 'Adding %s host key for %s: %s' %
|
||||
(key.get_name(), hostname, hexlify(key.get_fingerprint())))
|
||||
|
||||
|
||||
class RejectPolicy (MissingHostKeyPolicy):
|
||||
"""
|
||||
Policy for automatically rejecting the unknown hostname & key. This is
|
||||
used by `.SSHClient`.
|
||||
"""
|
||||
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
client._log(DEBUG, 'Rejecting %s host key for %s: %s' %
|
||||
(key.get_name(), hostname, hexlify(key.get_fingerprint())))
|
||||
raise SSHException('Server %r not found in known_hosts' % hostname)
|
||||
|
||||
|
||||
class WarningPolicy (MissingHostKeyPolicy):
|
||||
"""
|
||||
Policy for logging a Python-style warning for an unknown host key, but
|
||||
accepting it. This is used by `.SSHClient`.
|
||||
"""
|
||||
def missing_host_key(self, client, hostname, key):
|
||||
warnings.warn('Unknown %s host key for %s: %s' %
|
||||
(key.get_name(), hostname, hexlify(key.get_fingerprint())))
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -19,6 +19,8 @@
|
|||
"""
|
||||
Common constants and global variables.
|
||||
"""
|
||||
import logging
|
||||
from paramiko.py3compat import byte_chr, PY2, bytes_types, string_types, b, long
|
||||
|
||||
MSG_DISCONNECT, MSG_IGNORE, MSG_UNIMPLEMENTED, MSG_DEBUG, MSG_SERVICE_REQUEST, \
|
||||
MSG_SERVICE_ACCEPT = range(1, 7)
|
||||
|
@ -33,6 +35,35 @@ MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, \
|
|||
MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE, MSG_CHANNEL_REQUEST, \
|
||||
MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE = range(90, 101)
|
||||
|
||||
cMSG_DISCONNECT = byte_chr(MSG_DISCONNECT)
|
||||
cMSG_IGNORE = byte_chr(MSG_IGNORE)
|
||||
cMSG_UNIMPLEMENTED = byte_chr(MSG_UNIMPLEMENTED)
|
||||
cMSG_DEBUG = byte_chr(MSG_DEBUG)
|
||||
cMSG_SERVICE_REQUEST = byte_chr(MSG_SERVICE_REQUEST)
|
||||
cMSG_SERVICE_ACCEPT = byte_chr(MSG_SERVICE_ACCEPT)
|
||||
cMSG_KEXINIT = byte_chr(MSG_KEXINIT)
|
||||
cMSG_NEWKEYS = byte_chr(MSG_NEWKEYS)
|
||||
cMSG_USERAUTH_REQUEST = byte_chr(MSG_USERAUTH_REQUEST)
|
||||
cMSG_USERAUTH_FAILURE = byte_chr(MSG_USERAUTH_FAILURE)
|
||||
cMSG_USERAUTH_SUCCESS = byte_chr(MSG_USERAUTH_SUCCESS)
|
||||
cMSG_USERAUTH_BANNER = byte_chr(MSG_USERAUTH_BANNER)
|
||||
cMSG_USERAUTH_PK_OK = byte_chr(MSG_USERAUTH_PK_OK)
|
||||
cMSG_USERAUTH_INFO_REQUEST = byte_chr(MSG_USERAUTH_INFO_REQUEST)
|
||||
cMSG_USERAUTH_INFO_RESPONSE = byte_chr(MSG_USERAUTH_INFO_RESPONSE)
|
||||
cMSG_GLOBAL_REQUEST = byte_chr(MSG_GLOBAL_REQUEST)
|
||||
cMSG_REQUEST_SUCCESS = byte_chr(MSG_REQUEST_SUCCESS)
|
||||
cMSG_REQUEST_FAILURE = byte_chr(MSG_REQUEST_FAILURE)
|
||||
cMSG_CHANNEL_OPEN = byte_chr(MSG_CHANNEL_OPEN)
|
||||
cMSG_CHANNEL_OPEN_SUCCESS = byte_chr(MSG_CHANNEL_OPEN_SUCCESS)
|
||||
cMSG_CHANNEL_OPEN_FAILURE = byte_chr(MSG_CHANNEL_OPEN_FAILURE)
|
||||
cMSG_CHANNEL_WINDOW_ADJUST = byte_chr(MSG_CHANNEL_WINDOW_ADJUST)
|
||||
cMSG_CHANNEL_DATA = byte_chr(MSG_CHANNEL_DATA)
|
||||
cMSG_CHANNEL_EXTENDED_DATA = byte_chr(MSG_CHANNEL_EXTENDED_DATA)
|
||||
cMSG_CHANNEL_EOF = byte_chr(MSG_CHANNEL_EOF)
|
||||
cMSG_CHANNEL_CLOSE = byte_chr(MSG_CHANNEL_CLOSE)
|
||||
cMSG_CHANNEL_REQUEST = byte_chr(MSG_CHANNEL_REQUEST)
|
||||
cMSG_CHANNEL_SUCCESS = byte_chr(MSG_CHANNEL_SUCCESS)
|
||||
cMSG_CHANNEL_FAILURE = byte_chr(MSG_CHANNEL_FAILURE)
|
||||
|
||||
# for debugging:
|
||||
MSG_NAMES = {
|
||||
|
@ -95,30 +126,43 @@ CONNECTION_FAILED_CODE = {
|
|||
DISCONNECT_SERVICE_NOT_AVAILABLE, DISCONNECT_AUTH_CANCELLED_BY_USER, \
|
||||
DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 7, 13, 14
|
||||
|
||||
from Crypto import Random
|
||||
zero_byte = byte_chr(0)
|
||||
one_byte = byte_chr(1)
|
||||
four_byte = byte_chr(4)
|
||||
max_byte = byte_chr(0xff)
|
||||
cr_byte = byte_chr(13)
|
||||
linefeed_byte = byte_chr(10)
|
||||
crlf = cr_byte + linefeed_byte
|
||||
|
||||
# keep a crypto-strong PRNG nearby
|
||||
rng = Random.new()
|
||||
|
||||
import sys
|
||||
if sys.version_info < (2, 3):
|
||||
try:
|
||||
import logging
|
||||
except:
|
||||
import logging22 as logging
|
||||
import select
|
||||
PY22 = True
|
||||
|
||||
import socket
|
||||
if not hasattr(socket, 'timeout'):
|
||||
class timeout(socket.error): pass
|
||||
socket.timeout = timeout
|
||||
del timeout
|
||||
if PY2:
|
||||
cr_byte_value = cr_byte
|
||||
linefeed_byte_value = linefeed_byte
|
||||
else:
|
||||
import logging
|
||||
PY22 = False
|
||||
cr_byte_value = 13
|
||||
linefeed_byte_value = 10
|
||||
|
||||
|
||||
def asbytes(s):
|
||||
if not isinstance(s, bytes_types):
|
||||
if isinstance(s, string_types):
|
||||
s = b(s)
|
||||
else:
|
||||
try:
|
||||
s = s.asbytes()
|
||||
except Exception:
|
||||
raise Exception('Unknown type')
|
||||
return s
|
||||
|
||||
xffffffff = long(0xffffffff)
|
||||
x80000000 = long(0x80000000)
|
||||
o666 = 438
|
||||
o660 = 432
|
||||
o644 = 420
|
||||
o600 = 384
|
||||
o777 = 511
|
||||
o700 = 448
|
||||
o70 = 56
|
||||
|
||||
DEBUG = logging.DEBUG
|
||||
INFO = logging.INFO
|
||||
WARNING = logging.WARNING
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright (C) 2006-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright (C) 2012 Olle Lundberg <geek@nerd.sh>
|
||||
#
|
||||
# This file is part of paramiko.
|
||||
#
|
||||
|
@ -7,7 +8,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -17,7 +18,7 @@
|
|||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{SSHConfig}.
|
||||
Configuration file (aka ``ssh_config``) support.
|
||||
"""
|
||||
|
||||
import fnmatch
|
||||
|
@ -32,28 +33,27 @@ proxy_re = re.compile(r"^(proxycommand)\s*=*\s*(.*)", re.I)
|
|||
class SSHConfig (object):
|
||||
"""
|
||||
Representation of config information as stored in the format used by
|
||||
OpenSSH. Queries can be made via L{lookup}. The format is described in
|
||||
OpenSSH's C{ssh_config} man page. This class is provided primarily as a
|
||||
OpenSSH. Queries can be made via `lookup`. The format is described in
|
||||
OpenSSH's ``ssh_config`` man page. This class is provided primarily as a
|
||||
convenience to posix users (since the OpenSSH format is a de-facto
|
||||
standard on posix) but should work fine on Windows too.
|
||||
|
||||
@since: 1.6
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Create a new OpenSSH config object.
|
||||
"""
|
||||
self._config = [ { 'host': '*' } ]
|
||||
self._config = []
|
||||
|
||||
def parse(self, file_obj):
|
||||
"""
|
||||
Read an OpenSSH config from the given file object.
|
||||
|
||||
@param file_obj: a file-like object to read the config file from
|
||||
@type file_obj: file
|
||||
:param file file_obj: a file-like object to read the config file from
|
||||
"""
|
||||
configs = [self._config[0]]
|
||||
host = {"host": ['*'], "config": {}}
|
||||
for line in file_obj:
|
||||
line = line.rstrip('\n').lstrip()
|
||||
if (line == '') or (line[0] == '#'):
|
||||
|
@ -77,65 +77,76 @@ class SSHConfig (object):
|
|||
value = line[i:].lstrip()
|
||||
|
||||
if key == 'host':
|
||||
del configs[:]
|
||||
# the value may be multiple hosts, space-delimited
|
||||
for host in value.split():
|
||||
# do we have a pre-existing host config to append to?
|
||||
matches = [c for c in self._config if c['host'] == host]
|
||||
if len(matches) > 0:
|
||||
configs.append(matches[0])
|
||||
self._config.append(host)
|
||||
value = value.split()
|
||||
host = {key: value, 'config': {}}
|
||||
#identityfile, localforward, remoteforward keys are special cases, since they are allowed to be
|
||||
# specified multiple times and they should be tried in order
|
||||
# of specification.
|
||||
|
||||
elif key in ['identityfile', 'localforward', 'remoteforward']:
|
||||
if key in host['config']:
|
||||
host['config'][key].append(value)
|
||||
else:
|
||||
config = { 'host': host }
|
||||
self._config.append(config)
|
||||
configs.append(config)
|
||||
else:
|
||||
for config in configs:
|
||||
config[key] = value
|
||||
host['config'][key] = [value]
|
||||
elif key not in host['config']:
|
||||
host['config'].update({key: value})
|
||||
self._config.append(host)
|
||||
|
||||
def lookup(self, hostname):
|
||||
"""
|
||||
Return a dict of config options for a given hostname.
|
||||
|
||||
The host-matching rules of OpenSSH's C{ssh_config} man page are used,
|
||||
The host-matching rules of OpenSSH's ``ssh_config`` man page are used,
|
||||
which means that all configuration options from matching host
|
||||
specifications are merged, with more specific hostmasks taking
|
||||
precedence. In other words, if C{"Port"} is set under C{"Host *"}
|
||||
and also C{"Host *.example.com"}, and the lookup is for
|
||||
C{"ssh.example.com"}, then the port entry for C{"Host *.example.com"}
|
||||
precedence. In other words, if ``"Port"`` is set under ``"Host *"``
|
||||
and also ``"Host *.example.com"``, and the lookup is for
|
||||
``"ssh.example.com"``, then the port entry for ``"Host *.example.com"``
|
||||
will win out.
|
||||
|
||||
The keys in the returned dict are all normalized to lowercase (look for
|
||||
C{"port"}, not C{"Port"}. No other processing is done to the keys or
|
||||
values.
|
||||
``"port"``, not ``"Port"``. The values are processed according to the
|
||||
rules for substitution variable expansion in ``ssh_config``.
|
||||
|
||||
@param hostname: the hostname to lookup
|
||||
@type hostname: str
|
||||
:param str hostname: the hostname to lookup
|
||||
"""
|
||||
matches = [x for x in self._config if fnmatch.fnmatch(hostname, x['host'])]
|
||||
# Move * to the end
|
||||
_star = matches.pop(0)
|
||||
matches.append(_star)
|
||||
matches = [config for config in self._config if
|
||||
self._allowed(hostname, config['host'])]
|
||||
|
||||
ret = {}
|
||||
for m in matches:
|
||||
for k,v in m.iteritems():
|
||||
if not k in ret:
|
||||
ret[k] = v
|
||||
for match in matches:
|
||||
for key, value in match['config'].items():
|
||||
if key not in ret:
|
||||
# Create a copy of the original value,
|
||||
# else it will reference the original list
|
||||
# in self._config and update that value too
|
||||
# when the extend() is being called.
|
||||
ret[key] = value[:]
|
||||
elif key == 'identityfile':
|
||||
ret[key].extend(value)
|
||||
ret = self._expand_variables(ret, hostname)
|
||||
del ret['host']
|
||||
return ret
|
||||
|
||||
def _allowed(self, hostname, hosts):
|
||||
match = False
|
||||
for host in hosts:
|
||||
if host.startswith('!') and fnmatch.fnmatch(hostname, host[1:]):
|
||||
return False
|
||||
elif fnmatch.fnmatch(hostname, host):
|
||||
match = True
|
||||
return match
|
||||
|
||||
def _expand_variables(self, config, hostname):
|
||||
"""
|
||||
Return a dict of config options with expanded substitutions
|
||||
for a given hostname.
|
||||
|
||||
Please refer to man ssh_config(5) for the parameters that
|
||||
Please refer to man ``ssh_config`` for the parameters that
|
||||
are replaced.
|
||||
|
||||
@param config: the config for the hostname
|
||||
@type hostname: dict
|
||||
@param hostname: the hostname that the config belongs to
|
||||
@type hostname: str
|
||||
:param dict config: the config for the hostname
|
||||
:param str hostname: the hostname that the config belongs to
|
||||
"""
|
||||
|
||||
if 'hostname' in config:
|
||||
|
@ -155,10 +166,10 @@ class SSHConfig (object):
|
|||
remoteuser = user
|
||||
|
||||
host = socket.gethostname().split('.')[0]
|
||||
fqdn = socket.getfqdn()
|
||||
fqdn = LazyFqdn(config, host)
|
||||
homedir = os.path.expanduser('~')
|
||||
replacements = {
|
||||
'controlpath': [
|
||||
replacements = {'controlpath':
|
||||
[
|
||||
('%h', config['hostname']),
|
||||
('%l', fqdn),
|
||||
('%L', host),
|
||||
|
@ -167,7 +178,8 @@ class SSHConfig (object):
|
|||
('%r', remoteuser),
|
||||
('%u', user)
|
||||
],
|
||||
'identityfile': [
|
||||
'identityfile':
|
||||
[
|
||||
('~', homedir),
|
||||
('%d', homedir),
|
||||
('%h', config['hostname']),
|
||||
|
@ -175,14 +187,75 @@ class SSHConfig (object):
|
|||
('%u', user),
|
||||
('%r', remoteuser)
|
||||
],
|
||||
'proxycommand': [
|
||||
'proxycommand':
|
||||
[
|
||||
('%h', config['hostname']),
|
||||
('%p', port),
|
||||
('%r', remoteuser),
|
||||
],
|
||||
('%r', remoteuser)
|
||||
]
|
||||
}
|
||||
|
||||
for k in config:
|
||||
if k in replacements:
|
||||
for find, replace in replacements[k]:
|
||||
if isinstance(config[k], list):
|
||||
for item in range(len(config[k])):
|
||||
config[k][item] = config[k][item].\
|
||||
replace(find, str(replace))
|
||||
else:
|
||||
config[k] = config[k].replace(find, str(replace))
|
||||
return config
|
||||
|
||||
|
||||
class LazyFqdn(object):
|
||||
"""
|
||||
Returns the host's fqdn on request as string.
|
||||
"""
|
||||
|
||||
def __init__(self, config, host=None):
|
||||
self.fqdn = None
|
||||
self.config = config
|
||||
self.host = host
|
||||
|
||||
def __str__(self):
|
||||
if self.fqdn is None:
|
||||
#
|
||||
# If the SSH config contains AddressFamily, use that when
|
||||
# determining the local host's FQDN. Using socket.getfqdn() from
|
||||
# the standard library is the most general solution, but can
|
||||
# result in noticeable delays on some platforms when IPv6 is
|
||||
# misconfigured or not available, as it calls getaddrinfo with no
|
||||
# address family specified, so both IPv4 and IPv6 are checked.
|
||||
#
|
||||
|
||||
# Handle specific option
|
||||
fqdn = None
|
||||
address_family = self.config.get('addressfamily', 'any').lower()
|
||||
if address_family != 'any':
|
||||
try:
|
||||
family = socket.AF_INET if address_family == 'inet' \
|
||||
else socket.AF_INET6
|
||||
results = socket.getaddrinfo(
|
||||
self.host,
|
||||
None,
|
||||
family,
|
||||
socket.SOCK_DGRAM,
|
||||
socket.IPPROTO_IP,
|
||||
socket.AI_CANONNAME
|
||||
)
|
||||
for res in results:
|
||||
af, socktype, proto, canonname, sa = res
|
||||
if canonname and '.' in canonname:
|
||||
fqdn = canonname
|
||||
break
|
||||
# giaerror -> socket.getaddrinfo() can't resolve self.host
|
||||
# (which is from socket.gethostname()). Fall back to the
|
||||
# getfqdn() call below.
|
||||
except socket.gaierror:
|
||||
pass
|
||||
# Handle 'any' / unspecified
|
||||
if fqdn is None:
|
||||
fqdn = socket.getfqdn()
|
||||
# Cache
|
||||
self.fqdn = fqdn
|
||||
return self.fqdn
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -17,14 +17,17 @@
|
|||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{DSSKey}
|
||||
DSS keys.
|
||||
"""
|
||||
|
||||
from Crypto.PublicKey import DSA
|
||||
from Crypto.Hash import SHA
|
||||
import os
|
||||
from hashlib import sha1
|
||||
|
||||
from Crypto.PublicKey import DSA
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.common import zero_byte
|
||||
from paramiko.py3compat import long
|
||||
from paramiko.ssh_exception import SSHException
|
||||
from paramiko.message import Message
|
||||
from paramiko.ber import BER, BERException
|
||||
|
@ -56,7 +59,7 @@ class DSSKey (PKey):
|
|||
else:
|
||||
if msg is None:
|
||||
raise SSHException('Key object may not be empty')
|
||||
if msg.get_string() != 'ssh-dss':
|
||||
if msg.get_text() != 'ssh-dss':
|
||||
raise SSHException('Invalid key')
|
||||
self.p = msg.get_mpint()
|
||||
self.q = msg.get_mpint()
|
||||
|
@ -64,14 +67,17 @@ class DSSKey (PKey):
|
|||
self.y = msg.get_mpint()
|
||||
self.size = util.bit_length(self.p)
|
||||
|
||||
def __str__(self):
|
||||
def asbytes(self):
|
||||
m = Message()
|
||||
m.add_string('ssh-dss')
|
||||
m.add_mpint(self.p)
|
||||
m.add_mpint(self.q)
|
||||
m.add_mpint(self.g)
|
||||
m.add_mpint(self.y)
|
||||
return str(m)
|
||||
return m.asbytes()
|
||||
|
||||
def __str__(self):
|
||||
return self.asbytes()
|
||||
|
||||
def __hash__(self):
|
||||
h = hash(self.get_name())
|
||||
|
@ -91,13 +97,13 @@ class DSSKey (PKey):
|
|||
def can_sign(self):
|
||||
return self.x is not None
|
||||
|
||||
def sign_ssh_data(self, rng, data):
|
||||
digest = SHA.new(data).digest()
|
||||
def sign_ssh_data(self, data):
|
||||
digest = sha1(data).digest()
|
||||
dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q), long(self.x)))
|
||||
# generate a suitable k
|
||||
qsize = len(util.deflate_long(self.q, 0))
|
||||
while True:
|
||||
k = util.inflate_long(rng.read(qsize), 1)
|
||||
k = util.inflate_long(os.urandom(qsize), 1)
|
||||
if (k > 2) and (k < self.q):
|
||||
break
|
||||
r, s = dss.sign(util.inflate_long(digest, 1), k)
|
||||
|
@ -107,26 +113,26 @@ class DSSKey (PKey):
|
|||
rstr = util.deflate_long(r, 0)
|
||||
sstr = util.deflate_long(s, 0)
|
||||
if len(rstr) < 20:
|
||||
rstr = '\x00' * (20 - len(rstr)) + rstr
|
||||
rstr = zero_byte * (20 - len(rstr)) + rstr
|
||||
if len(sstr) < 20:
|
||||
sstr = '\x00' * (20 - len(sstr)) + sstr
|
||||
sstr = zero_byte * (20 - len(sstr)) + sstr
|
||||
m.add_string(rstr + sstr)
|
||||
return m
|
||||
|
||||
def verify_ssh_sig(self, data, msg):
|
||||
if len(str(msg)) == 40:
|
||||
if len(msg.asbytes()) == 40:
|
||||
# spies.com bug: signature has no header
|
||||
sig = str(msg)
|
||||
sig = msg.asbytes()
|
||||
else:
|
||||
kind = msg.get_string()
|
||||
kind = msg.get_text()
|
||||
if kind != 'ssh-dss':
|
||||
return 0
|
||||
sig = msg.get_string()
|
||||
sig = msg.get_binary()
|
||||
|
||||
# pull out (r, s) which are NOT encoded as mpints
|
||||
sigR = util.inflate_long(sig[:20], 1)
|
||||
sigS = util.inflate_long(sig[20:], 1)
|
||||
sigM = util.inflate_long(SHA.new(data).digest(), 1)
|
||||
sigM = util.inflate_long(sha1(data).digest(), 1)
|
||||
|
||||
dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q)))
|
||||
return dss.verify(sigM, (sigR, sigS))
|
||||
|
@ -140,7 +146,7 @@ class DSSKey (PKey):
|
|||
b.encode(keylist)
|
||||
except BERException:
|
||||
raise SSHException('Unable to create ber encoding of key')
|
||||
return str(b)
|
||||
return b.asbytes()
|
||||
|
||||
def write_private_key_file(self, filename, password=None):
|
||||
self._write_private_key_file('DSA', filename, self._encode_key(), password)
|
||||
|
@ -153,24 +159,20 @@ class DSSKey (PKey):
|
|||
Generate a new private DSS key. This factory function can be used to
|
||||
generate a new host key or authentication key.
|
||||
|
||||
@param bits: number of bits the generated key should be.
|
||||
@type bits: int
|
||||
@param progress_func: an optional function to call at key points in
|
||||
key generation (used by C{pyCrypto.PublicKey}).
|
||||
@type progress_func: function
|
||||
@return: new private key
|
||||
@rtype: L{DSSKey}
|
||||
:param int bits: number of bits the generated key should be.
|
||||
:param function progress_func:
|
||||
an optional function to call at key points in key generation (used
|
||||
by ``pyCrypto.PublicKey``).
|
||||
:return: new `.DSSKey` private key
|
||||
"""
|
||||
dsa = DSA.generate(bits, rng.read, progress_func)
|
||||
dsa = DSA.generate(bits, os.urandom, progress_func)
|
||||
key = DSSKey(vals=(dsa.p, dsa.q, dsa.g, dsa.y))
|
||||
key.x = dsa.x
|
||||
return key
|
||||
generate = staticmethod(generate)
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _from_private_key_file(self, filename, password):
|
||||
data = self._read_private_key_file('DSA', filename, password)
|
||||
self._decode_key(data)
|
||||
|
@ -184,8 +186,8 @@ class DSSKey (PKey):
|
|||
# DSAPrivateKey = { version = 0, p, q, g, y, x }
|
||||
try:
|
||||
keylist = BER(data).decode()
|
||||
except BERException, x:
|
||||
raise SSHException('Unable to parse key file: ' + str(x))
|
||||
except BERException as e:
|
||||
raise SSHException('Unable to parse key file: ' + str(e))
|
||||
if (type(keylist) is not list) or (len(keylist) < 6) or (keylist[0] != 0):
|
||||
raise SSHException('not a valid DSA private key file (bad ber encoding)')
|
||||
self.p = keylist[1]
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
#
|
||||
# This file is part of paramiko.
|
||||
#
|
||||
# Paramiko is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free
|
||||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted 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 Lesser General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{ECDSAKey}
|
||||
"""
|
||||
|
||||
import binascii
|
||||
from hashlib import sha256
|
||||
|
||||
from ecdsa import SigningKey, VerifyingKey, der, curves
|
||||
from ecdsa.test_pyecdsa import ECDSA
|
||||
|
||||
from paramiko.common import four_byte, one_byte
|
||||
from paramiko.message import Message
|
||||
from paramiko.pkey import PKey
|
||||
from paramiko.py3compat import byte_chr, u
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
|
||||
class ECDSAKey (PKey):
|
||||
"""
|
||||
Representation of an ECDSA key which can be used to sign and verify SSH2
|
||||
data.
|
||||
"""
|
||||
|
||||
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None):
|
||||
self.verifying_key = None
|
||||
self.signing_key = None
|
||||
if file_obj is not None:
|
||||
self._from_private_key(file_obj, password)
|
||||
return
|
||||
if filename is not None:
|
||||
self._from_private_key_file(filename, password)
|
||||
return
|
||||
if (msg is None) and (data is not None):
|
||||
msg = Message(data)
|
||||
if vals is not None:
|
||||
self.verifying_key, self.signing_key = vals
|
||||
else:
|
||||
if msg is None:
|
||||
raise SSHException('Key object may not be empty')
|
||||
if msg.get_text() != 'ecdsa-sha2-nistp256':
|
||||
raise SSHException('Invalid key')
|
||||
curvename = msg.get_text()
|
||||
if curvename != 'nistp256':
|
||||
raise SSHException("Can't handle curve of type %s" % curvename)
|
||||
|
||||
pointinfo = msg.get_binary()
|
||||
if pointinfo[0:1] != four_byte:
|
||||
raise SSHException('Point compression is being used: %s' %
|
||||
binascii.hexlify(pointinfo))
|
||||
self.verifying_key = VerifyingKey.from_string(pointinfo[1:],
|
||||
curve=curves.NIST256p)
|
||||
self.size = 256
|
||||
|
||||
def asbytes(self):
|
||||
key = self.verifying_key
|
||||
m = Message()
|
||||
m.add_string('ecdsa-sha2-nistp256')
|
||||
m.add_string('nistp256')
|
||||
|
||||
point_str = four_byte + key.to_string()
|
||||
|
||||
m.add_string(point_str)
|
||||
return m.asbytes()
|
||||
|
||||
def __str__(self):
|
||||
return self.asbytes()
|
||||
|
||||
def __hash__(self):
|
||||
h = hash(self.get_name())
|
||||
h = h * 37 + hash(self.verifying_key.pubkey.point.x())
|
||||
h = h * 37 + hash(self.verifying_key.pubkey.point.y())
|
||||
return hash(h)
|
||||
|
||||
def get_name(self):
|
||||
return 'ecdsa-sha2-nistp256'
|
||||
|
||||
def get_bits(self):
|
||||
return self.size
|
||||
|
||||
def can_sign(self):
|
||||
return self.signing_key is not None
|
||||
|
||||
def sign_ssh_data(self, data):
|
||||
sig = self.signing_key.sign_deterministic(
|
||||
data, sigencode=self._sigencode, hashfunc=sha256)
|
||||
m = Message()
|
||||
m.add_string('ecdsa-sha2-nistp256')
|
||||
m.add_string(sig)
|
||||
return m
|
||||
|
||||
def verify_ssh_sig(self, data, msg):
|
||||
if msg.get_text() != 'ecdsa-sha2-nistp256':
|
||||
return False
|
||||
sig = msg.get_binary()
|
||||
|
||||
# verify the signature by SHA'ing the data and encrypting it
|
||||
# using the public key.
|
||||
hash_obj = sha256(data).digest()
|
||||
return self.verifying_key.verify_digest(sig, hash_obj,
|
||||
sigdecode=self._sigdecode)
|
||||
|
||||
def write_private_key_file(self, filename, password=None):
|
||||
key = self.signing_key or self.verifying_key
|
||||
self._write_private_key_file('EC', filename, key.to_der(), password)
|
||||
|
||||
def write_private_key(self, file_obj, password=None):
|
||||
key = self.signing_key or self.verifying_key
|
||||
self._write_private_key('EC', file_obj, key.to_der(), password)
|
||||
|
||||
def generate(bits, progress_func=None):
|
||||
"""
|
||||
Generate a new private RSA key. This factory function can be used to
|
||||
generate a new host key or authentication key.
|
||||
|
||||
@param bits: number of bits the generated key should be.
|
||||
@type bits: int
|
||||
@param progress_func: an optional function to call at key points in
|
||||
key generation (used by C{pyCrypto.PublicKey}).
|
||||
@type progress_func: function
|
||||
@return: new private key
|
||||
@rtype: L{RSAKey}
|
||||
"""
|
||||
signing_key = ECDSA.generate()
|
||||
key = ECDSAKey(vals=(signing_key, signing_key.get_verifying_key()))
|
||||
return key
|
||||
generate = staticmethod(generate)
|
||||
|
||||
### internals...
|
||||
|
||||
def _from_private_key_file(self, filename, password):
|
||||
data = self._read_private_key_file('EC', filename, password)
|
||||
self._decode_key(data)
|
||||
|
||||
def _from_private_key(self, file_obj, password):
|
||||
data = self._read_private_key('EC', file_obj, password)
|
||||
self._decode_key(data)
|
||||
|
||||
ALLOWED_PADDINGS = [one_byte, byte_chr(2) * 2, byte_chr(3) * 3, byte_chr(4) * 4,
|
||||
byte_chr(5) * 5, byte_chr(6) * 6, byte_chr(7) * 7]
|
||||
|
||||
def _decode_key(self, data):
|
||||
s, padding = der.remove_sequence(data)
|
||||
if padding:
|
||||
if padding not in self.ALLOWED_PADDINGS:
|
||||
raise ValueError("weird padding: %s" % u(binascii.hexlify(data)))
|
||||
data = data[:-len(padding)]
|
||||
key = SigningKey.from_der(data)
|
||||
self.signing_key = key
|
||||
self.verifying_key = key.get_verifying_key()
|
||||
self.size = 256
|
||||
|
||||
def _sigencode(self, r, s, order):
|
||||
msg = Message()
|
||||
msg.add_mpint(r)
|
||||
msg.add_mpint(s)
|
||||
return msg.asbytes()
|
||||
|
||||
def _sigdecode(self, sig, order):
|
||||
msg = Message(sig)
|
||||
r = msg.get_mpint()
|
||||
s = msg.get_mpint()
|
||||
return r, s
|
190
paramiko/file.py
190
paramiko/file.py
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -15,17 +15,14 @@
|
|||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
BufferedFile.
|
||||
"""
|
||||
|
||||
from cStringIO import StringIO
|
||||
from paramiko.common import linefeed_byte_value, crlf, cr_byte, linefeed_byte, \
|
||||
cr_byte_value
|
||||
from paramiko.py3compat import BytesIO, PY2, u, b, bytes_types
|
||||
|
||||
|
||||
class BufferedFile (object):
|
||||
"""
|
||||
Reusable base class to implement python-style file buffering around a
|
||||
Reusable base class to implement Python-style file buffering around a
|
||||
simpler stream.
|
||||
"""
|
||||
|
||||
|
@ -47,8 +44,8 @@ class BufferedFile (object):
|
|||
self.newlines = None
|
||||
self._flags = 0
|
||||
self._bufsize = self._DEFAULT_BUFSIZE
|
||||
self._wbuffer = StringIO()
|
||||
self._rbuffer = ''
|
||||
self._wbuffer = BytesIO()
|
||||
self._rbuffer = bytes()
|
||||
self._at_trailing_cr = False
|
||||
self._closed = False
|
||||
# pos - position within the file, according to the user
|
||||
|
@ -67,10 +64,7 @@ class BufferedFile (object):
|
|||
file. This iterator happens to return the file itself, since a file is
|
||||
its own iterator.
|
||||
|
||||
@raise ValueError: if the file is closed.
|
||||
|
||||
@return: an interator.
|
||||
@rtype: iterator
|
||||
:raises ValueError: if the file is closed.
|
||||
"""
|
||||
if self._closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
|
@ -89,10 +83,26 @@ class BufferedFile (object):
|
|||
buffering is not turned on.
|
||||
"""
|
||||
self._write_all(self._wbuffer.getvalue())
|
||||
self._wbuffer = StringIO()
|
||||
self._wbuffer = BytesIO()
|
||||
return
|
||||
|
||||
if PY2:
|
||||
def next(self):
|
||||
"""
|
||||
Returns the next line from the input, or raises
|
||||
`~exceptions.StopIteration` when EOF is hit. Unlike Python file
|
||||
objects, it's okay to mix calls to `next` and `readline`.
|
||||
|
||||
:raises StopIteration: when the end of the file is reached.
|
||||
|
||||
:return: a line (`str`) read from the file.
|
||||
"""
|
||||
line = self.readline()
|
||||
if not line:
|
||||
raise StopIteration
|
||||
return line
|
||||
else:
|
||||
def __next__(self):
|
||||
"""
|
||||
Returns the next line from the input, or raises L{StopIteration} when
|
||||
EOF is hit. Unlike python file objects, it's okay to mix calls to
|
||||
|
@ -110,15 +120,20 @@ class BufferedFile (object):
|
|||
|
||||
def read(self, size=None):
|
||||
"""
|
||||
Read at most C{size} bytes from the file (less if we hit the end of the
|
||||
file first). If the C{size} argument is negative or omitted, read all
|
||||
Read at most ``size`` bytes from the file (less if we hit the end of the
|
||||
file first). If the ``size`` argument is negative or omitted, read all
|
||||
the remaining data in the file.
|
||||
|
||||
@param size: maximum number of bytes to read
|
||||
@type size: int
|
||||
@return: data read from the file, or an empty string if EOF was
|
||||
.. note::
|
||||
``'b'`` mode flag is ignored (``self.FLAG_BINARY`` in
|
||||
``self._flags``), because SSH treats all files as binary, since we
|
||||
have no idea what encoding the file is in, or even if the file is
|
||||
text data.
|
||||
|
||||
:param int size: maximum number of bytes to read
|
||||
:return:
|
||||
data read from the file (as bytes), or an empty string if EOF was
|
||||
encountered immediately
|
||||
@rtype: str
|
||||
"""
|
||||
if self._closed:
|
||||
raise IOError('File is closed')
|
||||
|
@ -127,7 +142,7 @@ class BufferedFile (object):
|
|||
if (size is None) or (size < 0):
|
||||
# go for broke
|
||||
result = self._rbuffer
|
||||
self._rbuffer = ''
|
||||
self._rbuffer = bytes()
|
||||
self._pos += len(result)
|
||||
while True:
|
||||
try:
|
||||
|
@ -171,14 +186,18 @@ class BufferedFile (object):
|
|||
incomplete line may be returned. An empty string is returned only when
|
||||
EOF is encountered immediately.
|
||||
|
||||
@note: Unlike stdio's C{fgets()}, the returned string contains null
|
||||
characters (C{'\\0'}) if they occurred in the input.
|
||||
.. note::
|
||||
Unlike stdio's ``fgets``, the returned string contains null
|
||||
characters (``'\\0'``) if they occurred in the input.
|
||||
|
||||
@param size: maximum length of returned string.
|
||||
@type size: int
|
||||
@return: next line of the file, or an empty string if the end of the
|
||||
:param int size: maximum length of returned string.
|
||||
:return:
|
||||
next line of the file, or an empty string if the end of the
|
||||
file has been reached.
|
||||
@rtype: str
|
||||
|
||||
If the file was opened in binary (``'b'``) mode: bytes are returned
|
||||
Else: the encoding of the file is assumed to be UTF-8 and character
|
||||
strings (`str`) are returned
|
||||
"""
|
||||
# it's almost silly how complex this function is.
|
||||
if self._closed:
|
||||
|
@ -190,11 +209,11 @@ class BufferedFile (object):
|
|||
if self._at_trailing_cr and (self._flags & self.FLAG_UNIVERSAL_NEWLINE) and (len(line) > 0):
|
||||
# edge case: the newline may be '\r\n' and we may have read
|
||||
# only the first '\r' last time.
|
||||
if line[0] == '\n':
|
||||
if line[0] == linefeed_byte_value:
|
||||
line = line[1:]
|
||||
self._record_newline('\r\n')
|
||||
self._record_newline(crlf)
|
||||
else:
|
||||
self._record_newline('\r')
|
||||
self._record_newline(cr_byte)
|
||||
self._at_trailing_cr = False
|
||||
# check size before looking for a linefeed, in case we already have
|
||||
# enough.
|
||||
|
@ -204,84 +223,82 @@ class BufferedFile (object):
|
|||
self._rbuffer = line[size:]
|
||||
line = line[:size]
|
||||
self._pos += len(line)
|
||||
return line
|
||||
return line if self._flags & self.FLAG_BINARY else u(line)
|
||||
n = size - len(line)
|
||||
else:
|
||||
n = self._bufsize
|
||||
if ('\n' in line) or ((self._flags & self.FLAG_UNIVERSAL_NEWLINE) and ('\r' in line)):
|
||||
if (linefeed_byte in line) or ((self._flags & self.FLAG_UNIVERSAL_NEWLINE) and (cr_byte in line)):
|
||||
break
|
||||
try:
|
||||
new_data = self._read(n)
|
||||
except EOFError:
|
||||
new_data = None
|
||||
if (new_data is None) or (len(new_data) == 0):
|
||||
self._rbuffer = ''
|
||||
self._rbuffer = bytes()
|
||||
self._pos += len(line)
|
||||
return line
|
||||
return line if self._flags & self.FLAG_BINARY else u(line)
|
||||
line += new_data
|
||||
self._realpos += len(new_data)
|
||||
# find the newline
|
||||
pos = line.find('\n')
|
||||
pos = line.find(linefeed_byte)
|
||||
if self._flags & self.FLAG_UNIVERSAL_NEWLINE:
|
||||
rpos = line.find('\r')
|
||||
if (rpos >= 0) and ((rpos < pos) or (pos < 0)):
|
||||
rpos = line.find(cr_byte)
|
||||
if (rpos >= 0) and (rpos < pos or pos < 0):
|
||||
pos = rpos
|
||||
xpos = pos + 1
|
||||
if (line[pos] == '\r') and (xpos < len(line)) and (line[xpos] == '\n'):
|
||||
if (line[pos] == cr_byte_value) and (xpos < len(line)) and (line[xpos] == linefeed_byte_value):
|
||||
xpos += 1
|
||||
self._rbuffer = line[xpos:]
|
||||
lf = line[pos:xpos]
|
||||
line = line[:pos] + '\n'
|
||||
if (len(self._rbuffer) == 0) and (lf == '\r'):
|
||||
line = line[:pos] + linefeed_byte
|
||||
if (len(self._rbuffer) == 0) and (lf == cr_byte):
|
||||
# we could read the line up to a '\r' and there could still be a
|
||||
# '\n' following that we read next time. note that and eat it.
|
||||
self._at_trailing_cr = True
|
||||
else:
|
||||
self._record_newline(lf)
|
||||
self._pos += len(line)
|
||||
return line
|
||||
return line if self._flags & self.FLAG_BINARY else u(line)
|
||||
|
||||
def readlines(self, sizehint=None):
|
||||
"""
|
||||
Read all remaining lines using L{readline} and return them as a list.
|
||||
If the optional C{sizehint} argument is present, instead of reading up
|
||||
Read all remaining lines using `readline` and return them as a list.
|
||||
If the optional ``sizehint`` argument is present, instead of reading up
|
||||
to EOF, whole lines totalling approximately sizehint bytes (possibly
|
||||
after rounding up to an internal buffer size) are read.
|
||||
|
||||
@param sizehint: desired maximum number of bytes to read.
|
||||
@type sizehint: int
|
||||
@return: list of lines read from the file.
|
||||
@rtype: list
|
||||
:param int sizehint: desired maximum number of bytes to read.
|
||||
:return: `list` of lines read from the file.
|
||||
"""
|
||||
lines = []
|
||||
bytes = 0
|
||||
byte_count = 0
|
||||
while True:
|
||||
line = self.readline()
|
||||
if len(line) == 0:
|
||||
break
|
||||
lines.append(line)
|
||||
bytes += len(line)
|
||||
if (sizehint is not None) and (bytes >= sizehint):
|
||||
byte_count += len(line)
|
||||
if (sizehint is not None) and (byte_count >= sizehint):
|
||||
break
|
||||
return lines
|
||||
|
||||
def seek(self, offset, whence=0):
|
||||
"""
|
||||
Set the file's current position, like stdio's C{fseek}. Not all file
|
||||
Set the file's current position, like stdio's ``fseek``. Not all file
|
||||
objects support seeking.
|
||||
|
||||
@note: If a file is opened in append mode (C{'a'} or C{'a+'}), any seek
|
||||
.. note::
|
||||
If a file is opened in append mode (``'a'`` or ``'a+'``), any seek
|
||||
operations will be undone at the next write (as the file position
|
||||
will move back to the end of the file).
|
||||
|
||||
@param offset: position to move to within the file, relative to
|
||||
C{whence}.
|
||||
@type offset: int
|
||||
@param whence: type of movement: 0 = absolute; 1 = relative to the
|
||||
current position; 2 = relative to the end of the file.
|
||||
@type whence: int
|
||||
:param int offset:
|
||||
position to move to within the file, relative to ``whence``.
|
||||
:param int whence:
|
||||
type of movement: 0 = absolute; 1 = relative to the current
|
||||
position; 2 = relative to the end of the file.
|
||||
|
||||
@raise IOError: if the file doesn't support random access.
|
||||
:raises IOError: if the file doesn't support random access.
|
||||
"""
|
||||
raise IOError('File does not support seeking.')
|
||||
|
||||
|
@ -291,21 +308,20 @@ class BufferedFile (object):
|
|||
useful if the underlying file doesn't support random access, or was
|
||||
opened in append mode.
|
||||
|
||||
@return: file position (in bytes).
|
||||
@rtype: int
|
||||
:return: file position (`number <int>` of bytes).
|
||||
"""
|
||||
return self._pos
|
||||
|
||||
def write(self, data):
|
||||
"""
|
||||
Write data to the file. If write buffering is on (C{bufsize} was
|
||||
Write data to the file. If write buffering is on (``bufsize`` was
|
||||
specified and non-zero), some or all of the data may not actually be
|
||||
written yet. (Use L{flush} or L{close} to force buffered data to be
|
||||
written yet. (Use `flush` or `close` to force buffered data to be
|
||||
written out.)
|
||||
|
||||
@param data: data to write.
|
||||
@type data: str
|
||||
:param str data: data to write
|
||||
"""
|
||||
data = b(data)
|
||||
if self._closed:
|
||||
raise IOError('File is closed')
|
||||
if not (self._flags & self.FLAG_WRITE):
|
||||
|
@ -316,12 +332,12 @@ class BufferedFile (object):
|
|||
self._wbuffer.write(data)
|
||||
if self._flags & self.FLAG_LINE_BUFFERED:
|
||||
# only scan the new data for linefeed, to avoid wasting time.
|
||||
last_newline_pos = data.rfind('\n')
|
||||
last_newline_pos = data.rfind(linefeed_byte)
|
||||
if last_newline_pos >= 0:
|
||||
wbuf = self._wbuffer.getvalue()
|
||||
last_newline_pos += len(wbuf) - len(data)
|
||||
self._write_all(wbuf[:last_newline_pos + 1])
|
||||
self._wbuffer = StringIO()
|
||||
self._wbuffer = BytesIO()
|
||||
self._wbuffer.write(wbuf[last_newline_pos + 1:])
|
||||
return
|
||||
# even if we're line buffering, if the buffer has grown past the
|
||||
|
@ -334,11 +350,10 @@ class BufferedFile (object):
|
|||
"""
|
||||
Write a sequence of strings to the file. The sequence can be any
|
||||
iterable object producing strings, typically a list of strings. (The
|
||||
name is intended to match L{readlines}; C{writelines} does not add line
|
||||
name is intended to match `readlines`; `writelines` does not add line
|
||||
separators.)
|
||||
|
||||
@param sequence: an iterable sequence of strings.
|
||||
@type sequence: sequence
|
||||
:param iterable sequence: an iterable sequence of strings.
|
||||
"""
|
||||
for line in sequence:
|
||||
self.write(line)
|
||||
|
@ -346,48 +361,45 @@ class BufferedFile (object):
|
|||
|
||||
def xreadlines(self):
|
||||
"""
|
||||
Identical to C{iter(f)}. This is a deprecated file interface that
|
||||
predates python iterator support.
|
||||
|
||||
@return: an iterator.
|
||||
@rtype: iterator
|
||||
Identical to ``iter(f)``. This is a deprecated file interface that
|
||||
predates Python iterator support.
|
||||
"""
|
||||
return self
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._closed
|
||||
|
||||
### overrides...
|
||||
|
||||
|
||||
def _read(self, size):
|
||||
"""
|
||||
I{(subclass override)}
|
||||
Read data from the stream. Return C{None} or raise C{EOFError} to
|
||||
(subclass override)
|
||||
Read data from the stream. Return ``None`` or raise ``EOFError`` to
|
||||
indicate EOF.
|
||||
"""
|
||||
raise EOFError()
|
||||
|
||||
def _write(self, data):
|
||||
"""
|
||||
I{(subclass override)}
|
||||
(subclass override)
|
||||
Write data into the stream.
|
||||
"""
|
||||
raise IOError('write not implemented')
|
||||
|
||||
def _get_size(self):
|
||||
"""
|
||||
I{(subclass override)}
|
||||
Return the size of the file. This is called from within L{_set_mode}
|
||||
(subclass override)
|
||||
Return the size of the file. This is called from within `_set_mode`
|
||||
if the file is opened in append mode, so the file position can be
|
||||
tracked and L{seek} and L{tell} will work correctly. If the file is
|
||||
tracked and `seek` and `tell` will work correctly. If the file is
|
||||
a stream that can't be randomly accessed, you don't need to override
|
||||
this method,
|
||||
"""
|
||||
return 0
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _set_mode(self, mode='r', bufsize=-1):
|
||||
"""
|
||||
Subclasses call this method to initialize the BufferedFile.
|
||||
|
@ -415,13 +427,13 @@ class BufferedFile (object):
|
|||
self._flags |= self.FLAG_READ
|
||||
if ('w' in mode) or ('+' in mode):
|
||||
self._flags |= self.FLAG_WRITE
|
||||
if ('a' in mode):
|
||||
if 'a' in mode:
|
||||
self._flags |= self.FLAG_WRITE | self.FLAG_APPEND
|
||||
self._size = self._get_size()
|
||||
self._pos = self._realpos = self._size
|
||||
if ('b' in mode):
|
||||
if 'b' in mode:
|
||||
self._flags |= self.FLAG_BINARY
|
||||
if ('U' in mode):
|
||||
if 'U' in mode:
|
||||
self._flags |= self.FLAG_UNIVERSAL_NEWLINE
|
||||
# built-in file objects have this attribute to store which kinds of
|
||||
# line terminations they've seen:
|
||||
|
@ -450,7 +462,7 @@ class BufferedFile (object):
|
|||
return
|
||||
if self.newlines is None:
|
||||
self.newlines = newline
|
||||
elif (type(self.newlines) is str) and (self.newlines != newline):
|
||||
elif self.newlines != newline and isinstance(self.newlines, bytes_types):
|
||||
self.newlines = (self.newlines, newline)
|
||||
elif newline not in self.newlines:
|
||||
self.newlines += (newline,)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -16,109 +16,45 @@
|
|||
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{HostKeys}
|
||||
"""
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
from Crypto.Hash import SHA, HMAC
|
||||
import UserDict
|
||||
import os
|
||||
|
||||
from hashlib import sha1
|
||||
from hmac import HMAC
|
||||
|
||||
from paramiko.py3compat import b, u, encodebytes, decodebytes
|
||||
|
||||
try:
|
||||
from collections import MutableMapping
|
||||
except ImportError:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from UserDict import DictMixin as MutableMapping
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko.dsskey import DSSKey
|
||||
from paramiko.rsakey import RSAKey
|
||||
from paramiko.util import get_logger, constant_time_bytes_eq
|
||||
from paramiko.ecdsakey import ECDSAKey
|
||||
|
||||
|
||||
class InvalidHostKey(Exception):
|
||||
|
||||
def __init__(self, line, exc):
|
||||
self.line = line
|
||||
self.exc = exc
|
||||
self.args = (line, exc)
|
||||
|
||||
|
||||
class HostKeyEntry:
|
||||
class HostKeys (MutableMapping):
|
||||
"""
|
||||
Representation of a line in an OpenSSH-style "known hosts" file.
|
||||
"""
|
||||
|
||||
def __init__(self, hostnames=None, key=None):
|
||||
self.valid = (hostnames is not None) and (key is not None)
|
||||
self.hostnames = hostnames
|
||||
self.key = key
|
||||
|
||||
def from_line(cls, line):
|
||||
"""
|
||||
Parses the given line of text to find the names for the host,
|
||||
the type of key, and the key data. The line is expected to be in the
|
||||
format used by the openssh known_hosts file.
|
||||
|
||||
Lines are expected to not have leading or trailing whitespace.
|
||||
We don't bother to check for comments or empty lines. All of
|
||||
that should be taken care of before sending the line to us.
|
||||
|
||||
@param line: a line from an OpenSSH known_hosts file
|
||||
@type line: str
|
||||
"""
|
||||
fields = line.split(' ')
|
||||
if len(fields) < 3:
|
||||
# Bad number of fields
|
||||
return None
|
||||
fields = fields[:3]
|
||||
|
||||
names, keytype, key = fields
|
||||
names = names.split(',')
|
||||
|
||||
# Decide what kind of key we're looking at and create an object
|
||||
# to hold it accordingly.
|
||||
try:
|
||||
if keytype == 'ssh-rsa':
|
||||
key = RSAKey(data=base64.decodestring(key))
|
||||
elif keytype == 'ssh-dss':
|
||||
key = DSSKey(data=base64.decodestring(key))
|
||||
else:
|
||||
return None
|
||||
except binascii.Error, e:
|
||||
raise InvalidHostKey(line, e)
|
||||
|
||||
return cls(names, key)
|
||||
from_line = classmethod(from_line)
|
||||
|
||||
def to_line(self):
|
||||
"""
|
||||
Returns a string in OpenSSH known_hosts file format, or None if
|
||||
the object is not in a valid state. A trailing newline is
|
||||
included.
|
||||
"""
|
||||
if self.valid:
|
||||
return '%s %s %s\n' % (','.join(self.hostnames), self.key.get_name(),
|
||||
self.key.get_base64())
|
||||
return None
|
||||
|
||||
def __repr__(self):
|
||||
return '<HostKeyEntry %r: %r>' % (self.hostnames, self.key)
|
||||
|
||||
|
||||
class HostKeys (UserDict.DictMixin):
|
||||
"""
|
||||
Representation of an openssh-style "known hosts" file. Host keys can be
|
||||
Representation of an OpenSSH-style "known hosts" file. Host keys can be
|
||||
read from one or more files, and then individual hosts can be looked up to
|
||||
verify server keys during SSH negotiation.
|
||||
|
||||
A HostKeys object can be treated like a dict; any dict lookup is equivalent
|
||||
to calling L{lookup}.
|
||||
A `.HostKeys` object can be treated like a dict; any dict lookup is
|
||||
equivalent to calling `lookup`.
|
||||
|
||||
@since: 1.5.3
|
||||
.. versionadded:: 1.5.3
|
||||
"""
|
||||
|
||||
def __init__(self, filename=None):
|
||||
"""
|
||||
Create a new HostKeys object, optionally loading keys from an openssh
|
||||
Create a new HostKeys object, optionally loading keys from an OpenSSH
|
||||
style host-key file.
|
||||
|
||||
@param filename: filename to load host keys from, or C{None}
|
||||
@type filename: str
|
||||
:param str filename: filename to load host keys from, or ``None``
|
||||
"""
|
||||
# emulate a dict of { hostname: { keytype: PKey } }
|
||||
self._entries = []
|
||||
|
@ -128,14 +64,11 @@ class HostKeys (UserDict.DictMixin):
|
|||
def add(self, hostname, keytype, key):
|
||||
"""
|
||||
Add a host key entry to the table. Any existing entry for a
|
||||
C{(hostname, keytype)} pair will be replaced.
|
||||
``(hostname, keytype)`` pair will be replaced.
|
||||
|
||||
@param hostname: the hostname (or IP) to add
|
||||
@type hostname: str
|
||||
@param keytype: key type (C{"ssh-rsa"} or C{"ssh-dss"})
|
||||
@type keytype: str
|
||||
@param key: the key to add
|
||||
@type key: L{PKey}
|
||||
:param str hostname: the hostname (or IP) to add
|
||||
:param str keytype: key type (``"ssh-rsa"`` or ``"ssh-dss"``)
|
||||
:param .PKey key: the key to add
|
||||
"""
|
||||
for e in self._entries:
|
||||
if (hostname in e.hostnames) and (e.key.get_name() == keytype):
|
||||
|
@ -145,68 +78,81 @@ class HostKeys (UserDict.DictMixin):
|
|||
|
||||
def load(self, filename):
|
||||
"""
|
||||
Read a file of known SSH host keys, in the format used by openssh.
|
||||
Read a file of known SSH host keys, in the format used by OpenSSH.
|
||||
This type of file unfortunately doesn't exist on Windows, but on
|
||||
posix, it will usually be stored in
|
||||
C{os.path.expanduser("~/.ssh/known_hosts")}.
|
||||
``os.path.expanduser("~/.ssh/known_hosts")``.
|
||||
|
||||
If this method is called multiple times, the host keys are merged,
|
||||
not cleared. So multiple calls to C{load} will just call L{add},
|
||||
not cleared. So multiple calls to `load` will just call `add`,
|
||||
replacing any existing entries and adding new ones.
|
||||
|
||||
@param filename: name of the file to read host keys from
|
||||
@type filename: str
|
||||
:param str filename: name of the file to read host keys from
|
||||
|
||||
@raise IOError: if there was an error reading the file
|
||||
:raises IOError: if there was an error reading the file
|
||||
"""
|
||||
f = open(filename, 'r')
|
||||
for line in f:
|
||||
with open(filename, 'r') as f:
|
||||
for lineno, line in enumerate(f):
|
||||
line = line.strip()
|
||||
if (len(line) == 0) or (line[0] == '#'):
|
||||
continue
|
||||
e = HostKeyEntry.from_line(line)
|
||||
e = HostKeyEntry.from_line(line, lineno)
|
||||
if e is not None:
|
||||
_hostnames = e.hostnames
|
||||
for h in _hostnames:
|
||||
if self.check(h, e.key):
|
||||
e.hostnames.remove(h)
|
||||
if len(e.hostnames):
|
||||
self._entries.append(e)
|
||||
f.close()
|
||||
|
||||
def save(self, filename):
|
||||
"""
|
||||
Save host keys into a file, in the format used by openssh. The order of
|
||||
Save host keys into a file, in the format used by OpenSSH. The order of
|
||||
keys in the file will be preserved when possible (if these keys were
|
||||
loaded from a file originally). The single exception is that combined
|
||||
lines will be split into individual key lines, which is arguably a bug.
|
||||
|
||||
@param filename: name of the file to write
|
||||
@type filename: str
|
||||
:param str filename: name of the file to write
|
||||
|
||||
@raise IOError: if there was an error writing the file
|
||||
:raises IOError: if there was an error writing the file
|
||||
|
||||
@since: 1.6.1
|
||||
.. versionadded:: 1.6.1
|
||||
"""
|
||||
f = open(filename, 'w')
|
||||
with open(filename, 'w') as f:
|
||||
for e in self._entries:
|
||||
line = e.to_line()
|
||||
if line:
|
||||
f.write(line)
|
||||
f.close()
|
||||
|
||||
def lookup(self, hostname):
|
||||
"""
|
||||
Find a hostkey entry for a given hostname or IP. If no entry is found,
|
||||
C{None} is returned. Otherwise a dictionary of keytype to key is
|
||||
returned. The keytype will be either C{"ssh-rsa"} or C{"ssh-dss"}.
|
||||
``None`` is returned. Otherwise a dictionary of keytype to key is
|
||||
returned. The keytype will be either ``"ssh-rsa"`` or ``"ssh-dss"``.
|
||||
|
||||
@param hostname: the hostname (or IP) to lookup
|
||||
@type hostname: str
|
||||
@return: keys associated with this host (or C{None})
|
||||
@rtype: dict(str, L{PKey})
|
||||
:param str hostname: the hostname (or IP) to lookup
|
||||
:return: dict of `str` -> `.PKey` keys associated with this host (or ``None``)
|
||||
"""
|
||||
class SubDict (UserDict.DictMixin):
|
||||
class SubDict (MutableMapping):
|
||||
def __init__(self, hostname, entries, hostkeys):
|
||||
self._hostname = hostname
|
||||
self._entries = entries
|
||||
self._hostkeys = hostkeys
|
||||
|
||||
def __iter__(self):
|
||||
for k in self.keys():
|
||||
yield k
|
||||
|
||||
def __len__(self):
|
||||
return len(self.keys())
|
||||
|
||||
def __delitem__(self, key):
|
||||
for e in list(self._entries):
|
||||
if e.key.get_name() == key:
|
||||
self._entries.remove(e)
|
||||
else:
|
||||
raise KeyError(key)
|
||||
|
||||
def __getitem__(self, key):
|
||||
for e in self._entries:
|
||||
if e.key.get_name() == key:
|
||||
|
@ -233,7 +179,7 @@ class HostKeys (UserDict.DictMixin):
|
|||
entries = []
|
||||
for e in self._entries:
|
||||
for h in e.hostnames:
|
||||
if (h.startswith('|1|') and (self.hash_host(hostname, h) == h)) or (h == hostname):
|
||||
if h.startswith('|1|') and constant_time_bytes_eq(self.hash_host(hostname, h), h) or h == hostname:
|
||||
entries.append(e)
|
||||
if len(entries) == 0:
|
||||
return None
|
||||
|
@ -244,13 +190,10 @@ class HostKeys (UserDict.DictMixin):
|
|||
Return True if the given key is associated with the given hostname
|
||||
in this dictionary.
|
||||
|
||||
@param hostname: hostname (or IP) of the SSH server
|
||||
@type hostname: str
|
||||
@param key: the key to check
|
||||
@type key: L{PKey}
|
||||
@return: C{True} if the key is associated with the hostname; C{False}
|
||||
if not
|
||||
@rtype: bool
|
||||
:param str hostname: hostname (or IP) of the SSH server
|
||||
:param .PKey key: the key to check
|
||||
:return:
|
||||
``True`` if the key is associated with the hostname; else ``False``
|
||||
"""
|
||||
k = self.lookup(hostname)
|
||||
if k is None:
|
||||
|
@ -258,7 +201,7 @@ class HostKeys (UserDict.DictMixin):
|
|||
host_key = k.get(key.get_name(), None)
|
||||
if host_key is None:
|
||||
return False
|
||||
return str(host_key) == str(key)
|
||||
return host_key.asbytes() == key.asbytes()
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
|
@ -266,6 +209,16 @@ class HostKeys (UserDict.DictMixin):
|
|||
"""
|
||||
self._entries = []
|
||||
|
||||
def __iter__(self):
|
||||
for k in self.keys():
|
||||
yield k
|
||||
|
||||
def __len__(self):
|
||||
return len(self.keys())
|
||||
|
||||
def __delitem__(self, key):
|
||||
k = self[key]
|
||||
|
||||
def __getitem__(self, key):
|
||||
ret = self.lookup(key)
|
||||
if ret is None:
|
||||
|
@ -288,7 +241,7 @@ class HostKeys (UserDict.DictMixin):
|
|||
self._entries.append(HostKeyEntry([hostname], entry[key_type]))
|
||||
|
||||
def keys(self):
|
||||
# python 2.4 sets would be nice here.
|
||||
# Python 2.4 sets would be nice here.
|
||||
ret = []
|
||||
for e in self._entries:
|
||||
for h in e.hostnames:
|
||||
|
@ -304,25 +257,97 @@ class HostKeys (UserDict.DictMixin):
|
|||
|
||||
def hash_host(hostname, salt=None):
|
||||
"""
|
||||
Return a "hashed" form of the hostname, as used by openssh when storing
|
||||
Return a "hashed" form of the hostname, as used by OpenSSH when storing
|
||||
hashed hostnames in the known_hosts file.
|
||||
|
||||
@param hostname: the hostname to hash
|
||||
@type hostname: str
|
||||
@param salt: optional salt to use when hashing (must be 20 bytes long)
|
||||
@type salt: str
|
||||
@return: the hashed hostname
|
||||
@rtype: str
|
||||
:param str hostname: the hostname to hash
|
||||
:param str salt: optional salt to use when hashing (must be 20 bytes long)
|
||||
:return: the hashed hostname as a `str`
|
||||
"""
|
||||
if salt is None:
|
||||
salt = rng.read(SHA.digest_size)
|
||||
salt = os.urandom(sha1().digest_size)
|
||||
else:
|
||||
if salt.startswith('|1|'):
|
||||
salt = salt.split('|')[2]
|
||||
salt = base64.decodestring(salt)
|
||||
assert len(salt) == SHA.digest_size
|
||||
hmac = HMAC.HMAC(salt, hostname, SHA).digest()
|
||||
hostkey = '|1|%s|%s' % (base64.encodestring(salt), base64.encodestring(hmac))
|
||||
salt = decodebytes(b(salt))
|
||||
assert len(salt) == sha1().digest_size
|
||||
hmac = HMAC(salt, b(hostname), sha1).digest()
|
||||
hostkey = '|1|%s|%s' % (u(encodebytes(salt)), u(encodebytes(hmac)))
|
||||
return hostkey.replace('\n', '')
|
||||
hash_host = staticmethod(hash_host)
|
||||
|
||||
|
||||
class InvalidHostKey(Exception):
|
||||
def __init__(self, line, exc):
|
||||
self.line = line
|
||||
self.exc = exc
|
||||
self.args = (line, exc)
|
||||
|
||||
|
||||
class HostKeyEntry:
|
||||
"""
|
||||
Representation of a line in an OpenSSH-style "known hosts" file.
|
||||
"""
|
||||
|
||||
def __init__(self, hostnames=None, key=None):
|
||||
self.valid = (hostnames is not None) and (key is not None)
|
||||
self.hostnames = hostnames
|
||||
self.key = key
|
||||
|
||||
def from_line(cls, line, lineno=None):
|
||||
"""
|
||||
Parses the given line of text to find the names for the host,
|
||||
the type of key, and the key data. The line is expected to be in the
|
||||
format used by the OpenSSH known_hosts file.
|
||||
|
||||
Lines are expected to not have leading or trailing whitespace.
|
||||
We don't bother to check for comments or empty lines. All of
|
||||
that should be taken care of before sending the line to us.
|
||||
|
||||
:param str line: a line from an OpenSSH known_hosts file
|
||||
"""
|
||||
log = get_logger('paramiko.hostkeys')
|
||||
fields = line.split(' ')
|
||||
if len(fields) < 3:
|
||||
# Bad number of fields
|
||||
log.info("Not enough fields found in known_hosts in line %s (%r)" %
|
||||
(lineno, line))
|
||||
return None
|
||||
fields = fields[:3]
|
||||
|
||||
names, keytype, key = fields
|
||||
names = names.split(',')
|
||||
|
||||
# Decide what kind of key we're looking at and create an object
|
||||
# to hold it accordingly.
|
||||
try:
|
||||
key = b(key)
|
||||
if keytype == 'ssh-rsa':
|
||||
key = RSAKey(data=decodebytes(key))
|
||||
elif keytype == 'ssh-dss':
|
||||
key = DSSKey(data=decodebytes(key))
|
||||
elif keytype == 'ecdsa-sha2-nistp256':
|
||||
key = ECDSAKey(data=decodebytes(key))
|
||||
else:
|
||||
log.info("Unable to handle key of type %s" % (keytype,))
|
||||
return None
|
||||
|
||||
except binascii.Error as e:
|
||||
raise InvalidHostKey(line, e)
|
||||
|
||||
return cls(names, key)
|
||||
from_line = classmethod(from_line)
|
||||
|
||||
def to_line(self):
|
||||
"""
|
||||
Returns a string in OpenSSH known_hosts file format, or None if
|
||||
the object is not in a valid state. A trailing newline is
|
||||
included.
|
||||
"""
|
||||
if self.valid:
|
||||
return '%s %s %s\n' % (','.join(self.hostnames), self.key.get_name(),
|
||||
self.key.get_base64())
|
||||
return None
|
||||
|
||||
def __repr__(self):
|
||||
return '<HostKeyEntry %r: %r>' % (self.hostnames, self.key)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -17,22 +17,25 @@
|
|||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Variant on L{KexGroup1 <paramiko.kex_group1.KexGroup1>} where the prime "p" and
|
||||
Variant on `KexGroup1 <paramiko.kex_group1.KexGroup1>` where the prime "p" and
|
||||
generator "g" are provided by the server. A bit more work is required on the
|
||||
client side, and a B{lot} more on the server side.
|
||||
"""
|
||||
|
||||
from Crypto.Hash import SHA
|
||||
from Crypto.Util import number
|
||||
import os
|
||||
from hashlib import sha1
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.common import DEBUG
|
||||
from paramiko.message import Message
|
||||
from paramiko.py3compat import byte_chr, byte_ord, byte_mask
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
|
||||
_MSG_KEXDH_GEX_REQUEST_OLD, _MSG_KEXDH_GEX_GROUP, _MSG_KEXDH_GEX_INIT, \
|
||||
_MSG_KEXDH_GEX_REPLY, _MSG_KEXDH_GEX_REQUEST = range(30, 35)
|
||||
c_MSG_KEXDH_GEX_REQUEST_OLD, c_MSG_KEXDH_GEX_GROUP, c_MSG_KEXDH_GEX_INIT, \
|
||||
c_MSG_KEXDH_GEX_REPLY, c_MSG_KEXDH_GEX_REQUEST = [byte_chr(c) for c in range(30, 35)]
|
||||
|
||||
|
||||
class KexGex (object):
|
||||
|
@ -62,11 +65,11 @@ class KexGex (object):
|
|||
m = Message()
|
||||
if _test_old_style:
|
||||
# only used for unit tests: we shouldn't ever send this
|
||||
m.add_byte(chr(_MSG_KEXDH_GEX_REQUEST_OLD))
|
||||
m.add_byte(c_MSG_KEXDH_GEX_REQUEST_OLD)
|
||||
m.add_int(self.preferred_bits)
|
||||
self.old_style = True
|
||||
else:
|
||||
m.add_byte(chr(_MSG_KEXDH_GEX_REQUEST))
|
||||
m.add_byte(c_MSG_KEXDH_GEX_REQUEST)
|
||||
m.add_int(self.min_bits)
|
||||
m.add_int(self.preferred_bits)
|
||||
m.add_int(self.max_bits)
|
||||
|
@ -86,23 +89,21 @@ class KexGex (object):
|
|||
return self._parse_kexdh_gex_request_old(m)
|
||||
raise SSHException('KexGex asked to handle packet type %d' % ptype)
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _generate_x(self):
|
||||
# generate an "x" (1 < x < (p-1)/2).
|
||||
q = (self.p - 1) // 2
|
||||
qnorm = util.deflate_long(q, 0)
|
||||
qhbyte = ord(qnorm[0])
|
||||
bytes = len(qnorm)
|
||||
qhbyte = byte_ord(qnorm[0])
|
||||
byte_count = len(qnorm)
|
||||
qmask = 0xff
|
||||
while not (qhbyte & 0x80):
|
||||
qhbyte <<= 1
|
||||
qmask >>= 1
|
||||
while True:
|
||||
x_bytes = self.transport.rng.read(bytes)
|
||||
x_bytes = chr(ord(x_bytes[0]) & qmask) + x_bytes[1:]
|
||||
x_bytes = os.urandom(byte_count)
|
||||
x_bytes = byte_mask(x_bytes[0], qmask) + x_bytes[1:]
|
||||
x = util.inflate_long(x_bytes, 1)
|
||||
if (x > 1) and (x < q):
|
||||
break
|
||||
|
@ -135,7 +136,7 @@ class KexGex (object):
|
|||
self.transport._log(DEBUG, 'Picking p (%d <= %d <= %d bits)' % (minbits, preferredbits, maxbits))
|
||||
self.g, self.p = pack.get_modulus(minbits, preferredbits, maxbits)
|
||||
m = Message()
|
||||
m.add_byte(chr(_MSG_KEXDH_GEX_GROUP))
|
||||
m.add_byte(c_MSG_KEXDH_GEX_GROUP)
|
||||
m.add_mpint(self.p)
|
||||
m.add_mpint(self.g)
|
||||
self.transport._send_message(m)
|
||||
|
@ -156,7 +157,7 @@ class KexGex (object):
|
|||
self.transport._log(DEBUG, 'Picking p (~ %d bits)' % (self.preferred_bits,))
|
||||
self.g, self.p = pack.get_modulus(self.min_bits, self.preferred_bits, self.max_bits)
|
||||
m = Message()
|
||||
m.add_byte(chr(_MSG_KEXDH_GEX_GROUP))
|
||||
m.add_byte(c_MSG_KEXDH_GEX_GROUP)
|
||||
m.add_mpint(self.p)
|
||||
m.add_mpint(self.g)
|
||||
self.transport._send_message(m)
|
||||
|
@ -175,7 +176,7 @@ class KexGex (object):
|
|||
# now compute e = g^x mod p
|
||||
self.e = pow(self.g, self.x, self.p)
|
||||
m = Message()
|
||||
m.add_byte(chr(_MSG_KEXDH_GEX_INIT))
|
||||
m.add_byte(c_MSG_KEXDH_GEX_INIT)
|
||||
m.add_mpint(self.e)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(_MSG_KEXDH_GEX_REPLY)
|
||||
|
@ -187,7 +188,7 @@ class KexGex (object):
|
|||
self._generate_x()
|
||||
self.f = pow(self.g, self.x, self.p)
|
||||
K = pow(self.e, self.x, self.p)
|
||||
key = str(self.transport.get_server_key())
|
||||
key = self.transport.get_server_key().asbytes()
|
||||
# okay, build up the hash H of (V_C || V_S || I_C || I_S || K_S || min || n || max || p || g || e || f || K)
|
||||
hm = Message()
|
||||
hm.add(self.transport.remote_version, self.transport.local_version,
|
||||
|
@ -203,16 +204,16 @@ class KexGex (object):
|
|||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
H = SHA.new(str(hm)).digest()
|
||||
H = sha1(hm.asbytes()).digest()
|
||||
self.transport._set_K_H(K, H)
|
||||
# sign it
|
||||
sig = self.transport.get_server_key().sign_ssh_data(self.transport.rng, H)
|
||||
sig = self.transport.get_server_key().sign_ssh_data(H)
|
||||
# send reply
|
||||
m = Message()
|
||||
m.add_byte(chr(_MSG_KEXDH_GEX_REPLY))
|
||||
m.add_byte(c_MSG_KEXDH_GEX_REPLY)
|
||||
m.add_string(key)
|
||||
m.add_mpint(self.f)
|
||||
m.add_string(str(sig))
|
||||
m.add_string(sig)
|
||||
self.transport._send_message(m)
|
||||
self.transport._activate_outbound()
|
||||
|
||||
|
@ -238,6 +239,6 @@ class KexGex (object):
|
|||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
self.transport._set_K_H(K, SHA.new(str(hm)).digest())
|
||||
self.transport._set_K_H(K, sha1(hm.asbytes()).digest())
|
||||
self.transport._verify_key(host_key, sig)
|
||||
self.transport._activate_outbound()
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -21,20 +21,26 @@ Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of
|
|||
1024 bit key halves, using a known "p" prime and "g" generator.
|
||||
"""
|
||||
|
||||
from Crypto.Hash import SHA
|
||||
import os
|
||||
from hashlib import sha1
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.common import max_byte, zero_byte
|
||||
from paramiko.message import Message
|
||||
from paramiko.py3compat import byte_chr, long, byte_mask
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
|
||||
_MSG_KEXDH_INIT, _MSG_KEXDH_REPLY = range(30, 32)
|
||||
c_MSG_KEXDH_INIT, c_MSG_KEXDH_REPLY = [byte_chr(c) for c in range(30, 32)]
|
||||
|
||||
# draft-ietf-secsh-transport-09.txt, page 17
|
||||
P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFFL
|
||||
P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF
|
||||
G = 2
|
||||
|
||||
b7fffffffffffffff = byte_chr(0x7f) + max_byte * 7
|
||||
b0000000000000000 = zero_byte * 8
|
||||
|
||||
|
||||
class KexGroup1(object):
|
||||
|
||||
|
@ -42,9 +48,9 @@ class KexGroup1(object):
|
|||
|
||||
def __init__(self, transport):
|
||||
self.transport = transport
|
||||
self.x = 0L
|
||||
self.e = 0L
|
||||
self.f = 0L
|
||||
self.x = long(0)
|
||||
self.e = long(0)
|
||||
self.f = long(0)
|
||||
|
||||
def start_kex(self):
|
||||
self._generate_x()
|
||||
|
@ -56,7 +62,7 @@ class KexGroup1(object):
|
|||
# compute e = g^x mod p (where g=2), and send it
|
||||
self.e = pow(G, self.x, P)
|
||||
m = Message()
|
||||
m.add_byte(chr(_MSG_KEXDH_INIT))
|
||||
m.add_byte(c_MSG_KEXDH_INIT)
|
||||
m.add_mpint(self.e)
|
||||
self.transport._send_message(m)
|
||||
self.transport._expect_packet(_MSG_KEXDH_REPLY)
|
||||
|
@ -68,10 +74,8 @@ class KexGroup1(object):
|
|||
return self._parse_kexdh_reply(m)
|
||||
raise SSHException('KexGroup1 asked to handle packet type %d' % ptype)
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _generate_x(self):
|
||||
# generate an "x" (1 < x < q), where q is (p-1)/2.
|
||||
# p is a 128-byte (1024-bit) number, where the first 64 bits are 1.
|
||||
|
@ -79,10 +83,10 @@ class KexGroup1(object):
|
|||
# potential x where the first 63 bits are 1, because some of those will be
|
||||
# larger than q (but this is a tiny tiny subset of potential x).
|
||||
while 1:
|
||||
x_bytes = self.transport.rng.read(128)
|
||||
x_bytes = chr(ord(x_bytes[0]) & 0x7f) + x_bytes[1:]
|
||||
if (x_bytes[:8] != '\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF') and \
|
||||
(x_bytes[:8] != '\x00\x00\x00\x00\x00\x00\x00\x00'):
|
||||
x_bytes = os.urandom(128)
|
||||
x_bytes = byte_mask(x_bytes[0], 0x7f) + x_bytes[1:]
|
||||
if (x_bytes[:8] != b7fffffffffffffff and
|
||||
x_bytes[:8] != b0000000000000000):
|
||||
break
|
||||
self.x = util.inflate_long(x_bytes)
|
||||
|
||||
|
@ -92,7 +96,7 @@ class KexGroup1(object):
|
|||
self.f = m.get_mpint()
|
||||
if (self.f < 1) or (self.f > P - 1):
|
||||
raise SSHException('Server kex "f" is out of range')
|
||||
sig = m.get_string()
|
||||
sig = m.get_binary()
|
||||
K = pow(self.f, self.x, P)
|
||||
# okay, build up the hash H of (V_C || V_S || I_C || I_S || K_S || e || f || K)
|
||||
hm = Message()
|
||||
|
@ -102,7 +106,7 @@ class KexGroup1(object):
|
|||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
self.transport._set_K_H(K, SHA.new(str(hm)).digest())
|
||||
self.transport._set_K_H(K, sha1(hm.asbytes()).digest())
|
||||
self.transport._verify_key(host_key, sig)
|
||||
self.transport._activate_outbound()
|
||||
|
||||
|
@ -112,7 +116,7 @@ class KexGroup1(object):
|
|||
if (self.e < 1) or (self.e > P - 1):
|
||||
raise SSHException('Client kex "e" is out of range')
|
||||
K = pow(self.e, self.x, P)
|
||||
key = str(self.transport.get_server_key())
|
||||
key = self.transport.get_server_key().asbytes()
|
||||
# okay, build up the hash H of (V_C || V_S || I_C || I_S || K_S || e || f || K)
|
||||
hm = Message()
|
||||
hm.add(self.transport.remote_version, self.transport.local_version,
|
||||
|
@ -121,15 +125,15 @@ class KexGroup1(object):
|
|||
hm.add_mpint(self.e)
|
||||
hm.add_mpint(self.f)
|
||||
hm.add_mpint(K)
|
||||
H = SHA.new(str(hm)).digest()
|
||||
H = sha1(hm.asbytes()).digest()
|
||||
self.transport._set_K_H(K, H)
|
||||
# sign it
|
||||
sig = self.transport.get_server_key().sign_ssh_data(self.transport.rng, H)
|
||||
sig = self.transport.get_server_key().sign_ssh_data(H)
|
||||
# send reply
|
||||
m = Message()
|
||||
m.add_byte(chr(_MSG_KEXDH_REPLY))
|
||||
m.add_byte(c_MSG_KEXDH_REPLY)
|
||||
m.add_string(key)
|
||||
m.add_mpint(self.f)
|
||||
m.add_string(str(sig))
|
||||
m.add_string(sig)
|
||||
self.transport._send_message(m)
|
||||
self.transport._activate_outbound()
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
#
|
||||
# This file is part of paramiko.
|
||||
#
|
||||
# Paramiko is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free
|
||||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted 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 Lesser General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Stub out logging on python < 2.3.
|
||||
"""
|
||||
|
||||
|
||||
DEBUG = 10
|
||||
INFO = 20
|
||||
WARNING = 30
|
||||
ERROR = 40
|
||||
CRITICAL = 50
|
||||
|
||||
|
||||
def getLogger(name):
|
||||
return _logger
|
||||
|
||||
|
||||
class logger (object):
|
||||
def __init__(self):
|
||||
self.handlers = [ ]
|
||||
self.level = ERROR
|
||||
|
||||
def setLevel(self, level):
|
||||
self.level = level
|
||||
|
||||
def addHandler(self, h):
|
||||
self.handlers.append(h)
|
||||
|
||||
def addFilter(self, filter):
|
||||
pass
|
||||
|
||||
def log(self, level, text):
|
||||
if level >= self.level:
|
||||
for h in self.handlers:
|
||||
h.f.write(text + '\n')
|
||||
h.f.flush()
|
||||
|
||||
class StreamHandler (object):
|
||||
def __init__(self, f):
|
||||
self.f = f
|
||||
|
||||
def setFormatter(self, f):
|
||||
pass
|
||||
|
||||
class Formatter (object):
|
||||
def __init__(self, x, y):
|
||||
pass
|
||||
|
||||
_logger = logger()
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -21,52 +21,56 @@ Implementation of an SSH2 "message".
|
|||
"""
|
||||
|
||||
import struct
|
||||
import cStringIO
|
||||
|
||||
from paramiko import util
|
||||
from paramiko.common import zero_byte, max_byte, one_byte, asbytes
|
||||
from paramiko.py3compat import long, BytesIO, u, integer_types
|
||||
|
||||
|
||||
class Message (object):
|
||||
"""
|
||||
An SSH2 I{Message} is a stream of bytes that encodes some combination of
|
||||
strings, integers, bools, and infinite-precision integers (known in python
|
||||
as I{long}s). This class builds or breaks down such a byte stream.
|
||||
An SSH2 message is a stream of bytes that encodes some combination of
|
||||
strings, integers, bools, and infinite-precision integers (known in Python
|
||||
as longs). This class builds or breaks down such a byte stream.
|
||||
|
||||
Normally you don't need to deal with anything this low-level, but it's
|
||||
exposed for people implementing custom extensions, or features that
|
||||
paramiko doesn't support yet.
|
||||
"""
|
||||
|
||||
big_int = long(0xff000000)
|
||||
|
||||
def __init__(self, content=None):
|
||||
"""
|
||||
Create a new SSH2 Message.
|
||||
Create a new SSH2 message.
|
||||
|
||||
@param content: the byte stream to use as the Message content (passed
|
||||
in only when decomposing a Message).
|
||||
@type content: string
|
||||
:param str content:
|
||||
the byte stream to use as the message content (passed in only when
|
||||
decomposing a message).
|
||||
"""
|
||||
if content != None:
|
||||
self.packet = cStringIO.StringIO(content)
|
||||
if content is not None:
|
||||
self.packet = BytesIO(content)
|
||||
else:
|
||||
self.packet = cStringIO.StringIO()
|
||||
self.packet = BytesIO()
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Return the byte stream content of this Message, as a string.
|
||||
|
||||
@return: the contents of this Message.
|
||||
@rtype: string
|
||||
Return the byte stream content of this message, as a string/bytes obj.
|
||||
"""
|
||||
return self.packet.getvalue()
|
||||
return self.asbytes()
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Returns a string representation of this object, for debugging.
|
||||
|
||||
@rtype: string
|
||||
"""
|
||||
return 'paramiko.Message(' + repr(self.packet.getvalue()) + ')'
|
||||
|
||||
def asbytes(self):
|
||||
"""
|
||||
Return the byte stream content of this Message, as bytes.
|
||||
"""
|
||||
return self.packet.getvalue()
|
||||
|
||||
def rewind(self):
|
||||
"""
|
||||
Rewind the message to the beginning as if no items had been parsed
|
||||
|
@ -76,11 +80,8 @@ class Message (object):
|
|||
|
||||
def get_remainder(self):
|
||||
"""
|
||||
Return the bytes of this Message that haven't already been parsed and
|
||||
returned.
|
||||
|
||||
@return: a string of the bytes not parsed yet.
|
||||
@rtype: string
|
||||
Return the bytes (as a `str`) of this message that haven't already been
|
||||
parsed and returned.
|
||||
"""
|
||||
position = self.packet.tell()
|
||||
remainder = self.packet.read()
|
||||
|
@ -89,12 +90,9 @@ class Message (object):
|
|||
|
||||
def get_so_far(self):
|
||||
"""
|
||||
Returns the bytes of this Message that have been parsed and returned.
|
||||
The string passed into a Message's constructor can be regenerated by
|
||||
concatenating C{get_so_far} and L{get_remainder}.
|
||||
|
||||
@return: a string of the bytes parsed so far.
|
||||
@rtype: string
|
||||
Returns the `str` bytes of this message that have been parsed and
|
||||
returned. The string passed into a message's constructor can be
|
||||
regenerated by concatenating ``get_so_far`` and `get_remainder`.
|
||||
"""
|
||||
position = self.packet.tell()
|
||||
self.rewind()
|
||||
|
@ -102,43 +100,51 @@ class Message (object):
|
|||
|
||||
def get_bytes(self, n):
|
||||
"""
|
||||
Return the next C{n} bytes of the Message, without decomposing into
|
||||
an int, string, etc. Just the raw bytes are returned.
|
||||
|
||||
@return: a string of the next C{n} bytes of the Message, or a string
|
||||
of C{n} zero bytes, if there aren't C{n} bytes remaining.
|
||||
@rtype: string
|
||||
Return the next ``n`` bytes of the message (as a `str`), without
|
||||
decomposing into an int, decoded string, etc. Just the raw bytes are
|
||||
returned. Returns a string of ``n`` zero bytes if there weren't ``n``
|
||||
bytes remaining in the message.
|
||||
"""
|
||||
b = self.packet.read(n)
|
||||
if len(b) < n:
|
||||
return b + '\x00' * (n - len(b))
|
||||
max_pad_size = 1 << 20 # Limit padding to 1 MB
|
||||
if len(b) < n < max_pad_size:
|
||||
return b + zero_byte * (n - len(b))
|
||||
return b
|
||||
|
||||
def get_byte(self):
|
||||
"""
|
||||
Return the next byte of the Message, without decomposing it. This
|
||||
is equivalent to L{get_bytes(1)<get_bytes>}.
|
||||
Return the next byte of the message, without decomposing it. This
|
||||
is equivalent to `get_bytes(1) <get_bytes>`.
|
||||
|
||||
@return: the next byte of the Message, or C{'\000'} if there aren't
|
||||
:return:
|
||||
the next (`str`) byte of the message, or ``'\000'`` if there aren't
|
||||
any bytes remaining.
|
||||
@rtype: string
|
||||
"""
|
||||
return self.get_bytes(1)
|
||||
|
||||
def get_boolean(self):
|
||||
"""
|
||||
Fetch a boolean from the stream.
|
||||
|
||||
@return: C{True} or C{False} (from the Message).
|
||||
@rtype: bool
|
||||
"""
|
||||
b = self.get_bytes(1)
|
||||
return b != '\x00'
|
||||
return b != zero_byte
|
||||
|
||||
def get_int(self):
|
||||
"""
|
||||
Fetch an int from the stream.
|
||||
|
||||
:return: a 32-bit unsigned `int`.
|
||||
"""
|
||||
byte = self.get_bytes(1)
|
||||
if byte == max_byte:
|
||||
return util.inflate_long(self.get_binary())
|
||||
byte += self.get_bytes(3)
|
||||
return struct.unpack('>I', byte)[0]
|
||||
|
||||
def get_size(self):
|
||||
"""
|
||||
Fetch an int from the stream.
|
||||
|
||||
@return: a 32-bit unsigned integer.
|
||||
@rtype: int
|
||||
"""
|
||||
|
@ -148,8 +154,7 @@ class Message (object):
|
|||
"""
|
||||
Fetch a 64-bit int from the stream.
|
||||
|
||||
@return: a 64-bit unsigned integer.
|
||||
@rtype: long
|
||||
:return: a 64-bit unsigned integer (`long`).
|
||||
"""
|
||||
return struct.unpack('>Q', self.get_bytes(8))[0]
|
||||
|
||||
|
@ -157,12 +162,19 @@ class Message (object):
|
|||
"""
|
||||
Fetch a long int (mpint) from the stream.
|
||||
|
||||
@return: an arbitrary-length integer.
|
||||
@rtype: long
|
||||
:return: an arbitrary-length integer (`long`).
|
||||
"""
|
||||
return util.inflate_long(self.get_string())
|
||||
return util.inflate_long(self.get_binary())
|
||||
|
||||
def get_string(self):
|
||||
"""
|
||||
Fetch a `str` from the stream. This could be a byte string and may
|
||||
contain unprintable characters. (It's not unheard of for a string to
|
||||
contain another byte-stream message.)
|
||||
"""
|
||||
return self.get_bytes(self.get_size())
|
||||
|
||||
def get_text(self):
|
||||
"""
|
||||
Fetch a string from the stream. This could be a byte string and may
|
||||
contain unprintable characters. (It's not unheard of for a string to
|
||||
|
@ -171,24 +183,33 @@ class Message (object):
|
|||
@return: a string.
|
||||
@rtype: string
|
||||
"""
|
||||
return self.get_bytes(self.get_int())
|
||||
return u(self.get_bytes(self.get_size()))
|
||||
#return self.get_bytes(self.get_size())
|
||||
|
||||
def get_binary(self):
|
||||
"""
|
||||
Fetch a string from the stream. This could be a byte string and may
|
||||
contain unprintable characters. (It's not unheard of for a string to
|
||||
contain another byte-stream Message.)
|
||||
|
||||
@return: a string.
|
||||
@rtype: string
|
||||
"""
|
||||
return self.get_bytes(self.get_size())
|
||||
|
||||
def get_list(self):
|
||||
"""
|
||||
Fetch a list of strings from the stream. These are trivially encoded
|
||||
as comma-separated values in a string.
|
||||
Fetch a `list` of `strings <str>` from the stream.
|
||||
|
||||
@return: a list of strings.
|
||||
@rtype: list of strings
|
||||
These are trivially encoded as comma-separated values in a string.
|
||||
"""
|
||||
return self.get_string().split(',')
|
||||
return self.get_text().split(',')
|
||||
|
||||
def add_bytes(self, b):
|
||||
"""
|
||||
Write bytes to the stream, without any formatting.
|
||||
|
||||
@param b: bytes to add
|
||||
@type b: str
|
||||
:param str b: bytes to add
|
||||
"""
|
||||
self.packet.write(b)
|
||||
return self
|
||||
|
@ -197,8 +218,7 @@ class Message (object):
|
|||
"""
|
||||
Write a single byte to the stream, without any formatting.
|
||||
|
||||
@param b: byte to add
|
||||
@type b: str
|
||||
:param str b: byte to add
|
||||
"""
|
||||
self.packet.write(b)
|
||||
return self
|
||||
|
@ -207,22 +227,33 @@ class Message (object):
|
|||
"""
|
||||
Add a boolean value to the stream.
|
||||
|
||||
@param b: boolean value to add
|
||||
@type b: bool
|
||||
:param bool b: boolean value to add
|
||||
"""
|
||||
if b:
|
||||
self.add_byte('\x01')
|
||||
self.packet.write(one_byte)
|
||||
else:
|
||||
self.add_byte('\x00')
|
||||
self.packet.write(zero_byte)
|
||||
return self
|
||||
|
||||
def add_size(self, n):
|
||||
"""
|
||||
Add an integer to the stream.
|
||||
|
||||
:param int n: integer to add
|
||||
"""
|
||||
self.packet.write(struct.pack('>I', n))
|
||||
return self
|
||||
|
||||
def add_int(self, n):
|
||||
"""
|
||||
Add an integer to the stream.
|
||||
|
||||
@param n: integer to add
|
||||
@type n: int
|
||||
:param int n: integer to add
|
||||
"""
|
||||
if n >= Message.big_int:
|
||||
self.packet.write(max_byte)
|
||||
self.add_string(util.deflate_long(n))
|
||||
else:
|
||||
self.packet.write(struct.pack('>I', n))
|
||||
return self
|
||||
|
||||
|
@ -230,8 +261,7 @@ class Message (object):
|
|||
"""
|
||||
Add a 64-bit int to the stream.
|
||||
|
||||
@param n: long int to add
|
||||
@type n: long
|
||||
:param long n: long int to add
|
||||
"""
|
||||
self.packet.write(struct.pack('>Q', n))
|
||||
return self
|
||||
|
@ -241,8 +271,7 @@ class Message (object):
|
|||
Add a long int to the stream, encoded as an infinite-precision
|
||||
integer. This method only works on positive numbers.
|
||||
|
||||
@param z: long int to add
|
||||
@type z: long
|
||||
:param long z: long int to add
|
||||
"""
|
||||
self.add_string(util.deflate_long(z))
|
||||
return self
|
||||
|
@ -251,10 +280,10 @@ class Message (object):
|
|||
"""
|
||||
Add a string to the stream.
|
||||
|
||||
@param s: string to add
|
||||
@type s: str
|
||||
:param str s: string to add
|
||||
"""
|
||||
self.add_int(len(s))
|
||||
s = asbytes(s)
|
||||
self.add_size(len(s))
|
||||
self.packet.write(s)
|
||||
return self
|
||||
|
||||
|
@ -264,38 +293,30 @@ class Message (object):
|
|||
a single string of values separated by commas. (Yes, really, that's
|
||||
how SSH2 does it.)
|
||||
|
||||
@param l: list of strings to add
|
||||
@type l: list(str)
|
||||
:param list l: list of strings to add
|
||||
"""
|
||||
self.add_string(','.join(l))
|
||||
return self
|
||||
|
||||
def _add(self, i):
|
||||
if type(i) is str:
|
||||
return self.add_string(i)
|
||||
elif type(i) is int:
|
||||
return self.add_int(i)
|
||||
elif type(i) is long:
|
||||
if i > 0xffffffffL:
|
||||
return self.add_mpint(i)
|
||||
else:
|
||||
return self.add_int(i)
|
||||
elif type(i) is bool:
|
||||
if type(i) is bool:
|
||||
return self.add_boolean(i)
|
||||
elif isinstance(i, integer_types):
|
||||
return self.add_int(i)
|
||||
elif type(i) is list:
|
||||
return self.add_list(i)
|
||||
else:
|
||||
raise Exception('Unknown type')
|
||||
return self.add_string(i)
|
||||
|
||||
def add(self, *seq):
|
||||
"""
|
||||
Add a sequence of items to the stream. The values are encoded based
|
||||
on their type: str, int, bool, list, or long.
|
||||
|
||||
@param seq: the sequence of items
|
||||
@type seq: sequence
|
||||
.. warning::
|
||||
Longs are encoded non-deterministically. Don't use this method.
|
||||
|
||||
@bug: longs are encoded non-deterministically. Don't use this method.
|
||||
:param seq: the sequence of items
|
||||
"""
|
||||
for item in seq:
|
||||
self._add(item)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -17,33 +17,27 @@
|
|||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Packetizer.
|
||||
Packet handling
|
||||
"""
|
||||
|
||||
import errno
|
||||
import select
|
||||
import os
|
||||
import socket
|
||||
import struct
|
||||
import threading
|
||||
import time
|
||||
from hmac import HMAC
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.common import linefeed_byte, cr_byte_value, asbytes, MSG_NAMES, \
|
||||
DEBUG, xffffffff, zero_byte
|
||||
from paramiko.py3compat import u, byte_ord
|
||||
from paramiko.ssh_exception import SSHException, ProxyCommandFailure
|
||||
from paramiko.message import Message
|
||||
|
||||
|
||||
got_r_hmac = False
|
||||
try:
|
||||
import r_hmac
|
||||
got_r_hmac = True
|
||||
except ImportError:
|
||||
pass
|
||||
def compute_hmac(key, message, digest_class):
|
||||
if got_r_hmac:
|
||||
return r_hmac.HMAC(key, message, digest_class).digest()
|
||||
from Crypto.Hash import HMAC
|
||||
return HMAC.HMAC(key, message, digest_class).digest()
|
||||
return HMAC(key, message, digest_class).digest()
|
||||
|
||||
|
||||
class NeedRekeyException (Exception):
|
||||
|
@ -70,7 +64,7 @@ class Packetizer (object):
|
|||
self.__dump_packets = False
|
||||
self.__need_rekey = False
|
||||
self.__init_count = 0
|
||||
self.__remainder = ''
|
||||
self.__remainder = bytes()
|
||||
|
||||
# used for noticing when to re-key:
|
||||
self.__sent_bytes = 0
|
||||
|
@ -87,14 +81,15 @@ class Packetizer (object):
|
|||
self.__mac_size_in = 0
|
||||
self.__block_engine_out = None
|
||||
self.__block_engine_in = None
|
||||
self.__sdctr_out = False
|
||||
self.__mac_engine_out = None
|
||||
self.__mac_engine_in = None
|
||||
self.__mac_key_out = ''
|
||||
self.__mac_key_in = ''
|
||||
self.__mac_key_out = bytes()
|
||||
self.__mac_key_in = bytes()
|
||||
self.__compress_engine_out = None
|
||||
self.__compress_engine_in = None
|
||||
self.__sequence_number_out = 0L
|
||||
self.__sequence_number_in = 0L
|
||||
self.__sequence_number_out = 0
|
||||
self.__sequence_number_in = 0
|
||||
|
||||
# lock around outbound writes (packet computation)
|
||||
self.__write_lock = threading.RLock()
|
||||
|
@ -106,15 +101,16 @@ class Packetizer (object):
|
|||
|
||||
def set_log(self, log):
|
||||
"""
|
||||
Set the python log object to use for logging.
|
||||
Set the Python log object to use for logging.
|
||||
"""
|
||||
self.__logger = log
|
||||
|
||||
def set_outbound_cipher(self, block_engine, block_size, mac_engine, mac_size, mac_key):
|
||||
def set_outbound_cipher(self, block_engine, block_size, mac_engine, mac_size, mac_key, sdctr=False):
|
||||
"""
|
||||
Switch outbound data cipher.
|
||||
"""
|
||||
self.__block_engine_out = block_engine
|
||||
self.__sdctr_out = sdctr
|
||||
self.__block_size_out = block_size
|
||||
self.__mac_engine_out = mac_engine
|
||||
self.__mac_size_out = mac_size
|
||||
|
@ -170,17 +166,15 @@ class Packetizer (object):
|
|||
|
||||
def need_rekey(self):
|
||||
"""
|
||||
Returns C{True} if a new set of keys needs to be negotiated. This
|
||||
Returns ``True`` if a new set of keys needs to be negotiated. This
|
||||
will be triggered during a packet read or write, so it should be
|
||||
checked after every read or write, or at least after every few.
|
||||
|
||||
@return: C{True} if a new set of keys needs to be negotiated
|
||||
"""
|
||||
return self.__need_rekey
|
||||
|
||||
def set_keepalive(self, interval, callback):
|
||||
"""
|
||||
Turn on/off the callback keepalive. If C{interval} seconds pass with
|
||||
Turn on/off the callback keepalive. If ``interval`` seconds pass with
|
||||
no data read from or written to the socket, the callback will be
|
||||
executed and the timer will be reset.
|
||||
"""
|
||||
|
@ -192,21 +186,18 @@ class Packetizer (object):
|
|||
"""
|
||||
Read as close to N bytes as possible, blocking as long as necessary.
|
||||
|
||||
@param n: number of bytes to read
|
||||
@type n: int
|
||||
@return: the data read
|
||||
@rtype: str
|
||||
@raise EOFError: if the socket was closed before all the bytes could
|
||||
be read
|
||||
:param int n: number of bytes to read
|
||||
:return: the data read, as a `str`
|
||||
|
||||
:raises EOFError:
|
||||
if the socket was closed before all the bytes could be read
|
||||
"""
|
||||
out = ''
|
||||
out = bytes()
|
||||
# handle over-reading from reading the banner line
|
||||
if len(self.__remainder) > 0:
|
||||
out = self.__remainder[:n]
|
||||
self.__remainder = self.__remainder[n:]
|
||||
n -= len(out)
|
||||
if PY22:
|
||||
return self._py22_read_all(n, out)
|
||||
while n > 0:
|
||||
got_timeout = False
|
||||
try:
|
||||
|
@ -217,7 +208,7 @@ class Packetizer (object):
|
|||
n -= len(x)
|
||||
except socket.timeout:
|
||||
got_timeout = True
|
||||
except socket.error, e:
|
||||
except socket.error as e:
|
||||
# on Linux, sometimes instead of socket.timeout, we get
|
||||
# EAGAIN. this is a bug in recent (> 2.6.9) kernels but
|
||||
# we need to work around it.
|
||||
|
@ -246,7 +237,7 @@ class Packetizer (object):
|
|||
n = self.__socket.send(out)
|
||||
except socket.timeout:
|
||||
retry_write = True
|
||||
except socket.error, e:
|
||||
except socket.error as e:
|
||||
if (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EAGAIN):
|
||||
retry_write = True
|
||||
elif (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EINTR):
|
||||
|
@ -276,22 +267,22 @@ class Packetizer (object):
|
|||
line, so it's okay to attempt large reads.
|
||||
"""
|
||||
buf = self.__remainder
|
||||
while not '\n' in buf:
|
||||
while not linefeed_byte in buf:
|
||||
buf += self._read_timeout(timeout)
|
||||
n = buf.index('\n')
|
||||
n = buf.index(linefeed_byte)
|
||||
self.__remainder = buf[n + 1:]
|
||||
buf = buf[:n]
|
||||
if (len(buf) > 0) and (buf[-1] == '\r'):
|
||||
if (len(buf) > 0) and (buf[-1] == cr_byte_value):
|
||||
buf = buf[:-1]
|
||||
return buf
|
||||
return u(buf)
|
||||
|
||||
def send_message(self, data):
|
||||
"""
|
||||
Write a block of data using the current cipher, as an SSH block.
|
||||
"""
|
||||
# encrypt this sucka
|
||||
data = str(data)
|
||||
cmd = ord(data[0])
|
||||
data = asbytes(data)
|
||||
cmd = byte_ord(data[0])
|
||||
if cmd in MSG_NAMES:
|
||||
cmd_name = MSG_NAMES[cmd]
|
||||
else:
|
||||
|
@ -305,20 +296,20 @@ class Packetizer (object):
|
|||
if self.__dump_packets:
|
||||
self._log(DEBUG, 'Write packet <%s>, length %d' % (cmd_name, orig_len))
|
||||
self._log(DEBUG, util.format_binary(packet, 'OUT: '))
|
||||
if self.__block_engine_out != None:
|
||||
if self.__block_engine_out is not None:
|
||||
out = self.__block_engine_out.encrypt(packet)
|
||||
else:
|
||||
out = packet
|
||||
# + mac
|
||||
if self.__block_engine_out != None:
|
||||
if self.__block_engine_out is not None:
|
||||
payload = struct.pack('>I', self.__sequence_number_out) + packet
|
||||
out += compute_hmac(self.__mac_key_out, payload, self.__mac_engine_out)[:self.__mac_size_out]
|
||||
self.__sequence_number_out = (self.__sequence_number_out + 1) & 0xffffffffL
|
||||
self.__sequence_number_out = (self.__sequence_number_out + 1) & xffffffff
|
||||
self.write_all(out)
|
||||
|
||||
self.__sent_bytes += len(out)
|
||||
self.__sent_packets += 1
|
||||
if ((self.__sent_packets >= self.REKEY_PACKETS) or (self.__sent_bytes >= self.REKEY_BYTES)) \
|
||||
if (self.__sent_packets >= self.REKEY_PACKETS or self.__sent_bytes >= self.REKEY_BYTES)\
|
||||
and not self.__need_rekey:
|
||||
# only ask once for rekeying
|
||||
self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes sent)' %
|
||||
|
@ -334,14 +325,14 @@ class Packetizer (object):
|
|||
Only one thread should ever be in this function (no other locking is
|
||||
done).
|
||||
|
||||
@raise SSHException: if the packet is mangled
|
||||
@raise NeedRekeyException: if the transport should rekey
|
||||
:raises SSHException: if the packet is mangled
|
||||
:raises NeedRekeyException: if the transport should rekey
|
||||
"""
|
||||
header = self.read_all(self.__block_size_in, check_rekey=True)
|
||||
if self.__block_engine_in != None:
|
||||
if self.__block_engine_in is not None:
|
||||
header = self.__block_engine_in.decrypt(header)
|
||||
if self.__dump_packets:
|
||||
self._log(DEBUG, util.format_binary(header, 'IN: '));
|
||||
self._log(DEBUG, util.format_binary(header, 'IN: '))
|
||||
packet_size = struct.unpack('>I', header[:4])[0]
|
||||
# leftover contains decrypted bytes from the first block (after the length field)
|
||||
leftover = header[4:]
|
||||
|
@ -350,19 +341,19 @@ class Packetizer (object):
|
|||
buf = self.read_all(packet_size + self.__mac_size_in - len(leftover))
|
||||
packet = buf[:packet_size - len(leftover)]
|
||||
post_packet = buf[packet_size - len(leftover):]
|
||||
if self.__block_engine_in != None:
|
||||
if self.__block_engine_in is not None:
|
||||
packet = self.__block_engine_in.decrypt(packet)
|
||||
if self.__dump_packets:
|
||||
self._log(DEBUG, util.format_binary(packet, 'IN: '));
|
||||
self._log(DEBUG, util.format_binary(packet, 'IN: '))
|
||||
packet = leftover + packet
|
||||
|
||||
if self.__mac_size_in > 0:
|
||||
mac = post_packet[:self.__mac_size_in]
|
||||
mac_payload = struct.pack('>II', self.__sequence_number_in, packet_size) + packet
|
||||
my_mac = compute_hmac(self.__mac_key_in, mac_payload, self.__mac_engine_in)[:self.__mac_size_in]
|
||||
if my_mac != mac:
|
||||
if not util.constant_time_bytes_eq(my_mac, mac):
|
||||
raise SSHException('Mismatched MAC')
|
||||
padding = ord(packet[0])
|
||||
padding = byte_ord(packet[0])
|
||||
payload = packet[1:packet_size - padding]
|
||||
|
||||
if self.__dump_packets:
|
||||
|
@ -373,7 +364,7 @@ class Packetizer (object):
|
|||
|
||||
msg = Message(payload[1:])
|
||||
msg.seqno = self.__sequence_number_in
|
||||
self.__sequence_number_in = (self.__sequence_number_in + 1) & 0xffffffffL
|
||||
self.__sequence_number_in = (self.__sequence_number_in + 1) & xffffffff
|
||||
|
||||
# check for rekey
|
||||
raw_packet_size = packet_size + self.__mac_size_in + 4
|
||||
|
@ -396,7 +387,7 @@ class Packetizer (object):
|
|||
self.__received_packets_overflow = 0
|
||||
self._trigger_rekey()
|
||||
|
||||
cmd = ord(payload[0])
|
||||
cmd = byte_ord(payload[0])
|
||||
if cmd in MSG_NAMES:
|
||||
cmd_name = MSG_NAMES[cmd]
|
||||
else:
|
||||
|
@ -405,10 +396,8 @@ class Packetizer (object):
|
|||
self._log(DEBUG, 'Read packet <%s>, length %d' % (cmd_name, len(payload)))
|
||||
return cmd, msg
|
||||
|
||||
|
||||
########## protected
|
||||
|
||||
|
||||
def _log(self, level, msg):
|
||||
if self.__logger is None:
|
||||
return
|
||||
|
@ -428,40 +417,7 @@ class Packetizer (object):
|
|||
self.__keepalive_callback()
|
||||
self.__keepalive_last = now
|
||||
|
||||
def _py22_read_all(self, n, out):
|
||||
while n > 0:
|
||||
r, w, e = select.select([self.__socket], [], [], 0.1)
|
||||
if self.__socket not in r:
|
||||
if self.__closed:
|
||||
raise EOFError()
|
||||
self._check_keepalive()
|
||||
else:
|
||||
x = self.__socket.recv(n)
|
||||
if len(x) == 0:
|
||||
raise EOFError()
|
||||
out += x
|
||||
n -= len(x)
|
||||
return out
|
||||
|
||||
def _py22_read_timeout(self, timeout):
|
||||
start = time.time()
|
||||
while True:
|
||||
r, w, e = select.select([self.__socket], [], [], 0.1)
|
||||
if self.__socket in r:
|
||||
x = self.__socket.recv(1)
|
||||
if len(x) == 0:
|
||||
raise EOFError()
|
||||
break
|
||||
if self.__closed:
|
||||
raise EOFError()
|
||||
now = time.time()
|
||||
if now - start >= timeout:
|
||||
raise socket.timeout()
|
||||
return x
|
||||
|
||||
def _read_timeout(self, timeout):
|
||||
if PY22:
|
||||
return self._py22_read_timeout(timeout)
|
||||
start = time.time()
|
||||
while True:
|
||||
try:
|
||||
|
@ -471,9 +427,9 @@ class Packetizer (object):
|
|||
break
|
||||
except socket.timeout:
|
||||
pass
|
||||
except EnvironmentError, e:
|
||||
if ((type(e.args) is tuple) and (len(e.args) > 0) and
|
||||
(e.args[0] == errno.EINTR)):
|
||||
except EnvironmentError as e:
|
||||
if (type(e.args) is tuple and len(e.args) > 0 and
|
||||
e.args[0] == errno.EINTR):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
@ -490,12 +446,12 @@ class Packetizer (object):
|
|||
padding = 3 + bsize - ((len(payload) + 8) % bsize)
|
||||
packet = struct.pack('>IB', len(payload) + padding + 1, padding)
|
||||
packet += payload
|
||||
if self.__block_engine_out is not None:
|
||||
packet += rng.read(padding)
|
||||
else:
|
||||
# cute trick i caught openssh doing: if we're not encrypting,
|
||||
if self.__sdctr_out or self.__block_engine_out is None:
|
||||
# cute trick i caught openssh doing: if we're not encrypting or SDCTR mode (RFC4344),
|
||||
# don't waste random bytes for the padding
|
||||
packet += (chr(0) * padding)
|
||||
packet += (zero_byte * padding)
|
||||
else:
|
||||
packet += os.urandom(padding)
|
||||
return packet
|
||||
|
||||
def _trigger_rekey(self):
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -17,11 +17,12 @@
|
|||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Abstraction of a one-way pipe where the read end can be used in select().
|
||||
Normally this is trivial, but Windows makes it nearly impossible.
|
||||
Abstraction of a one-way pipe where the read end can be used in
|
||||
`select.select`. Normally this is trivial, but Windows makes it nearly
|
||||
impossible.
|
||||
|
||||
The pipe acts like an Event, which can be set or cleared. When set, the pipe
|
||||
will trigger as readable in select().
|
||||
will trigger as readable in `select <select.select>`.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
@ -63,7 +64,7 @@ class PosixPipe (object):
|
|||
if self._set or self._closed:
|
||||
return
|
||||
self._set = True
|
||||
os.write(self._wfd, '*')
|
||||
os.write(self._wfd, b'*')
|
||||
|
||||
def set_forever(self):
|
||||
self._forever = True
|
||||
|
@ -109,7 +110,7 @@ class WindowsPipe (object):
|
|||
if self._set or self._closed:
|
||||
return
|
||||
self._set = True
|
||||
self._wsock.send('*')
|
||||
self._wsock.send(b'*')
|
||||
|
||||
def set_forever (self):
|
||||
self._forever = True
|
||||
|
|
249
paramiko/pkey.py
249
paramiko/pkey.py
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -23,13 +23,13 @@ Common API for all public keys.
|
|||
import base64
|
||||
from binascii import hexlify, unhexlify
|
||||
import os
|
||||
from hashlib import md5
|
||||
|
||||
from Crypto.Hash import MD5
|
||||
from Crypto.Cipher import DES3, AES
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.message import Message
|
||||
from paramiko.common import o600, zero_byte
|
||||
from paramiko.py3compat import u, encodebytes, decodebytes, b
|
||||
from paramiko.ssh_exception import SSHException, PasswordRequiredException
|
||||
|
||||
|
||||
|
@ -44,36 +44,35 @@ class PKey (object):
|
|||
'DES-EDE3-CBC': {'cipher': DES3, 'keysize': 24, 'blocksize': 8, 'mode': DES3.MODE_CBC},
|
||||
}
|
||||
|
||||
|
||||
def __init__(self, msg=None, data=None):
|
||||
"""
|
||||
Create a new instance of this public key type. If C{msg} is given,
|
||||
Create a new instance of this public key type. If ``msg`` is given,
|
||||
the key's public part(s) will be filled in from the message. If
|
||||
C{data} is given, the key's public part(s) will be filled in from
|
||||
``data`` is given, the key's public part(s) will be filled in from
|
||||
the string.
|
||||
|
||||
@param msg: an optional SSH L{Message} containing a public key of this
|
||||
type.
|
||||
@type msg: L{Message}
|
||||
@param data: an optional string containing a public key of this type
|
||||
@type data: str
|
||||
:param .Message msg:
|
||||
an optional SSH `.Message` containing a public key of this type.
|
||||
:param str data: an optional string containing a public key of this type
|
||||
|
||||
@raise SSHException: if a key cannot be created from the C{data} or
|
||||
C{msg} given, or no key was passed in.
|
||||
:raises SSHException:
|
||||
if a key cannot be created from the ``data`` or ``msg`` given, or
|
||||
no key was passed in.
|
||||
"""
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
def asbytes(self):
|
||||
"""
|
||||
Return a string of an SSH L{Message} made up of the public part(s) of
|
||||
this key. This string is suitable for passing to L{__init__} to
|
||||
Return a string of an SSH `.Message` made up of the public part(s) of
|
||||
this key. This string is suitable for passing to `__init__` to
|
||||
re-create the key object later.
|
||||
|
||||
@return: string representation of an SSH key message.
|
||||
@rtype: str
|
||||
"""
|
||||
return ''
|
||||
return bytes()
|
||||
|
||||
def __str__(self):
|
||||
return self.asbytes()
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def __cmp__(self, other):
|
||||
"""
|
||||
Compare this key to another. Returns 0 if this key is equivalent to
|
||||
|
@ -81,24 +80,24 @@ class PKey (object):
|
|||
of the key are compared, so a public key will compare equal to its
|
||||
corresponding private key.
|
||||
|
||||
@param other: key to compare to.
|
||||
@type other: L{PKey}
|
||||
@return: 0 if the two keys are equivalent, non-0 otherwise.
|
||||
@rtype: int
|
||||
:param .Pkey other: key to compare to.
|
||||
"""
|
||||
hs = hash(self)
|
||||
ho = hash(other)
|
||||
if hs != ho:
|
||||
return cmp(hs, ho)
|
||||
return cmp(str(self), str(other))
|
||||
return cmp(self.asbytes(), other.asbytes())
|
||||
|
||||
def __eq__(self, other):
|
||||
return hash(self) == hash(other)
|
||||
|
||||
def get_name(self):
|
||||
"""
|
||||
Return the name of this private key implementation.
|
||||
|
||||
@return: name of this private key type, in SSH terminology (for
|
||||
example, C{"ssh-rsa"}).
|
||||
@rtype: str
|
||||
:return:
|
||||
name of this private key type, in SSH terminology, as a `str` (for
|
||||
example, ``"ssh-rsa"``).
|
||||
"""
|
||||
return ''
|
||||
|
||||
|
@ -107,18 +106,14 @@ class PKey (object):
|
|||
Return the number of significant bits in this key. This is useful
|
||||
for judging the relative security of a key.
|
||||
|
||||
@return: bits in the key.
|
||||
@rtype: int
|
||||
:return: bits in the key (as an `int`)
|
||||
"""
|
||||
return 0
|
||||
|
||||
def can_sign(self):
|
||||
"""
|
||||
Return C{True} if this key has the private part necessary for signing
|
||||
Return ``True`` if this key has the private part necessary for signing
|
||||
data.
|
||||
|
||||
@return: C{True} if this is a private key.
|
||||
@rtype: bool
|
||||
"""
|
||||
return False
|
||||
|
||||
|
@ -127,11 +122,11 @@ class PKey (object):
|
|||
Return an MD5 fingerprint of the public part of this key. Nothing
|
||||
secret is revealed.
|
||||
|
||||
@return: a 16-byte string (binary) of the MD5 fingerprint, in SSH
|
||||
:return:
|
||||
a 16-byte `string <str>` (binary) of the MD5 fingerprint, in SSH
|
||||
format.
|
||||
@rtype: str
|
||||
"""
|
||||
return MD5.new(str(self)).digest()
|
||||
return md5(self.asbytes()).digest()
|
||||
|
||||
def get_base64(self):
|
||||
"""
|
||||
|
@ -139,61 +134,50 @@ class PKey (object):
|
|||
secret is revealed. This format is compatible with that used to store
|
||||
public key files or recognized host keys.
|
||||
|
||||
@return: a base64 string containing the public part of the key.
|
||||
@rtype: str
|
||||
:return: a base64 `string <str>` containing the public part of the key.
|
||||
"""
|
||||
return base64.encodestring(str(self)).replace('\n', '')
|
||||
return u(encodebytes(self.asbytes())).replace('\n', '')
|
||||
|
||||
def sign_ssh_data(self, rng, data):
|
||||
def sign_ssh_data(self, data):
|
||||
"""
|
||||
Sign a blob of data with this private key, and return a L{Message}
|
||||
Sign a blob of data with this private key, and return a `.Message`
|
||||
representing an SSH signature message.
|
||||
|
||||
@param rng: a secure random number generator.
|
||||
@type rng: L{Crypto.Util.rng.RandomPool}
|
||||
@param data: the data to sign.
|
||||
@type data: str
|
||||
@return: an SSH signature message.
|
||||
@rtype: L{Message}
|
||||
:param str data: the data to sign.
|
||||
:return: an SSH signature `message <.Message>`.
|
||||
"""
|
||||
return ''
|
||||
return bytes()
|
||||
|
||||
def verify_ssh_sig(self, data, msg):
|
||||
"""
|
||||
Given a blob of data, and an SSH message representing a signature of
|
||||
that data, verify that it was signed with this key.
|
||||
|
||||
@param data: the data that was signed.
|
||||
@type data: str
|
||||
@param msg: an SSH signature message
|
||||
@type msg: L{Message}
|
||||
@return: C{True} if the signature verifies correctly; C{False}
|
||||
otherwise.
|
||||
@rtype: boolean
|
||||
:param str data: the data that was signed.
|
||||
:param .Message msg: an SSH signature message
|
||||
:return:
|
||||
``True`` if the signature verifies correctly; ``False`` otherwise.
|
||||
"""
|
||||
return False
|
||||
|
||||
def from_private_key_file(cls, filename, password=None):
|
||||
"""
|
||||
Create a key object by reading a private key file. If the private
|
||||
key is encrypted and C{password} is not C{None}, the given password
|
||||
will be used to decrypt the key (otherwise L{PasswordRequiredException}
|
||||
is thrown). Through the magic of python, this factory method will
|
||||
exist in all subclasses of PKey (such as L{RSAKey} or L{DSSKey}), but
|
||||
key is encrypted and ``password`` is not ``None``, the given password
|
||||
will be used to decrypt the key (otherwise `.PasswordRequiredException`
|
||||
is thrown). Through the magic of Python, this factory method will
|
||||
exist in all subclasses of PKey (such as `.RSAKey` or `.DSSKey`), but
|
||||
is useless on the abstract PKey class.
|
||||
|
||||
@param filename: name of the file to read
|
||||
@type filename: str
|
||||
@param password: an optional password to use to decrypt the key file,
|
||||
:param str filename: name of the file to read
|
||||
:param str password: an optional password to use to decrypt the key file,
|
||||
if it's encrypted
|
||||
@type password: str
|
||||
@return: a new key object based on the given private key
|
||||
@rtype: L{PKey}
|
||||
:return: a new `.PKey` based on the given private key
|
||||
|
||||
@raise IOError: if there was an error reading the file
|
||||
@raise PasswordRequiredException: if the private key file is
|
||||
encrypted, and C{password} is C{None}
|
||||
@raise SSHException: if the key file is invalid
|
||||
:raises IOError: if there was an error reading the file
|
||||
:raises PasswordRequiredException: if the private key file is
|
||||
encrypted, and ``password`` is ``None``
|
||||
:raises SSHException: if the key file is invalid
|
||||
"""
|
||||
key = cls(filename=filename, password=password)
|
||||
return key
|
||||
|
@ -202,22 +186,19 @@ class PKey (object):
|
|||
def from_private_key(cls, file_obj, password=None):
|
||||
"""
|
||||
Create a key object by reading a private key from a file (or file-like)
|
||||
object. If the private key is encrypted and C{password} is not C{None},
|
||||
object. If the private key is encrypted and ``password`` is not ``None``,
|
||||
the given password will be used to decrypt the key (otherwise
|
||||
L{PasswordRequiredException} is thrown).
|
||||
`.PasswordRequiredException` is thrown).
|
||||
|
||||
@param file_obj: the file to read from
|
||||
@type file_obj: file
|
||||
@param password: an optional password to use to decrypt the key, if it's
|
||||
encrypted
|
||||
@type password: str
|
||||
@return: a new key object based on the given private key
|
||||
@rtype: L{PKey}
|
||||
:param file file_obj: the file to read from
|
||||
:param str password:
|
||||
an optional password to use to decrypt the key, if it's encrypted
|
||||
:return: a new `.PKey` based on the given private key
|
||||
|
||||
@raise IOError: if there was an error reading the key
|
||||
@raise PasswordRequiredException: if the private key file is encrypted,
|
||||
and C{password} is C{None}
|
||||
@raise SSHException: if the key file is invalid
|
||||
:raises IOError: if there was an error reading the key
|
||||
:raises PasswordRequiredException: if the private key file is encrypted,
|
||||
and ``password`` is ``None``
|
||||
:raises SSHException: if the key file is invalid
|
||||
"""
|
||||
key = cls(file_obj=file_obj, password=password)
|
||||
return key
|
||||
|
@ -226,59 +207,52 @@ class PKey (object):
|
|||
def write_private_key_file(self, filename, password=None):
|
||||
"""
|
||||
Write private key contents into a file. If the password is not
|
||||
C{None}, the key is encrypted before writing.
|
||||
``None``, the key is encrypted before writing.
|
||||
|
||||
@param filename: name of the file to write
|
||||
@type filename: str
|
||||
@param password: an optional password to use to encrypt the key file
|
||||
@type password: str
|
||||
:param str filename: name of the file to write
|
||||
:param str password:
|
||||
an optional password to use to encrypt the key file
|
||||
|
||||
@raise IOError: if there was an error writing the file
|
||||
@raise SSHException: if the key is invalid
|
||||
:raises IOError: if there was an error writing the file
|
||||
:raises SSHException: if the key is invalid
|
||||
"""
|
||||
raise Exception('Not implemented in PKey')
|
||||
|
||||
def write_private_key(self, file_obj, password=None):
|
||||
"""
|
||||
Write private key contents into a file (or file-like) object. If the
|
||||
password is not C{None}, the key is encrypted before writing.
|
||||
password is not ``None``, the key is encrypted before writing.
|
||||
|
||||
@param file_obj: the file object to write into
|
||||
@type file_obj: file
|
||||
@param password: an optional password to use to encrypt the key
|
||||
@type password: str
|
||||
:param file file_obj: the file object to write into
|
||||
:param str password: an optional password to use to encrypt the key
|
||||
|
||||
@raise IOError: if there was an error writing to the file
|
||||
@raise SSHException: if the key is invalid
|
||||
:raises IOError: if there was an error writing to the file
|
||||
:raises SSHException: if the key is invalid
|
||||
"""
|
||||
raise Exception('Not implemented in PKey')
|
||||
|
||||
def _read_private_key_file(self, tag, filename, password=None):
|
||||
"""
|
||||
Read an SSH2-format private key file, looking for a string of the type
|
||||
C{"BEGIN xxx PRIVATE KEY"} for some C{xxx}, base64-decode the text we
|
||||
``"BEGIN xxx PRIVATE KEY"`` for some ``xxx``, base64-decode the text we
|
||||
find, and return it as a string. If the private key is encrypted and
|
||||
C{password} is not C{None}, the given password will be used to decrypt
|
||||
the key (otherwise L{PasswordRequiredException} is thrown).
|
||||
``password`` is not ``None``, the given password will be used to decrypt
|
||||
the key (otherwise `.PasswordRequiredException` is thrown).
|
||||
|
||||
@param tag: C{"RSA"} or C{"DSA"}, the tag used to mark the data block.
|
||||
@type tag: str
|
||||
@param filename: name of the file to read.
|
||||
@type filename: str
|
||||
@param password: an optional password to use to decrypt the key file,
|
||||
if it's encrypted.
|
||||
@type password: str
|
||||
@return: data blob that makes up the private key.
|
||||
@rtype: str
|
||||
:param str tag: ``"RSA"`` or ``"DSA"``, the tag used to mark the data block.
|
||||
:param str filename: name of the file to read.
|
||||
:param str password:
|
||||
an optional password to use to decrypt the key file, if it's
|
||||
encrypted.
|
||||
:return: data blob (`str`) that makes up the private key.
|
||||
|
||||
@raise IOError: if there was an error reading the file.
|
||||
@raise PasswordRequiredException: if the private key file is
|
||||
encrypted, and C{password} is C{None}.
|
||||
@raise SSHException: if the key file is invalid.
|
||||
:raises IOError: if there was an error reading the file.
|
||||
:raises PasswordRequiredException: if the private key file is
|
||||
encrypted, and ``password`` is ``None``.
|
||||
:raises SSHException: if the key file is invalid.
|
||||
"""
|
||||
f = open(filename, 'r')
|
||||
with open(filename, 'r') as f:
|
||||
data = self._read_private_key(tag, f, password)
|
||||
f.close()
|
||||
return data
|
||||
|
||||
def _read_private_key(self, tag, f, password=None):
|
||||
|
@ -303,8 +277,8 @@ class PKey (object):
|
|||
end += 1
|
||||
# if we trudged to the end of the file, just try to cope.
|
||||
try:
|
||||
data = base64.decodestring(''.join(lines[start:end]))
|
||||
except base64.binascii.Error, e:
|
||||
data = decodebytes(b(''.join(lines[start:end])))
|
||||
except base64.binascii.Error as e:
|
||||
raise SSHException('base64 decoding error: ' + str(e))
|
||||
if 'proc-type' not in headers:
|
||||
# unencryped: done
|
||||
|
@ -315,7 +289,7 @@ class PKey (object):
|
|||
try:
|
||||
encryption_type, saltstr = headers['dek-info'].split(',')
|
||||
except:
|
||||
raise SSHException('Can\'t parse DEK-info in private key file')
|
||||
raise SSHException("Can't parse DEK-info in private key file")
|
||||
if encryption_type not in self._CIPHER_TABLE:
|
||||
raise SSHException('Unknown private key cipher "%s"' % encryption_type)
|
||||
# if no password was passed in, raise an exception pointing out that we need one
|
||||
|
@ -324,8 +298,8 @@ class PKey (object):
|
|||
cipher = self._CIPHER_TABLE[encryption_type]['cipher']
|
||||
keysize = self._CIPHER_TABLE[encryption_type]['keysize']
|
||||
mode = self._CIPHER_TABLE[encryption_type]['mode']
|
||||
salt = unhexlify(saltstr)
|
||||
key = util.generate_key_bytes(MD5, salt, password, keysize)
|
||||
salt = unhexlify(b(saltstr))
|
||||
key = util.generate_key_bytes(md5, salt, password, keysize)
|
||||
return cipher.new(key, mode, salt).decrypt(data)
|
||||
|
||||
def _write_private_key_file(self, tag, filename, data, password=None):
|
||||
|
@ -335,44 +309,39 @@ class PKey (object):
|
|||
a trivially-encoded format (base64) which is completely insecure. If
|
||||
a password is given, DES-EDE3-CBC is used.
|
||||
|
||||
@param tag: C{"RSA"} or C{"DSA"}, the tag used to mark the data block.
|
||||
@type tag: str
|
||||
@param filename: name of the file to write.
|
||||
@type filename: str
|
||||
@param data: data blob that makes up the private key.
|
||||
@type data: str
|
||||
@param password: an optional password to use to encrypt the file.
|
||||
@type password: str
|
||||
:param str tag: ``"RSA"`` or ``"DSA"``, the tag used to mark the data block.
|
||||
:param file filename: name of the file to write.
|
||||
:param str data: data blob that makes up the private key.
|
||||
:param str password: an optional password to use to encrypt the file.
|
||||
|
||||
@raise IOError: if there was an error writing the file.
|
||||
:raises IOError: if there was an error writing the file.
|
||||
"""
|
||||
f = open(filename, 'w', 0600)
|
||||
with open(filename, 'w', o600) as f:
|
||||
# grrr... the mode doesn't always take hold
|
||||
os.chmod(filename, 0600)
|
||||
os.chmod(filename, o600)
|
||||
self._write_private_key(tag, f, data, password)
|
||||
f.close()
|
||||
|
||||
def _write_private_key(self, tag, f, data, password=None):
|
||||
f.write('-----BEGIN %s PRIVATE KEY-----\n' % tag)
|
||||
if password is not None:
|
||||
# since we only support one cipher here, use it
|
||||
cipher_name = self._CIPHER_TABLE.keys()[0]
|
||||
cipher_name = list(self._CIPHER_TABLE.keys())[0]
|
||||
cipher = self._CIPHER_TABLE[cipher_name]['cipher']
|
||||
keysize = self._CIPHER_TABLE[cipher_name]['keysize']
|
||||
blocksize = self._CIPHER_TABLE[cipher_name]['blocksize']
|
||||
mode = self._CIPHER_TABLE[cipher_name]['mode']
|
||||
salt = rng.read(8)
|
||||
key = util.generate_key_bytes(MD5, salt, password, keysize)
|
||||
salt = os.urandom(16)
|
||||
key = util.generate_key_bytes(md5, salt, password, keysize)
|
||||
if len(data) % blocksize != 0:
|
||||
n = blocksize - len(data) % blocksize
|
||||
#data += rng.read(n)
|
||||
#data += os.urandom(n)
|
||||
# that would make more sense ^, but it confuses openssh.
|
||||
data += '\0' * n
|
||||
data += zero_byte * n
|
||||
data = cipher.new(key, mode, salt).encrypt(data)
|
||||
f.write('Proc-Type: 4,ENCRYPTED\n')
|
||||
f.write('DEK-Info: %s,%s\n' % (cipher_name, hexlify(salt).upper()))
|
||||
f.write('DEK-Info: %s,%s\n' % (cipher_name, u(hexlify(salt)).upper()))
|
||||
f.write('\n')
|
||||
s = base64.encodestring(data)
|
||||
s = u(encodebytes(data))
|
||||
# re-wrap to 64-char lines
|
||||
s = ''.join(s.split('\n'))
|
||||
s = '\n'.join([s[i: i + 64] for i in range(0, len(s), 64)])
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -20,33 +20,17 @@
|
|||
Utility functions for dealing with primes.
|
||||
"""
|
||||
|
||||
from Crypto.Util import number
|
||||
import os
|
||||
|
||||
from paramiko import util
|
||||
from paramiko.py3compat import byte_mask, long
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
|
||||
def _generate_prime(bits, rng):
|
||||
"primtive attempt at prime generation"
|
||||
hbyte_mask = pow(2, bits % 8) - 1
|
||||
while True:
|
||||
# loop catches the case where we increment n into a higher bit-range
|
||||
x = rng.read((bits+7) // 8)
|
||||
if hbyte_mask > 0:
|
||||
x = chr(ord(x[0]) & hbyte_mask) + x[1:]
|
||||
n = util.inflate_long(x, 1)
|
||||
n |= 1
|
||||
n |= (1 << (bits - 1))
|
||||
while not number.isPrime(n):
|
||||
n += 2
|
||||
if util.bit_length(n) == bits:
|
||||
break
|
||||
return n
|
||||
|
||||
def _roll_random(rng, n):
|
||||
"returns a random # from 0 to N-1"
|
||||
def _roll_random(n):
|
||||
"""returns a random # from 0 to N-1"""
|
||||
bits = util.bit_length(n - 1)
|
||||
bytes = (bits + 7) // 8
|
||||
byte_count = (bits + 7) // 8
|
||||
hbyte_mask = pow(2, bits % 8) - 1
|
||||
|
||||
# so here's the plan:
|
||||
|
@ -56,9 +40,9 @@ def _roll_random(rng, n):
|
|||
# fits, so i can't guarantee that this loop will ever finish, but the odds
|
||||
# of it looping forever should be infinitesimal.
|
||||
while True:
|
||||
x = rng.read(bytes)
|
||||
x = os.urandom(byte_count)
|
||||
if hbyte_mask > 0:
|
||||
x = chr(ord(x[0]) & hbyte_mask) + x[1:]
|
||||
x = byte_mask(x[0], hbyte_mask) + x[1:]
|
||||
num = util.inflate_long(x, 1)
|
||||
if num < n:
|
||||
break
|
||||
|
@ -71,11 +55,10 @@ class ModulusPack (object):
|
|||
on systems that have such a file.
|
||||
"""
|
||||
|
||||
def __init__(self, rpool):
|
||||
def __init__(self):
|
||||
# pack is a hash of: bits -> [ (generator, modulus) ... ]
|
||||
self.pack = {}
|
||||
self.discarded = []
|
||||
self.rng = rpool
|
||||
|
||||
def _parse_modulus(self, line):
|
||||
timestamp, mod_type, tests, tries, size, generator, modulus = line.split()
|
||||
|
@ -109,10 +92,10 @@ class ModulusPack (object):
|
|||
|
||||
def read_file(self, filename):
|
||||
"""
|
||||
@raise IOError: passed from any file operations that fail.
|
||||
:raises IOError: passed from any file operations that fail.
|
||||
"""
|
||||
self.pack = {}
|
||||
f = open(filename, 'r')
|
||||
with open(filename, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if (len(line) == 0) or (line[0] == '#'):
|
||||
|
@ -121,17 +104,15 @@ class ModulusPack (object):
|
|||
self._parse_modulus(line)
|
||||
except:
|
||||
continue
|
||||
f.close()
|
||||
|
||||
def get_modulus(self, min, prefer, max):
|
||||
bitsizes = self.pack.keys()
|
||||
bitsizes.sort()
|
||||
bitsizes = sorted(self.pack.keys())
|
||||
if len(bitsizes) == 0:
|
||||
raise SSHException('no moduli available')
|
||||
good = -1
|
||||
# find nearest bitsize >= preferred
|
||||
for b in bitsizes:
|
||||
if (b >= prefer) and (b < max) and ((b < good) or (good == -1)):
|
||||
if (b >= prefer) and (b < max) and (b < good or good == -1):
|
||||
good = b
|
||||
# if that failed, find greatest bitsize >= min
|
||||
if good == -1:
|
||||
|
@ -147,5 +128,5 @@ class ModulusPack (object):
|
|||
if min > good:
|
||||
good = bitsizes[-1]
|
||||
# now pick a random modulus of this bitsize
|
||||
n = _roll_random(self.rng, len(self.pack[good]))
|
||||
n = _roll_random(len(self.pack[good]))
|
||||
return self.pack[good][n]
|
||||
|
|
|
@ -16,14 +16,14 @@
|
|||
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{ProxyCommand}.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
import os
|
||||
from shlex import split as shlsplit
|
||||
import signal
|
||||
from subprocess import Popen, PIPE
|
||||
from select import select
|
||||
import socket
|
||||
|
||||
from paramiko.ssh_exception import ProxyCommandFailure
|
||||
|
||||
|
@ -33,59 +33,73 @@ class ProxyCommand(object):
|
|||
Wraps a subprocess running ProxyCommand-driven programs.
|
||||
|
||||
This class implements a the socket-like interface needed by the
|
||||
L{Transport} and L{Packetizer} classes. Using this class instead of a
|
||||
`.Transport` and `.Packetizer` classes. Using this class instead of a
|
||||
regular socket makes it possible to talk with a Popen'd command that will
|
||||
proxy traffic between the client and a server hosted in another machine.
|
||||
"""
|
||||
def __init__(self, command_line):
|
||||
"""
|
||||
Create a new CommandProxy instance. The instance created by this
|
||||
class can be passed as an argument to the L{Transport} class.
|
||||
class can be passed as an argument to the `.Transport` class.
|
||||
|
||||
@param command_line: the command that should be executed and
|
||||
used as the proxy.
|
||||
@type command_line: str
|
||||
:param str command_line:
|
||||
the command that should be executed and used as the proxy.
|
||||
"""
|
||||
self.cmd = shlsplit(command_line)
|
||||
self.process = Popen(self.cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
self.timeout = None
|
||||
self.buffer = []
|
||||
|
||||
def send(self, content):
|
||||
"""
|
||||
Write the content received from the SSH client to the standard
|
||||
input of the forked command.
|
||||
|
||||
@param content: string to be sent to the forked command
|
||||
@type content: str
|
||||
:param str content: string to be sent to the forked command
|
||||
"""
|
||||
try:
|
||||
self.process.stdin.write(content)
|
||||
except IOError, e:
|
||||
except IOError as e:
|
||||
# There was a problem with the child process. It probably
|
||||
# died and we can't proceed. The best option here is to
|
||||
# raise an exception informing the user that the informed
|
||||
# ProxyCommand is not working.
|
||||
raise BadProxyCommand(' '.join(self.cmd), e.strerror)
|
||||
raise ProxyCommandFailure(' '.join(self.cmd), e.strerror)
|
||||
return len(content)
|
||||
|
||||
def recv(self, size):
|
||||
"""
|
||||
Read from the standard output of the forked program.
|
||||
|
||||
@param size: how many chars should be read
|
||||
@type size: int
|
||||
:param int size: how many chars should be read
|
||||
|
||||
@return: the length of the read content
|
||||
@rtype: int
|
||||
:return: the length of the read content, as an `int`
|
||||
"""
|
||||
try:
|
||||
return os.read(self.process.stdout.fileno(), size)
|
||||
except IOError, e:
|
||||
raise BadProxyCommand(' '.join(self.cmd), e.strerror)
|
||||
start = datetime.now()
|
||||
while len(self.buffer) < size:
|
||||
if self.timeout is not None:
|
||||
elapsed = (datetime.now() - start).microseconds
|
||||
timeout = self.timeout * 1000 * 1000 # to microseconds
|
||||
if elapsed >= timeout:
|
||||
raise socket.timeout()
|
||||
r, w, x = select([self.process.stdout], [], [], 0.0)
|
||||
if r and r[0] == self.process.stdout:
|
||||
b = os.read(self.process.stdout.fileno(), 1)
|
||||
# Store in class-level buffer for persistence across
|
||||
# timeouts; this makes us act more like a real socket
|
||||
# (where timeouts don't actually drop data.)
|
||||
self.buffer.append(b)
|
||||
result = ''.join(self.buffer)
|
||||
self.buffer = []
|
||||
return result
|
||||
except socket.timeout:
|
||||
raise # socket.timeout is a subclass of IOError
|
||||
except IOError as e:
|
||||
raise ProxyCommandFailure(' '.join(self.cmd), e.strerror)
|
||||
|
||||
def close(self):
|
||||
os.kill(self.process.pid, signal.SIGTERM)
|
||||
|
||||
def settimeout(self, timeout):
|
||||
# Timeouts are meaningless for this implementation, but are part of the
|
||||
# spec, so must be present.
|
||||
pass
|
||||
self.timeout = timeout
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
import sys
|
||||
import base64
|
||||
|
||||
__all__ = ['PY2', 'string_types', 'integer_types', 'text_type', 'bytes_types', 'bytes', 'long', 'input',
|
||||
'decodebytes', 'encodebytes', 'bytestring', 'byte_ord', 'byte_chr', 'byte_mask',
|
||||
'b', 'u', 'b2s', 'StringIO', 'BytesIO', 'is_callable', 'MAXSIZE', 'next']
|
||||
|
||||
PY2 = sys.version_info[0] < 3
|
||||
|
||||
if PY2:
|
||||
string_types = basestring
|
||||
text_type = unicode
|
||||
bytes_types = str
|
||||
bytes = str
|
||||
integer_types = (int, long)
|
||||
long = long
|
||||
input = raw_input
|
||||
decodebytes = base64.decodestring
|
||||
encodebytes = base64.encodestring
|
||||
|
||||
|
||||
def bytestring(s): # NOQA
|
||||
if isinstance(s, unicode):
|
||||
return s.encode('utf-8')
|
||||
return s
|
||||
|
||||
|
||||
byte_ord = ord # NOQA
|
||||
byte_chr = chr # NOQA
|
||||
|
||||
|
||||
def byte_mask(c, mask):
|
||||
return chr(ord(c) & mask)
|
||||
|
||||
|
||||
def b(s, encoding='utf8'): # NOQA
|
||||
"""cast unicode or bytes to bytes"""
|
||||
if isinstance(s, str):
|
||||
return s
|
||||
elif isinstance(s, unicode):
|
||||
return s.encode(encoding)
|
||||
else:
|
||||
raise TypeError("Expected unicode or bytes, got %r" % s)
|
||||
|
||||
|
||||
def u(s, encoding='utf8'): # NOQA
|
||||
"""cast bytes or unicode to unicode"""
|
||||
if isinstance(s, str):
|
||||
return s.decode(encoding)
|
||||
elif isinstance(s, unicode):
|
||||
return s
|
||||
else:
|
||||
raise TypeError("Expected unicode or bytes, got %r" % s)
|
||||
|
||||
|
||||
def b2s(s):
|
||||
return s
|
||||
|
||||
|
||||
try:
|
||||
import cStringIO
|
||||
|
||||
StringIO = cStringIO.StringIO # NOQA
|
||||
except ImportError:
|
||||
import StringIO
|
||||
|
||||
StringIO = StringIO.StringIO # NOQA
|
||||
|
||||
BytesIO = StringIO
|
||||
|
||||
|
||||
def is_callable(c): # NOQA
|
||||
return callable(c)
|
||||
|
||||
|
||||
def get_next(c): # NOQA
|
||||
return c.next
|
||||
|
||||
|
||||
def next(c):
|
||||
return c.next()
|
||||
|
||||
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
|
||||
class X(object):
|
||||
def __len__(self):
|
||||
return 1 << 31
|
||||
|
||||
|
||||
try:
|
||||
len(X())
|
||||
except OverflowError:
|
||||
# 32-bit
|
||||
MAXSIZE = int((1 << 31) - 1) # NOQA
|
||||
else:
|
||||
# 64-bit
|
||||
MAXSIZE = int((1 << 63) - 1) # NOQA
|
||||
del X
|
||||
else:
|
||||
import collections
|
||||
import struct
|
||||
string_types = str
|
||||
text_type = str
|
||||
bytes = bytes
|
||||
bytes_types = bytes
|
||||
integer_types = int
|
||||
class long(int):
|
||||
pass
|
||||
input = input
|
||||
decodebytes = base64.decodebytes
|
||||
encodebytes = base64.encodebytes
|
||||
|
||||
def bytestring(s):
|
||||
return s
|
||||
|
||||
def byte_ord(c):
|
||||
# In case we're handed a string instead of an int.
|
||||
if not isinstance(c, int):
|
||||
c = ord(c)
|
||||
return c
|
||||
|
||||
def byte_chr(c):
|
||||
assert isinstance(c, int)
|
||||
return struct.pack('B', c)
|
||||
|
||||
def byte_mask(c, mask):
|
||||
assert isinstance(c, int)
|
||||
return struct.pack('B', c & mask)
|
||||
|
||||
def b(s, encoding='utf8'):
|
||||
"""cast unicode or bytes to bytes"""
|
||||
if isinstance(s, bytes):
|
||||
return s
|
||||
elif isinstance(s, str):
|
||||
return s.encode(encoding)
|
||||
else:
|
||||
raise TypeError("Expected unicode or bytes, got %r" % s)
|
||||
|
||||
def u(s, encoding='utf8'):
|
||||
"""cast bytes or unicode to unicode"""
|
||||
if isinstance(s, bytes):
|
||||
return s.decode(encoding)
|
||||
elif isinstance(s, str):
|
||||
return s
|
||||
else:
|
||||
raise TypeError("Expected unicode or bytes, got %r" % s)
|
||||
|
||||
def b2s(s):
|
||||
return s.decode() if isinstance(s, bytes) else s
|
||||
|
||||
import io
|
||||
StringIO = io.StringIO # NOQA
|
||||
BytesIO = io.BytesIO # NOQA
|
||||
|
||||
def is_callable(c):
|
||||
return isinstance(c, collections.Callable)
|
||||
|
||||
def get_next(c):
|
||||
return c.__next__
|
||||
|
||||
next = next
|
||||
|
||||
MAXSIZE = sys.maxsize # NOQA
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -28,13 +28,13 @@ class ResourceManager (object):
|
|||
A registry of objects and resources that should be closed when those
|
||||
objects are deleted.
|
||||
|
||||
This is meant to be a safer alternative to python's C{__del__} method,
|
||||
This is meant to be a safer alternative to Python's ``__del__`` method,
|
||||
which can cause reference cycles to never be collected. Objects registered
|
||||
with the ResourceManager can be collected but still free resources when
|
||||
they die.
|
||||
|
||||
Resources are registered using L{register}, and when an object is garbage
|
||||
collected, each registered resource is closed by having its C{close()}
|
||||
Resources are registered using `register`, and when an object is garbage
|
||||
collected, each registered resource is closed by having its ``close()``
|
||||
method called. Multiple resources may be registered per object, but a
|
||||
resource will only be closed once, even if multiple objects register it.
|
||||
(The last object to register it wins.)
|
||||
|
@ -47,14 +47,13 @@ class ResourceManager (object):
|
|||
"""
|
||||
Register a resource to be closed with an object is collected.
|
||||
|
||||
When the given C{obj} is garbage-collected by the python interpreter,
|
||||
the C{resource} will be closed by having its C{close()} method called.
|
||||
When the given ``obj`` is garbage-collected by the Python interpreter,
|
||||
the ``resource`` will be closed by having its ``close()`` method called.
|
||||
Any exceptions are ignored.
|
||||
|
||||
@param obj: the object to track
|
||||
@type obj: object
|
||||
@param resource: the resource to close when the object is collected
|
||||
@type resource: object
|
||||
:param object obj: the object to track
|
||||
:param object resource:
|
||||
the resource to close when the object is collected
|
||||
"""
|
||||
def callback(ref):
|
||||
try:
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -17,20 +17,24 @@
|
|||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{RSAKey}
|
||||
RSA keys.
|
||||
"""
|
||||
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Hash import SHA, MD5
|
||||
from Crypto.Cipher import DES3
|
||||
import os
|
||||
from hashlib import sha1
|
||||
|
||||
from Crypto.PublicKey import RSA
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.common import max_byte, zero_byte, one_byte
|
||||
from paramiko.message import Message
|
||||
from paramiko.ber import BER, BERException
|
||||
from paramiko.pkey import PKey
|
||||
from paramiko.py3compat import long
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
SHA1_DIGESTINFO = b'\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'
|
||||
|
||||
|
||||
class RSAKey (PKey):
|
||||
"""
|
||||
|
@ -57,18 +61,21 @@ class RSAKey (PKey):
|
|||
else:
|
||||
if msg is None:
|
||||
raise SSHException('Key object may not be empty')
|
||||
if msg.get_string() != 'ssh-rsa':
|
||||
if msg.get_text() != 'ssh-rsa':
|
||||
raise SSHException('Invalid key')
|
||||
self.e = msg.get_mpint()
|
||||
self.n = msg.get_mpint()
|
||||
self.size = util.bit_length(self.n)
|
||||
|
||||
def __str__(self):
|
||||
def asbytes(self):
|
||||
m = Message()
|
||||
m.add_string('ssh-rsa')
|
||||
m.add_mpint(self.e)
|
||||
m.add_mpint(self.n)
|
||||
return str(m)
|
||||
return m.asbytes()
|
||||
|
||||
def __str__(self):
|
||||
return self.asbytes()
|
||||
|
||||
def __hash__(self):
|
||||
h = hash(self.get_name())
|
||||
|
@ -85,23 +92,23 @@ class RSAKey (PKey):
|
|||
def can_sign(self):
|
||||
return self.d is not None
|
||||
|
||||
def sign_ssh_data(self, rpool, data):
|
||||
digest = SHA.new(data).digest()
|
||||
def sign_ssh_data(self, data):
|
||||
digest = sha1(data).digest()
|
||||
rsa = RSA.construct((long(self.n), long(self.e), long(self.d)))
|
||||
sig = util.deflate_long(rsa.sign(self._pkcs1imify(digest), '')[0], 0)
|
||||
sig = util.deflate_long(rsa.sign(self._pkcs1imify(digest), bytes())[0], 0)
|
||||
m = Message()
|
||||
m.add_string('ssh-rsa')
|
||||
m.add_string(sig)
|
||||
return m
|
||||
|
||||
def verify_ssh_sig(self, data, msg):
|
||||
if msg.get_string() != 'ssh-rsa':
|
||||
if msg.get_text() != 'ssh-rsa':
|
||||
return False
|
||||
sig = util.inflate_long(msg.get_string(), True)
|
||||
sig = util.inflate_long(msg.get_binary(), True)
|
||||
# verify the signature by SHA'ing the data and encrypting it using the
|
||||
# public key. some wackiness ensues where we "pkcs1imify" the 20-byte
|
||||
# hash into a string as long as the RSA key.
|
||||
hash_obj = util.inflate_long(self._pkcs1imify(SHA.new(data).digest()), True)
|
||||
hash_obj = util.inflate_long(self._pkcs1imify(sha1(data).digest()), True)
|
||||
rsa = RSA.construct((long(self.n), long(self.e)))
|
||||
return rsa.verify(hash_obj, (sig,))
|
||||
|
||||
|
@ -116,7 +123,7 @@ class RSAKey (PKey):
|
|||
b.encode(keylist)
|
||||
except BERException:
|
||||
raise SSHException('Unable to create ber encoding of key')
|
||||
return str(b)
|
||||
return b.asbytes()
|
||||
|
||||
def write_private_key_file(self, filename, password=None):
|
||||
self._write_private_key_file('RSA', filename, self._encode_key(), password)
|
||||
|
@ -129,15 +136,13 @@ class RSAKey (PKey):
|
|||
Generate a new private RSA key. This factory function can be used to
|
||||
generate a new host key or authentication key.
|
||||
|
||||
@param bits: number of bits the generated key should be.
|
||||
@type bits: int
|
||||
@param progress_func: an optional function to call at key points in
|
||||
key generation (used by C{pyCrypto.PublicKey}).
|
||||
@type progress_func: function
|
||||
@return: new private key
|
||||
@rtype: L{RSAKey}
|
||||
:param int bits: number of bits the generated key should be.
|
||||
:param function progress_func:
|
||||
an optional function to call at key points in key generation (used
|
||||
by ``pyCrypto.PublicKey``).
|
||||
:return: new `.RSAKey` private key
|
||||
"""
|
||||
rsa = RSA.generate(bits, rng.read, progress_func)
|
||||
rsa = RSA.generate(bits, os.urandom, progress_func)
|
||||
key = RSAKey(vals=(rsa.e, rsa.n))
|
||||
key.d = rsa.d
|
||||
key.p = rsa.p
|
||||
|
@ -145,19 +150,16 @@ class RSAKey (PKey):
|
|||
return key
|
||||
generate = staticmethod(generate)
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _pkcs1imify(self, data):
|
||||
"""
|
||||
turn a 20-byte SHA1 hash into a blob of data as large as the key's N,
|
||||
using PKCS1's \"emsa-pkcs1-v1_5\" encoding. totally bizarre.
|
||||
"""
|
||||
SHA1_DIGESTINFO = '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'
|
||||
size = len(util.deflate_long(self.n, 0))
|
||||
filler = '\xff' * (size - len(SHA1_DIGESTINFO) - len(data) - 3)
|
||||
return '\x00\x01' + filler + '\x00' + SHA1_DIGESTINFO + data
|
||||
filler = max_byte * (size - len(SHA1_DIGESTINFO) - len(data) - 3)
|
||||
return zero_byte + one_byte + filler + zero_byte + SHA1_DIGESTINFO + data
|
||||
|
||||
def _from_private_key_file(self, filename, password):
|
||||
data = self._read_private_key_file('RSA', filename, password)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -17,62 +17,21 @@
|
|||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{ServerInterface} is an interface to override for server support.
|
||||
`.ServerInterface` is an interface to override for server support.
|
||||
"""
|
||||
|
||||
import threading
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
|
||||
|
||||
class InteractiveQuery (object):
|
||||
"""
|
||||
A query (set of prompts) for a user during interactive authentication.
|
||||
"""
|
||||
|
||||
def __init__(self, name='', instructions='', *prompts):
|
||||
"""
|
||||
Create a new interactive query to send to the client. The name and
|
||||
instructions are optional, but are generally displayed to the end
|
||||
user. A list of prompts may be included, or they may be added via
|
||||
the L{add_prompt} method.
|
||||
|
||||
@param name: name of this query
|
||||
@type name: str
|
||||
@param instructions: user instructions (usually short) about this query
|
||||
@type instructions: str
|
||||
@param prompts: one or more authentication prompts
|
||||
@type prompts: str
|
||||
"""
|
||||
self.name = name
|
||||
self.instructions = instructions
|
||||
self.prompts = []
|
||||
for x in prompts:
|
||||
if (type(x) is str) or (type(x) is unicode):
|
||||
self.add_prompt(x)
|
||||
else:
|
||||
self.add_prompt(x[0], x[1])
|
||||
|
||||
def add_prompt(self, prompt, echo=True):
|
||||
"""
|
||||
Add a prompt to this query. The prompt should be a (reasonably short)
|
||||
string. Multiple prompts can be added to the same query.
|
||||
|
||||
@param prompt: the user prompt
|
||||
@type prompt: str
|
||||
@param echo: C{True} (default) if the user's response should be echoed;
|
||||
C{False} if not (for a password or similar)
|
||||
@type echo: bool
|
||||
"""
|
||||
self.prompts.append((prompt, echo))
|
||||
from paramiko.common import DEBUG, ERROR, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, AUTH_FAILED
|
||||
from paramiko.py3compat import string_types
|
||||
|
||||
|
||||
class ServerInterface (object):
|
||||
"""
|
||||
This class defines an interface for controlling the behavior of paramiko
|
||||
This class defines an interface for controlling the behavior of Paramiko
|
||||
in server mode.
|
||||
|
||||
Methods on this class are called from paramiko's primary thread, so you
|
||||
Methods on this class are called from Paramiko's primary thread, so you
|
||||
shouldn't do too much work in them. (Certainly nothing that blocks or
|
||||
sleeps.)
|
||||
"""
|
||||
|
@ -80,7 +39,7 @@ class ServerInterface (object):
|
|||
def check_channel_request(self, kind, chanid):
|
||||
"""
|
||||
Determine if a channel request of a given type will be granted, and
|
||||
return C{OPEN_SUCCEEDED} or an error code. This method is
|
||||
return ``OPEN_SUCCEEDED`` or an error code. This method is
|
||||
called in server mode when the client requests a channel, after
|
||||
authentication is complete.
|
||||
|
||||
|
@ -88,37 +47,37 @@ class ServerInterface (object):
|
|||
useless), you should also override some of the channel request methods
|
||||
below, which are used to determine which services will be allowed on
|
||||
a given channel:
|
||||
- L{check_channel_pty_request}
|
||||
- L{check_channel_shell_request}
|
||||
- L{check_channel_subsystem_request}
|
||||
- L{check_channel_window_change_request}
|
||||
- L{check_channel_x11_request}
|
||||
- L{check_channel_forward_agent_request}
|
||||
|
||||
The C{chanid} parameter is a small number that uniquely identifies the
|
||||
channel within a L{Transport}. A L{Channel} object is not created
|
||||
unless this method returns C{OPEN_SUCCEEDED} -- once a
|
||||
L{Channel} object is created, you can call L{Channel.get_id} to
|
||||
- `check_channel_pty_request`
|
||||
- `check_channel_shell_request`
|
||||
- `check_channel_subsystem_request`
|
||||
- `check_channel_window_change_request`
|
||||
- `check_channel_x11_request`
|
||||
- `check_channel_forward_agent_request`
|
||||
|
||||
The ``chanid`` parameter is a small number that uniquely identifies the
|
||||
channel within a `.Transport`. A `.Channel` object is not created
|
||||
unless this method returns ``OPEN_SUCCEEDED`` -- once a
|
||||
`.Channel` object is created, you can call `.Channel.get_id` to
|
||||
retrieve the channel ID.
|
||||
|
||||
The return value should either be C{OPEN_SUCCEEDED} (or
|
||||
C{0}) to allow the channel request, or one of the following error
|
||||
The return value should either be ``OPEN_SUCCEEDED`` (or
|
||||
``0``) to allow the channel request, or one of the following error
|
||||
codes to reject it:
|
||||
- C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}
|
||||
- C{OPEN_FAILED_CONNECT_FAILED}
|
||||
- C{OPEN_FAILED_UNKNOWN_CHANNEL_TYPE}
|
||||
- C{OPEN_FAILED_RESOURCE_SHORTAGE}
|
||||
|
||||
- ``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``
|
||||
- ``OPEN_FAILED_CONNECT_FAILED``
|
||||
- ``OPEN_FAILED_UNKNOWN_CHANNEL_TYPE``
|
||||
- ``OPEN_FAILED_RESOURCE_SHORTAGE``
|
||||
|
||||
The default implementation always returns
|
||||
C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}.
|
||||
``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``.
|
||||
|
||||
@param kind: the kind of channel the client would like to open
|
||||
(usually C{"session"}).
|
||||
@type kind: str
|
||||
@param chanid: ID of the channel
|
||||
@type chanid: int
|
||||
@return: a success or failure code (listed above)
|
||||
@rtype: int
|
||||
:param str kind:
|
||||
the kind of channel the client would like to open (usually
|
||||
``"session"``).
|
||||
:param int chanid: ID of the channel
|
||||
:return: an `int` success or failure code (listed above)
|
||||
"""
|
||||
return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
||||
|
||||
|
@ -129,15 +88,13 @@ class ServerInterface (object):
|
|||
of authentication methods that might be successful.
|
||||
|
||||
The "list" is actually a string of comma-separated names of types of
|
||||
authentication. Possible values are C{"password"}, C{"publickey"},
|
||||
and C{"none"}.
|
||||
authentication. Possible values are ``"password"``, ``"publickey"``,
|
||||
and ``"none"``.
|
||||
|
||||
The default implementation always returns C{"password"}.
|
||||
The default implementation always returns ``"password"``.
|
||||
|
||||
@param username: the username requesting authentication.
|
||||
@type username: str
|
||||
@return: a comma-separated list of authentication types
|
||||
@rtype: str
|
||||
:param str username: the username requesting authentication.
|
||||
:return: a comma-separated `str` of authentication types
|
||||
"""
|
||||
return 'password'
|
||||
|
||||
|
@ -146,17 +103,17 @@ class ServerInterface (object):
|
|||
Determine if a client may open channels with no (further)
|
||||
authentication.
|
||||
|
||||
Return L{AUTH_FAILED} if the client must authenticate, or
|
||||
L{AUTH_SUCCESSFUL} if it's okay for the client to not
|
||||
Return `.AUTH_FAILED` if the client must authenticate, or
|
||||
`.AUTH_SUCCESSFUL` if it's okay for the client to not
|
||||
authenticate.
|
||||
|
||||
The default implementation always returns L{AUTH_FAILED}.
|
||||
The default implementation always returns `.AUTH_FAILED`.
|
||||
|
||||
@param username: the username of the client.
|
||||
@type username: str
|
||||
@return: L{AUTH_FAILED} if the authentication fails;
|
||||
L{AUTH_SUCCESSFUL} if it succeeds.
|
||||
@rtype: int
|
||||
:param str username: the username of the client.
|
||||
:return:
|
||||
`.AUTH_FAILED` if the authentication fails; `.AUTH_SUCCESSFUL` if
|
||||
it succeeds.
|
||||
:rtype: int
|
||||
"""
|
||||
return AUTH_FAILED
|
||||
|
||||
|
@ -165,25 +122,23 @@ class ServerInterface (object):
|
|||
Determine if a given username and password supplied by the client is
|
||||
acceptable for use in authentication.
|
||||
|
||||
Return L{AUTH_FAILED} if the password is not accepted,
|
||||
L{AUTH_SUCCESSFUL} if the password is accepted and completes
|
||||
the authentication, or L{AUTH_PARTIALLY_SUCCESSFUL} if your
|
||||
Return `.AUTH_FAILED` if the password is not accepted,
|
||||
`.AUTH_SUCCESSFUL` if the password is accepted and completes
|
||||
the authentication, or `.AUTH_PARTIALLY_SUCCESSFUL` if your
|
||||
authentication is stateful, and this key is accepted for
|
||||
authentication, but more authentication is required. (In this latter
|
||||
case, L{get_allowed_auths} will be called to report to the client what
|
||||
case, `get_allowed_auths` will be called to report to the client what
|
||||
options it has for continuing the authentication.)
|
||||
|
||||
The default implementation always returns L{AUTH_FAILED}.
|
||||
The default implementation always returns `.AUTH_FAILED`.
|
||||
|
||||
@param username: the username of the authenticating client.
|
||||
@type username: str
|
||||
@param password: the password given by the client.
|
||||
@type password: str
|
||||
@return: L{AUTH_FAILED} if the authentication fails;
|
||||
L{AUTH_SUCCESSFUL} if it succeeds;
|
||||
L{AUTH_PARTIALLY_SUCCESSFUL} if the password auth is
|
||||
:param str username: the username of the authenticating client.
|
||||
:param str password: the password given by the client.
|
||||
:return:
|
||||
`.AUTH_FAILED` if the authentication fails; `.AUTH_SUCCESSFUL` if
|
||||
it succeeds; `.AUTH_PARTIALLY_SUCCESSFUL` if the password auth is
|
||||
successful, but authentication must continue.
|
||||
@rtype: int
|
||||
:rtype: int
|
||||
"""
|
||||
return AUTH_FAILED
|
||||
|
||||
|
@ -194,29 +149,28 @@ class ServerInterface (object):
|
|||
check the username and key and decide if you would accept a signature
|
||||
made using this key.
|
||||
|
||||
Return L{AUTH_FAILED} if the key is not accepted,
|
||||
L{AUTH_SUCCESSFUL} if the key is accepted and completes the
|
||||
authentication, or L{AUTH_PARTIALLY_SUCCESSFUL} if your
|
||||
Return `.AUTH_FAILED` if the key is not accepted,
|
||||
`.AUTH_SUCCESSFUL` if the key is accepted and completes the
|
||||
authentication, or `.AUTH_PARTIALLY_SUCCESSFUL` if your
|
||||
authentication is stateful, and this password is accepted for
|
||||
authentication, but more authentication is required. (In this latter
|
||||
case, L{get_allowed_auths} will be called to report to the client what
|
||||
case, `get_allowed_auths` will be called to report to the client what
|
||||
options it has for continuing the authentication.)
|
||||
|
||||
Note that you don't have to actually verify any key signtature here.
|
||||
If you're willing to accept the key, paramiko will do the work of
|
||||
If you're willing to accept the key, Paramiko will do the work of
|
||||
verifying the client's signature.
|
||||
|
||||
The default implementation always returns L{AUTH_FAILED}.
|
||||
The default implementation always returns `.AUTH_FAILED`.
|
||||
|
||||
@param username: the username of the authenticating client
|
||||
@type username: str
|
||||
@param key: the key object provided by the client
|
||||
@type key: L{PKey <pkey.PKey>}
|
||||
@return: L{AUTH_FAILED} if the client can't authenticate
|
||||
with this key; L{AUTH_SUCCESSFUL} if it can;
|
||||
L{AUTH_PARTIALLY_SUCCESSFUL} if it can authenticate with
|
||||
this key but must continue with authentication
|
||||
@rtype: int
|
||||
:param str username: the username of the authenticating client
|
||||
:param .PKey key: the key object provided by the client
|
||||
:return:
|
||||
`.AUTH_FAILED` if the client can't authenticate with this key;
|
||||
`.AUTH_SUCCESSFUL` if it can; `.AUTH_PARTIALLY_SUCCESSFUL` if it
|
||||
can authenticate with this key but must continue with
|
||||
authentication
|
||||
:rtype: int
|
||||
"""
|
||||
return AUTH_FAILED
|
||||
|
||||
|
@ -224,24 +178,24 @@ class ServerInterface (object):
|
|||
"""
|
||||
Begin an interactive authentication challenge, if supported. You
|
||||
should override this method in server mode if you want to support the
|
||||
C{"keyboard-interactive"} auth type, which requires you to send a
|
||||
``"keyboard-interactive"`` auth type, which requires you to send a
|
||||
series of questions for the client to answer.
|
||||
|
||||
Return L{AUTH_FAILED} if this auth method isn't supported. Otherwise,
|
||||
you should return an L{InteractiveQuery} object containing the prompts
|
||||
Return `.AUTH_FAILED` if this auth method isn't supported. Otherwise,
|
||||
you should return an `.InteractiveQuery` object containing the prompts
|
||||
and instructions for the user. The response will be sent via a call
|
||||
to L{check_auth_interactive_response}.
|
||||
to `check_auth_interactive_response`.
|
||||
|
||||
The default implementation always returns L{AUTH_FAILED}.
|
||||
The default implementation always returns `.AUTH_FAILED`.
|
||||
|
||||
@param username: the username of the authenticating client
|
||||
@type username: str
|
||||
@param submethods: a comma-separated list of methods preferred by the
|
||||
client (usually empty)
|
||||
@type submethods: str
|
||||
@return: L{AUTH_FAILED} if this auth method isn't supported; otherwise
|
||||
an object containing queries for the user
|
||||
@rtype: int or L{InteractiveQuery}
|
||||
:param str username: the username of the authenticating client
|
||||
:param str submethods:
|
||||
a comma-separated list of methods preferred by the client (usually
|
||||
empty)
|
||||
:return:
|
||||
`.AUTH_FAILED` if this auth method isn't supported; otherwise an
|
||||
object containing queries for the user
|
||||
:rtype: int or `.InteractiveQuery`
|
||||
"""
|
||||
return AUTH_FAILED
|
||||
|
||||
|
@ -249,31 +203,30 @@ class ServerInterface (object):
|
|||
"""
|
||||
Continue or finish an interactive authentication challenge, if
|
||||
supported. You should override this method in server mode if you want
|
||||
to support the C{"keyboard-interactive"} auth type.
|
||||
to support the ``"keyboard-interactive"`` auth type.
|
||||
|
||||
Return L{AUTH_FAILED} if the responses are not accepted,
|
||||
L{AUTH_SUCCESSFUL} if the responses are accepted and complete
|
||||
the authentication, or L{AUTH_PARTIALLY_SUCCESSFUL} if your
|
||||
Return `.AUTH_FAILED` if the responses are not accepted,
|
||||
`.AUTH_SUCCESSFUL` if the responses are accepted and complete
|
||||
the authentication, or `.AUTH_PARTIALLY_SUCCESSFUL` if your
|
||||
authentication is stateful, and this set of responses is accepted for
|
||||
authentication, but more authentication is required. (In this latter
|
||||
case, L{get_allowed_auths} will be called to report to the client what
|
||||
case, `get_allowed_auths` will be called to report to the client what
|
||||
options it has for continuing the authentication.)
|
||||
|
||||
If you wish to continue interactive authentication with more questions,
|
||||
you may return an L{InteractiveQuery} object, which should cause the
|
||||
you may return an `.InteractiveQuery` object, which should cause the
|
||||
client to respond with more answers, calling this method again. This
|
||||
cycle can continue indefinitely.
|
||||
|
||||
The default implementation always returns L{AUTH_FAILED}.
|
||||
The default implementation always returns `.AUTH_FAILED`.
|
||||
|
||||
@param responses: list of responses from the client
|
||||
@type responses: list(str)
|
||||
@return: L{AUTH_FAILED} if the authentication fails;
|
||||
L{AUTH_SUCCESSFUL} if it succeeds;
|
||||
L{AUTH_PARTIALLY_SUCCESSFUL} if the interactive auth is
|
||||
successful, but authentication must continue; otherwise an object
|
||||
containing queries for the user
|
||||
@rtype: int or L{InteractiveQuery}
|
||||
:param list responses: list of `str` responses from the client
|
||||
:return:
|
||||
`.AUTH_FAILED` if the authentication fails; `.AUTH_SUCCESSFUL` if
|
||||
it succeeds; `.AUTH_PARTIALLY_SUCCESSFUL` if the interactive auth
|
||||
is successful, but authentication must continue; otherwise an
|
||||
object containing queries for the user
|
||||
:rtype: int or `.InteractiveQuery`
|
||||
"""
|
||||
return AUTH_FAILED
|
||||
|
||||
|
@ -281,22 +234,20 @@ class ServerInterface (object):
|
|||
"""
|
||||
Handle a request for port forwarding. The client is asking that
|
||||
connections to the given address and port be forwarded back across
|
||||
this ssh connection. An address of C{"0.0.0.0"} indicates a global
|
||||
address (any address associated with this server) and a port of C{0}
|
||||
this ssh connection. An address of ``"0.0.0.0"`` indicates a global
|
||||
address (any address associated with this server) and a port of ``0``
|
||||
indicates that no specific port is requested (usually the OS will pick
|
||||
a port).
|
||||
|
||||
The default implementation always returns C{False}, rejecting the
|
||||
The default implementation always returns ``False``, rejecting the
|
||||
port forwarding request. If the request is accepted, you should return
|
||||
the port opened for listening.
|
||||
|
||||
@param address: the requested address
|
||||
@type address: str
|
||||
@param port: the requested port
|
||||
@type port: int
|
||||
@return: the port number that was opened for listening, or C{False} to
|
||||
reject
|
||||
@rtype: int
|
||||
:param str address: the requested address
|
||||
:param int port: the requested port
|
||||
:return:
|
||||
the port number (`int`) that was opened for listening, or ``False``
|
||||
to reject
|
||||
"""
|
||||
return False
|
||||
|
||||
|
@ -306,19 +257,17 @@ class ServerInterface (object):
|
|||
If the given address and port is being forwarded across this ssh
|
||||
connection, the port should be closed.
|
||||
|
||||
@param address: the forwarded address
|
||||
@type address: str
|
||||
@param port: the forwarded port
|
||||
@type port: int
|
||||
:param str address: the forwarded address
|
||||
:param int port: the forwarded port
|
||||
"""
|
||||
pass
|
||||
|
||||
def check_global_request(self, kind, msg):
|
||||
"""
|
||||
Handle a global request of the given C{kind}. This method is called
|
||||
Handle a global request of the given ``kind``. This method is called
|
||||
in server mode and client mode, whenever the remote host makes a global
|
||||
request. If there are any arguments to the request, they will be in
|
||||
C{msg}.
|
||||
``msg``.
|
||||
|
||||
There aren't any useful global requests defined, aside from port
|
||||
forwarding, so usually this type of request is an extension to the
|
||||
|
@ -329,115 +278,100 @@ class ServerInterface (object):
|
|||
sent back with the successful result. (Note that the items in the
|
||||
tuple can only be strings, ints, longs, or bools.)
|
||||
|
||||
The default implementation always returns C{False}, indicating that it
|
||||
The default implementation always returns ``False``, indicating that it
|
||||
does not support any global requests.
|
||||
|
||||
@note: Port forwarding requests are handled separately, in
|
||||
L{check_port_forward_request}.
|
||||
.. note:: Port forwarding requests are handled separately, in
|
||||
`check_port_forward_request`.
|
||||
|
||||
@param kind: the kind of global request being made.
|
||||
@type kind: str
|
||||
@param msg: any extra arguments to the request.
|
||||
@type msg: L{Message}
|
||||
@return: C{True} or a tuple of data if the request was granted;
|
||||
C{False} otherwise.
|
||||
@rtype: bool
|
||||
:param str kind: the kind of global request being made.
|
||||
:param .Message msg: any extra arguments to the request.
|
||||
:return:
|
||||
``True`` or a `tuple` of data if the request was granted; ``False``
|
||||
otherwise.
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
### Channel requests
|
||||
|
||||
|
||||
def check_channel_pty_request(self, channel, term, width, height, pixelwidth, pixelheight,
|
||||
modes):
|
||||
"""
|
||||
Determine if a pseudo-terminal of the given dimensions (usually
|
||||
requested for shell access) can be provided on the given channel.
|
||||
|
||||
The default implementation always returns C{False}.
|
||||
The default implementation always returns ``False``.
|
||||
|
||||
@param channel: the L{Channel} the pty request arrived on.
|
||||
@type channel: L{Channel}
|
||||
@param term: type of terminal requested (for example, C{"vt100"}).
|
||||
@type term: str
|
||||
@param width: width of screen in characters.
|
||||
@type width: int
|
||||
@param height: height of screen in characters.
|
||||
@type height: int
|
||||
@param pixelwidth: width of screen in pixels, if known (may be C{0} if
|
||||
unknown).
|
||||
@type pixelwidth: int
|
||||
@param pixelheight: height of screen in pixels, if known (may be C{0}
|
||||
if unknown).
|
||||
@type pixelheight: int
|
||||
@return: C{True} if the psuedo-terminal has been allocated; C{False}
|
||||
:param .Channel channel: the `.Channel` the pty request arrived on.
|
||||
:param str term: type of terminal requested (for example, ``"vt100"``).
|
||||
:param int width: width of screen in characters.
|
||||
:param int height: height of screen in characters.
|
||||
:param int pixelwidth:
|
||||
width of screen in pixels, if known (may be ``0`` if unknown).
|
||||
:param int pixelheight:
|
||||
height of screen in pixels, if known (may be ``0`` if unknown).
|
||||
:return:
|
||||
``True`` if the psuedo-terminal has been allocated; ``False``
|
||||
otherwise.
|
||||
@rtype: bool
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_shell_request(self, channel):
|
||||
"""
|
||||
Determine if a shell will be provided to the client on the given
|
||||
channel. If this method returns C{True}, the channel should be
|
||||
channel. If this method returns ``True``, the channel should be
|
||||
connected to the stdin/stdout of a shell (or something that acts like
|
||||
a shell).
|
||||
|
||||
The default implementation always returns C{False}.
|
||||
The default implementation always returns ``False``.
|
||||
|
||||
@param channel: the L{Channel} the request arrived on.
|
||||
@type channel: L{Channel}
|
||||
@return: C{True} if this channel is now hooked up to a shell; C{False}
|
||||
if a shell can't or won't be provided.
|
||||
@rtype: bool
|
||||
:param .Channel channel: the `.Channel` the request arrived on.
|
||||
:return:
|
||||
``True`` if this channel is now hooked up to a shell; ``False`` if
|
||||
a shell can't or won't be provided.
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_exec_request(self, channel, command):
|
||||
"""
|
||||
Determine if a shell command will be executed for the client. If this
|
||||
method returns C{True}, the channel should be connected to the stdin,
|
||||
method returns ``True``, the channel should be connected to the stdin,
|
||||
stdout, and stderr of the shell command.
|
||||
|
||||
The default implementation always returns C{False}.
|
||||
The default implementation always returns ``False``.
|
||||
|
||||
@param channel: the L{Channel} the request arrived on.
|
||||
@type channel: L{Channel}
|
||||
@param command: the command to execute.
|
||||
@type command: str
|
||||
@return: C{True} if this channel is now hooked up to the stdin,
|
||||
stdout, and stderr of the executing command; C{False} if the
|
||||
command will not be executed.
|
||||
@rtype: bool
|
||||
:param .Channel channel: the `.Channel` the request arrived on.
|
||||
:param str command: the command to execute.
|
||||
:return:
|
||||
``True`` if this channel is now hooked up to the stdin, stdout, and
|
||||
stderr of the executing command; ``False`` if the command will not
|
||||
be executed.
|
||||
|
||||
@since: 1.1
|
||||
.. versionadded:: 1.1
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_subsystem_request(self, channel, name):
|
||||
"""
|
||||
Determine if a requested subsystem will be provided to the client on
|
||||
the given channel. If this method returns C{True}, all future I/O
|
||||
the given channel. If this method returns ``True``, all future I/O
|
||||
through this channel will be assumed to be connected to the requested
|
||||
subsystem. An example of a subsystem is C{sftp}.
|
||||
subsystem. An example of a subsystem is ``sftp``.
|
||||
|
||||
The default implementation checks for a subsystem handler assigned via
|
||||
L{Transport.set_subsystem_handler}.
|
||||
`.Transport.set_subsystem_handler`.
|
||||
If one has been set, the handler is invoked and this method returns
|
||||
C{True}. Otherwise it returns C{False}.
|
||||
``True``. Otherwise it returns ``False``.
|
||||
|
||||
@note: Because the default implementation uses the L{Transport} to
|
||||
.. note:: Because the default implementation uses the `.Transport` to
|
||||
identify valid subsystems, you probably won't need to override this
|
||||
method.
|
||||
|
||||
@param channel: the L{Channel} the pty request arrived on.
|
||||
@type channel: L{Channel}
|
||||
@param name: name of the requested subsystem.
|
||||
@type name: str
|
||||
@return: C{True} if this channel is now hooked up to the requested
|
||||
subsystem; C{False} if that subsystem can't or won't be provided.
|
||||
@rtype: bool
|
||||
:param .Channel channel: the `.Channel` the pty request arrived on.
|
||||
:param str name: name of the requested subsystem.
|
||||
:return:
|
||||
``True`` if this channel is now hooked up to the requested
|
||||
subsystem; ``False`` if that subsystem can't or won't be provided.
|
||||
"""
|
||||
handler_class, larg, kwarg = channel.get_transport()._get_subsystem_handler(name)
|
||||
if handler_class is None:
|
||||
|
@ -451,137 +385,178 @@ class ServerInterface (object):
|
|||
Determine if the pseudo-terminal on the given channel can be resized.
|
||||
This only makes sense if a pty was previously allocated on it.
|
||||
|
||||
The default implementation always returns C{False}.
|
||||
The default implementation always returns ``False``.
|
||||
|
||||
@param channel: the L{Channel} the pty request arrived on.
|
||||
@type channel: L{Channel}
|
||||
@param width: width of screen in characters.
|
||||
@type width: int
|
||||
@param height: height of screen in characters.
|
||||
@type height: int
|
||||
@param pixelwidth: width of screen in pixels, if known (may be C{0} if
|
||||
unknown).
|
||||
@type pixelwidth: int
|
||||
@param pixelheight: height of screen in pixels, if known (may be C{0}
|
||||
if unknown).
|
||||
@type pixelheight: int
|
||||
@return: C{True} if the terminal was resized; C{False} if not.
|
||||
@rtype: bool
|
||||
:param .Channel channel: the `.Channel` the pty request arrived on.
|
||||
:param int width: width of screen in characters.
|
||||
:param int height: height of screen in characters.
|
||||
:param int pixelwidth:
|
||||
width of screen in pixels, if known (may be ``0`` if unknown).
|
||||
:param int pixelheight:
|
||||
height of screen in pixels, if known (may be ``0`` if unknown).
|
||||
:return: ``True`` if the terminal was resized; ``False`` if not.
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_x11_request(self, channel, single_connection, auth_protocol, auth_cookie, screen_number):
|
||||
"""
|
||||
Determine if the client will be provided with an X11 session. If this
|
||||
method returns C{True}, X11 applications should be routed through new
|
||||
SSH channels, using L{Transport.open_x11_channel}.
|
||||
method returns ``True``, X11 applications should be routed through new
|
||||
SSH channels, using `.Transport.open_x11_channel`.
|
||||
|
||||
The default implementation always returns C{False}.
|
||||
The default implementation always returns ``False``.
|
||||
|
||||
@param channel: the L{Channel} the X11 request arrived on
|
||||
@type channel: L{Channel}
|
||||
@param single_connection: C{True} if only a single X11 channel should
|
||||
be opened
|
||||
@type single_connection: bool
|
||||
@param auth_protocol: the protocol used for X11 authentication
|
||||
@type auth_protocol: str
|
||||
@param auth_cookie: the cookie used to authenticate to X11
|
||||
@type auth_cookie: str
|
||||
@param screen_number: the number of the X11 screen to connect to
|
||||
@type screen_number: int
|
||||
@return: C{True} if the X11 session was opened; C{False} if not
|
||||
@rtype: bool
|
||||
:param .Channel channel: the `.Channel` the X11 request arrived on
|
||||
:param bool single_connection:
|
||||
``True`` if only a single X11 channel should be opened, else
|
||||
``False``.
|
||||
:param str auth_protocol: the protocol used for X11 authentication
|
||||
:param str auth_cookie: the cookie used to authenticate to X11
|
||||
:param int screen_number: the number of the X11 screen to connect to
|
||||
:return: ``True`` if the X11 session was opened; ``False`` if not
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_forward_agent_request(self, channel):
|
||||
"""
|
||||
Determine if the client will be provided with an forward agent session.
|
||||
If this method returns C{True}, the server will allow SSH Agent
|
||||
If this method returns ``True``, the server will allow SSH Agent
|
||||
forwarding.
|
||||
|
||||
The default implementation always returns C{False}.
|
||||
The default implementation always returns ``False``.
|
||||
|
||||
@param channel: the L{Channel} the request arrived on
|
||||
@type channel: L{Channel}
|
||||
@return: C{True} if the AgentForward was loaded; C{False} if not
|
||||
@rtype: bool
|
||||
:param .Channel channel: the `.Channel` the request arrived on
|
||||
:return: ``True`` if the AgentForward was loaded; ``False`` if not
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_channel_direct_tcpip_request(self, chanid, origin, destination):
|
||||
"""
|
||||
Determine if a local port forwarding channel will be granted, and
|
||||
return C{OPEN_SUCCEEDED} or an error code. This method is
|
||||
return ``OPEN_SUCCEEDED`` or an error code. This method is
|
||||
called in server mode when the client requests a channel, after
|
||||
authentication is complete.
|
||||
|
||||
The C{chanid} parameter is a small number that uniquely identifies the
|
||||
channel within a L{Transport}. A L{Channel} object is not created
|
||||
unless this method returns C{OPEN_SUCCEEDED} -- once a
|
||||
L{Channel} object is created, you can call L{Channel.get_id} to
|
||||
The ``chanid`` parameter is a small number that uniquely identifies the
|
||||
channel within a `.Transport`. A `.Channel` object is not created
|
||||
unless this method returns ``OPEN_SUCCEEDED`` -- once a
|
||||
`.Channel` object is created, you can call `.Channel.get_id` to
|
||||
retrieve the channel ID.
|
||||
|
||||
The origin and destination parameters are (ip_address, port) tuples
|
||||
that correspond to both ends of the TCP connection in the forwarding
|
||||
tunnel.
|
||||
|
||||
The return value should either be C{OPEN_SUCCEEDED} (or
|
||||
C{0}) to allow the channel request, or one of the following error
|
||||
The return value should either be ``OPEN_SUCCEEDED`` (or
|
||||
``0``) to allow the channel request, or one of the following error
|
||||
codes to reject it:
|
||||
- C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}
|
||||
- C{OPEN_FAILED_CONNECT_FAILED}
|
||||
- C{OPEN_FAILED_UNKNOWN_CHANNEL_TYPE}
|
||||
- C{OPEN_FAILED_RESOURCE_SHORTAGE}
|
||||
|
||||
- ``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``
|
||||
- ``OPEN_FAILED_CONNECT_FAILED``
|
||||
- ``OPEN_FAILED_UNKNOWN_CHANNEL_TYPE``
|
||||
- ``OPEN_FAILED_RESOURCE_SHORTAGE``
|
||||
|
||||
The default implementation always returns
|
||||
C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}.
|
||||
``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``.
|
||||
|
||||
@param chanid: ID of the channel
|
||||
@type chanid: int
|
||||
@param origin: 2-tuple containing the IP address and port of the
|
||||
originator (client side)
|
||||
@type origin: tuple
|
||||
@param destination: 2-tuple containing the IP address and port of the
|
||||
destination (server side)
|
||||
@type destination: tuple
|
||||
@return: a success or failure code (listed above)
|
||||
@rtype: int
|
||||
:param int chanid: ID of the channel
|
||||
:param tuple origin:
|
||||
2-tuple containing the IP address and port of the originator
|
||||
(client side)
|
||||
:param tuple destination:
|
||||
2-tuple containing the IP address and port of the destination
|
||||
(server side)
|
||||
:return: an `int` success or failure code (listed above)
|
||||
"""
|
||||
return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
||||
|
||||
def check_channel_env_request(self, channel, name, value):
|
||||
"""
|
||||
Check whether a given environment variable can be specified for the
|
||||
given channel. This method should return C{True} if the server
|
||||
is willing to set the specified environment variable. Note that
|
||||
some environment variables (e.g., PATH) can be exceedingly
|
||||
dangerous, so blindly allowing the client to set the environment
|
||||
is almost certainly not a good idea.
|
||||
|
||||
The default implementation always returns C{False}.
|
||||
|
||||
@param channel: the L{Channel} the env request arrived on
|
||||
@type channel: L{Channel}
|
||||
@param name: foo bar baz
|
||||
@type name: str
|
||||
@param value: flklj
|
||||
@type value: str
|
||||
@rtype: bool
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
class InteractiveQuery (object):
|
||||
"""
|
||||
A query (set of prompts) for a user during interactive authentication.
|
||||
"""
|
||||
|
||||
def __init__(self, name='', instructions='', *prompts):
|
||||
"""
|
||||
Create a new interactive query to send to the client. The name and
|
||||
instructions are optional, but are generally displayed to the end
|
||||
user. A list of prompts may be included, or they may be added via
|
||||
the `add_prompt` method.
|
||||
|
||||
:param str name: name of this query
|
||||
:param str instructions:
|
||||
user instructions (usually short) about this query
|
||||
:param str prompts: one or more authentication prompts
|
||||
"""
|
||||
self.name = name
|
||||
self.instructions = instructions
|
||||
self.prompts = []
|
||||
for x in prompts:
|
||||
if isinstance(x, string_types):
|
||||
self.add_prompt(x)
|
||||
else:
|
||||
self.add_prompt(x[0], x[1])
|
||||
|
||||
def add_prompt(self, prompt, echo=True):
|
||||
"""
|
||||
Add a prompt to this query. The prompt should be a (reasonably short)
|
||||
string. Multiple prompts can be added to the same query.
|
||||
|
||||
:param str prompt: the user prompt
|
||||
:param bool echo:
|
||||
``True`` (default) if the user's response should be echoed;
|
||||
``False`` if not (for a password or similar)
|
||||
"""
|
||||
self.prompts.append((prompt, echo))
|
||||
|
||||
|
||||
class SubsystemHandler (threading.Thread):
|
||||
"""
|
||||
Handler for a subsytem in server mode. If you create a subclass of this
|
||||
class and pass it to
|
||||
L{Transport.set_subsystem_handler},
|
||||
an object of this
|
||||
class and pass it to `.Transport.set_subsystem_handler`, an object of this
|
||||
class will be created for each request for this subsystem. Each new object
|
||||
will be executed within its own new thread by calling L{start_subsystem}.
|
||||
will be executed within its own new thread by calling `start_subsystem`.
|
||||
When that method completes, the channel is closed.
|
||||
|
||||
For example, if you made a subclass C{MP3Handler} and registered it as the
|
||||
handler for subsystem C{"mp3"}, then whenever a client has successfully
|
||||
authenticated and requests subsytem C{"mp3"}, an object of class
|
||||
C{MP3Handler} will be created, and L{start_subsystem} will be called on
|
||||
For example, if you made a subclass ``MP3Handler`` and registered it as the
|
||||
handler for subsystem ``"mp3"``, then whenever a client has successfully
|
||||
authenticated and requests subsytem ``"mp3"``, an object of class
|
||||
``MP3Handler`` will be created, and `start_subsystem` will be called on
|
||||
it from a new thread.
|
||||
"""
|
||||
def __init__(self, channel, name, server):
|
||||
"""
|
||||
Create a new handler for a channel. This is used by L{ServerInterface}
|
||||
Create a new handler for a channel. This is used by `.ServerInterface`
|
||||
to start up a new handler when a channel requests this subsystem. You
|
||||
don't need to override this method, but if you do, be sure to pass the
|
||||
C{channel} and C{name} parameters through to the original C{__init__}
|
||||
``channel`` and ``name`` parameters through to the original ``__init__``
|
||||
method here.
|
||||
|
||||
@param channel: the channel associated with this subsystem request.
|
||||
@type channel: L{Channel}
|
||||
@param name: name of the requested subsystem.
|
||||
@type name: str
|
||||
@param server: the server object for the session that started this
|
||||
subsystem
|
||||
@type server: L{ServerInterface}
|
||||
:param .Channel channel: the channel associated with this subsystem request.
|
||||
:param str name: name of the requested subsystem.
|
||||
:param .ServerInterface server:
|
||||
the server object for the session that started this subsystem
|
||||
"""
|
||||
threading.Thread.__init__(self, target=self._run)
|
||||
self.__channel = channel
|
||||
|
@ -591,10 +566,8 @@ class SubsystemHandler (threading.Thread):
|
|||
|
||||
def get_server(self):
|
||||
"""
|
||||
Return the L{ServerInterface} object associated with this channel and
|
||||
Return the `.ServerInterface` object associated with this channel and
|
||||
subsystem.
|
||||
|
||||
@rtype: L{ServerInterface}
|
||||
"""
|
||||
return self.__server
|
||||
|
||||
|
@ -602,7 +575,7 @@ class SubsystemHandler (threading.Thread):
|
|||
try:
|
||||
self.__transport._log(DEBUG, 'Starting handler for subsystem %s' % self.__name)
|
||||
self.start_subsystem(self.__name, self.__transport, self.__channel)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
self.__transport._log(ERROR, 'Exception in subsystem handler for "%s": %s' %
|
||||
(self.__name, str(e)))
|
||||
self.__transport._log(ERROR, util.tb_strings())
|
||||
|
@ -619,22 +592,20 @@ class SubsystemHandler (threading.Thread):
|
|||
subsystem is finished, this method will return. After this method
|
||||
returns, the channel is closed.
|
||||
|
||||
The combination of C{transport} and C{channel} are unique; this handler
|
||||
corresponds to exactly one L{Channel} on one L{Transport}.
|
||||
The combination of ``transport`` and ``channel`` are unique; this handler
|
||||
corresponds to exactly one `.Channel` on one `.Transport`.
|
||||
|
||||
@note: It is the responsibility of this method to exit if the
|
||||
underlying L{Transport} is closed. This can be done by checking
|
||||
L{Transport.is_active} or noticing an EOF
|
||||
on the L{Channel}. If this method loops forever without checking
|
||||
for this case, your python interpreter may refuse to exit because
|
||||
this thread will still be running.
|
||||
.. note::
|
||||
It is the responsibility of this method to exit if the underlying
|
||||
`.Transport` is closed. This can be done by checking
|
||||
`.Transport.is_active` or noticing an EOF on the `.Channel`. If
|
||||
this method loops forever without checking for this case, your
|
||||
Python interpreter may refuse to exit because this thread will
|
||||
still be running.
|
||||
|
||||
@param name: name of the requested subsystem.
|
||||
@type name: str
|
||||
@param transport: the server-mode L{Transport}.
|
||||
@type transport: L{Transport}
|
||||
@param channel: the channel associated with this subsystem request.
|
||||
@type channel: L{Channel}
|
||||
:param str name: name of the requested subsystem.
|
||||
:param .Transport transport: the server-mode `.Transport`.
|
||||
:param .Channel channel: the channel associated with this subsystem request.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -643,6 +614,6 @@ class SubsystemHandler (threading.Thread):
|
|||
Perform any cleanup at the end of a subsystem. The default
|
||||
implementation just closes the channel.
|
||||
|
||||
@since: 1.1
|
||||
.. versionadded:: 1.1
|
||||
"""
|
||||
self.__channel.close()
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -20,16 +20,15 @@ import select
|
|||
import socket
|
||||
import struct
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.channel import Channel
|
||||
from paramiko.common import asbytes, DEBUG
|
||||
from paramiko.message import Message
|
||||
from paramiko.py3compat import byte_chr, byte_ord
|
||||
|
||||
|
||||
CMD_INIT, CMD_VERSION, CMD_OPEN, CMD_CLOSE, CMD_READ, CMD_WRITE, CMD_LSTAT, CMD_FSTAT, \
|
||||
CMD_SETSTAT, CMD_FSETSTAT, CMD_OPENDIR, CMD_READDIR, CMD_REMOVE, CMD_MKDIR, \
|
||||
CMD_RMDIR, CMD_REALPATH, CMD_STAT, CMD_RENAME, CMD_READLINK, CMD_SYMLINK \
|
||||
= range(1, 21)
|
||||
CMD_RMDIR, CMD_REALPATH, CMD_STAT, CMD_RENAME, CMD_READLINK, CMD_SYMLINK = range(1, 21)
|
||||
CMD_STATUS, CMD_HANDLE, CMD_DATA, CMD_NAME, CMD_ATTRS = range(101, 106)
|
||||
CMD_EXTENDED, CMD_EXTENDED_REPLY = range(200, 202)
|
||||
|
||||
|
@ -99,10 +98,8 @@ class BaseSFTP (object):
|
|||
self.sock = None
|
||||
self.ultra_debug = False
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _send_version(self):
|
||||
self._send_packet(CMD_INIT, struct.pack('>I', _VERSION))
|
||||
t, data = self._read_packet()
|
||||
|
@ -125,7 +122,7 @@ class BaseSFTP (object):
|
|||
msg = Message()
|
||||
msg.add_int(_VERSION)
|
||||
msg.add(*extension_pairs)
|
||||
self._send_packet(CMD_VERSION, str(msg))
|
||||
self._send_packet(CMD_VERSION, msg)
|
||||
return version
|
||||
|
||||
def _log(self, level, msg, *args):
|
||||
|
@ -142,7 +139,7 @@ class BaseSFTP (object):
|
|||
return
|
||||
|
||||
def _read_all(self, n):
|
||||
out = ''
|
||||
out = bytes()
|
||||
while n > 0:
|
||||
if isinstance(self.sock, socket.socket):
|
||||
# sometimes sftp is used directly over a socket instead of
|
||||
|
@ -166,7 +163,8 @@ class BaseSFTP (object):
|
|||
|
||||
def _send_packet(self, t, packet):
|
||||
#self._log(DEBUG2, 'write: %s (len=%d)' % (CMD_NAMES.get(t, '0x%02x' % t), len(packet)))
|
||||
out = struct.pack('>I', len(packet) + 1) + chr(t) + packet
|
||||
packet = asbytes(packet)
|
||||
out = struct.pack('>I', len(packet) + 1) + byte_chr(t) + packet
|
||||
if self.ultra_debug:
|
||||
self._log(DEBUG, util.format_binary(out, 'OUT: '))
|
||||
self._write_all(out)
|
||||
|
@ -175,14 +173,14 @@ class BaseSFTP (object):
|
|||
x = self._read_all(4)
|
||||
# most sftp servers won't accept packets larger than about 32k, so
|
||||
# anything with the high byte set (> 16MB) is just garbage.
|
||||
if x[0] != '\x00':
|
||||
if byte_ord(x[0]):
|
||||
raise SFTPError('Garbage packet received')
|
||||
size = struct.unpack('>I', x)[0]
|
||||
data = self._read_all(size)
|
||||
if self.ultra_debug:
|
||||
self._log(DEBUG, util.format_binary(data, 'IN: '));
|
||||
self._log(DEBUG, util.format_binary(data, 'IN: '))
|
||||
if size > 0:
|
||||
t = ord(data[0])
|
||||
t = byte_ord(data[0])
|
||||
#self._log(DEBUG2, 'read: %s (len=%d)' % (CMD_NAMES.get(t), '0x%02x' % t, len(data)-1))
|
||||
return t, data[1:]
|
||||
return 0, ''
|
||||
return 0, bytes()
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -18,33 +18,34 @@
|
|||
|
||||
import stat
|
||||
import time
|
||||
from paramiko.common import *
|
||||
from paramiko.sftp import *
|
||||
from paramiko.common import x80000000, o700, o70, xffffffff
|
||||
from paramiko.py3compat import long, b
|
||||
|
||||
|
||||
class SFTPAttributes (object):
|
||||
"""
|
||||
Representation of the attributes of a file (or proxied file) for SFTP in
|
||||
client or server mode. It attemps to mirror the object returned by
|
||||
C{os.stat} as closely as possible, so it may have the following fields,
|
||||
with the same meanings as those returned by an C{os.stat} object:
|
||||
- st_size
|
||||
- st_uid
|
||||
- st_gid
|
||||
- st_mode
|
||||
- st_atime
|
||||
- st_mtime
|
||||
`os.stat` as closely as possible, so it may have the following fields,
|
||||
with the same meanings as those returned by an `os.stat` object:
|
||||
|
||||
- ``st_size``
|
||||
- ``st_uid``
|
||||
- ``st_gid``
|
||||
- ``st_mode``
|
||||
- ``st_atime``
|
||||
- ``st_mtime``
|
||||
|
||||
Because SFTP allows flags to have other arbitrary named attributes, these
|
||||
are stored in a dict named C{attr}. Occasionally, the filename is also
|
||||
stored, in C{filename}.
|
||||
are stored in a dict named ``attr``. Occasionally, the filename is also
|
||||
stored, in ``filename``.
|
||||
"""
|
||||
|
||||
FLAG_SIZE = 1
|
||||
FLAG_UIDGID = 2
|
||||
FLAG_PERMISSIONS = 4
|
||||
FLAG_AMTIME = 8
|
||||
FLAG_EXTENDED = 0x80000000L
|
||||
FLAG_EXTENDED = x80000000
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
|
@ -61,15 +62,12 @@ class SFTPAttributes (object):
|
|||
|
||||
def from_stat(cls, obj, filename=None):
|
||||
"""
|
||||
Create an SFTPAttributes object from an existing C{stat} object (an
|
||||
object returned by C{os.stat}).
|
||||
Create an `.SFTPAttributes` object from an existing ``stat`` object (an
|
||||
object returned by `os.stat`).
|
||||
|
||||
@param obj: an object returned by C{os.stat} (or equivalent).
|
||||
@type obj: object
|
||||
@param filename: the filename associated with this file.
|
||||
@type filename: str
|
||||
@return: new L{SFTPAttributes} object with the same attribute fields.
|
||||
@rtype: L{SFTPAttributes}
|
||||
:param object obj: an object returned by `os.stat` (or equivalent).
|
||||
:param str filename: the filename associated with this file.
|
||||
:return: new `.SFTPAttributes` object with the same attribute fields.
|
||||
"""
|
||||
attr = cls()
|
||||
attr.st_size = obj.st_size
|
||||
|
@ -86,10 +84,8 @@ class SFTPAttributes (object):
|
|||
def __repr__(self):
|
||||
return '<SFTPAttributes: %s>' % self._debug_str()
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _from_msg(cls, msg, filename=None, longname=None):
|
||||
attr = cls()
|
||||
attr._unpack(msg)
|
||||
|
@ -143,7 +139,7 @@ class SFTPAttributes (object):
|
|||
msg.add_int(long(self.st_mtime))
|
||||
if self._flags & self.FLAG_EXTENDED:
|
||||
msg.add_int(len(self.attr))
|
||||
for key, val in self.attr.iteritems():
|
||||
for key, val in self.attr.items():
|
||||
msg.add_string(key)
|
||||
msg.add_string(val)
|
||||
return
|
||||
|
@ -158,7 +154,7 @@ class SFTPAttributes (object):
|
|||
out += 'mode=' + oct(self.st_mode) + ' '
|
||||
if (self.st_atime is not None) and (self.st_mtime is not None):
|
||||
out += 'atime=%d mtime=%d ' % (self.st_atime, self.st_mtime)
|
||||
for k, v in self.attr.iteritems():
|
||||
for k, v in self.attr.items():
|
||||
out += '"%s"=%r ' % (str(k), v)
|
||||
out += ']'
|
||||
return out
|
||||
|
@ -175,7 +171,7 @@ class SFTPAttributes (object):
|
|||
_rwx = staticmethod(_rwx)
|
||||
|
||||
def __str__(self):
|
||||
"create a unix-style long description of the file (like ls -l)"
|
||||
"""create a unix-style long description of the file (like ls -l)"""
|
||||
if self.st_mode is not None:
|
||||
kind = stat.S_IFMT(self.st_mode)
|
||||
if kind == stat.S_IFIFO:
|
||||
|
@ -194,13 +190,13 @@ class SFTPAttributes (object):
|
|||
ks = 's'
|
||||
else:
|
||||
ks = '?'
|
||||
ks += self._rwx((self.st_mode & 0700) >> 6, self.st_mode & stat.S_ISUID)
|
||||
ks += self._rwx((self.st_mode & 070) >> 3, self.st_mode & stat.S_ISGID)
|
||||
ks += self._rwx((self.st_mode & o700) >> 6, self.st_mode & stat.S_ISUID)
|
||||
ks += self._rwx((self.st_mode & o70) >> 3, self.st_mode & stat.S_ISGID)
|
||||
ks += self._rwx(self.st_mode & 7, self.st_mode & stat.S_ISVTX, True)
|
||||
else:
|
||||
ks = '?---------'
|
||||
# compute display date
|
||||
if (self.st_mtime is None) or (self.st_mtime == 0xffffffffL):
|
||||
if (self.st_mtime is None) or (self.st_mtime == xffffffff):
|
||||
# shouldn't really happen
|
||||
datestr = '(unknown date)'
|
||||
else:
|
||||
|
@ -221,3 +217,5 @@ class SFTPAttributes (object):
|
|||
|
||||
return '%s 1 %-8d %-8d %8d %-12s %s' % (ks, uid, gid, self.st_size, datestr, filename)
|
||||
|
||||
def asbytes(self):
|
||||
return b(str(self))
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
#
|
||||
# This file is part of paramiko.
|
||||
# This file is part of Paramiko.
|
||||
#
|
||||
# Paramiko is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free
|
||||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -16,9 +16,6 @@
|
|||
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Client-mode SFTP support.
|
||||
"""
|
||||
|
||||
from binascii import hexlify
|
||||
import errno
|
||||
|
@ -27,8 +24,18 @@ import stat
|
|||
import threading
|
||||
import time
|
||||
import weakref
|
||||
from paramiko import util
|
||||
from paramiko.channel import Channel
|
||||
from paramiko.message import Message
|
||||
from paramiko.common import INFO, DEBUG, o777
|
||||
from paramiko.py3compat import bytestring, b, u, long, string_types, bytes_types
|
||||
from paramiko.sftp import BaseSFTP, CMD_OPENDIR, CMD_HANDLE, SFTPError, CMD_READDIR, \
|
||||
CMD_NAME, CMD_CLOSE, SFTP_FLAG_READ, SFTP_FLAG_WRITE, SFTP_FLAG_CREATE, \
|
||||
SFTP_FLAG_TRUNC, SFTP_FLAG_APPEND, SFTP_FLAG_EXCL, CMD_OPEN, CMD_REMOVE, \
|
||||
CMD_RENAME, CMD_MKDIR, CMD_RMDIR, CMD_STAT, CMD_ATTRS, CMD_LSTAT, \
|
||||
CMD_SYMLINK, CMD_SETSTAT, CMD_READLINK, CMD_REALPATH, CMD_STATUS, SFTP_OK, \
|
||||
SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED
|
||||
|
||||
from paramiko.sftp import *
|
||||
from paramiko.sftp_attr import SFTPAttributes
|
||||
from paramiko.ssh_exception import SSHException
|
||||
from paramiko.sftp_file import SFTPFile
|
||||
|
@ -42,31 +49,33 @@ def _to_unicode(s):
|
|||
"""
|
||||
try:
|
||||
return s.encode('ascii')
|
||||
except UnicodeError:
|
||||
except (UnicodeError, AttributeError):
|
||||
try:
|
||||
return s.decode('utf-8')
|
||||
except UnicodeError:
|
||||
return s
|
||||
|
||||
b_slash = b'/'
|
||||
|
||||
|
||||
class SFTPClient(BaseSFTP):
|
||||
"""
|
||||
SFTP client object. C{SFTPClient} is used to open an sftp session across
|
||||
an open ssh L{Transport} and do remote file operations.
|
||||
"""
|
||||
SFTP client object.
|
||||
|
||||
Used to open an SFTP session across an open SSH `.Transport` and perform
|
||||
remote file operations.
|
||||
"""
|
||||
def __init__(self, sock):
|
||||
"""
|
||||
Create an SFTP client from an existing L{Channel}. The channel
|
||||
should already have requested the C{"sftp"} subsystem.
|
||||
Create an SFTP client from an existing `.Channel`. The channel
|
||||
should already have requested the ``"sftp"`` subsystem.
|
||||
|
||||
An alternate way to create an SFTP client context is by using
|
||||
L{from_transport}.
|
||||
`from_transport`.
|
||||
|
||||
@param sock: an open L{Channel} using the C{"sftp"} subsystem
|
||||
@type sock: L{Channel}
|
||||
:param .Channel sock: an open `.Channel` using the ``"sftp"`` subsystem
|
||||
|
||||
@raise SSHException: if there's an exception while negotiating
|
||||
:raises SSHException: if there's an exception while negotiating
|
||||
sftp
|
||||
"""
|
||||
BaseSFTP.__init__(self)
|
||||
|
@ -85,19 +94,18 @@ class SFTPClient (BaseSFTP):
|
|||
self.ultra_debug = transport.get_hexdump()
|
||||
try:
|
||||
server_version = self._send_version()
|
||||
except EOFError, x:
|
||||
except EOFError:
|
||||
raise SSHException('EOF during negotiation')
|
||||
self._log(INFO, 'Opened sftp connection (server version %d)' % server_version)
|
||||
|
||||
def from_transport(cls, t):
|
||||
"""
|
||||
Create an SFTP client channel from an open L{Transport}.
|
||||
Create an SFTP client channel from an open `.Transport`.
|
||||
|
||||
@param t: an open L{Transport} which is already authenticated
|
||||
@type t: L{Transport}
|
||||
@return: a new L{SFTPClient} object, referring to an sftp session
|
||||
(channel) across the transport
|
||||
@rtype: L{SFTPClient}
|
||||
:param .Transport t: an open `.Transport` which is already authenticated
|
||||
:return:
|
||||
a new `.SFTPClient` object, referring to an sftp session (channel)
|
||||
across the transport
|
||||
"""
|
||||
chan = t.open_session()
|
||||
if chan is None:
|
||||
|
@ -109,84 +117,79 @@ class SFTPClient (BaseSFTP):
|
|||
def _log(self, level, msg, *args):
|
||||
if isinstance(msg, list):
|
||||
for m in msg:
|
||||
super(SFTPClient, self)._log(level, "[chan %s] " + m, *([ self.sock.get_name() ] + list(args)))
|
||||
self._log(level, m, *args)
|
||||
else:
|
||||
# escape '%' in msg (they could come from file or directory names) before logging
|
||||
msg = msg.replace('%','%%')
|
||||
super(SFTPClient, self)._log(level, "[chan %s] " + msg, *([self.sock.get_name()] + list(args)))
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the SFTP session and its underlying channel.
|
||||
|
||||
@since: 1.4
|
||||
.. versionadded:: 1.4
|
||||
"""
|
||||
self._log(INFO, 'sftp session closed.')
|
||||
self.sock.close()
|
||||
|
||||
def get_channel(self):
|
||||
"""
|
||||
Return the underlying L{Channel} object for this SFTP session. This
|
||||
Return the underlying `.Channel` object for this SFTP session. This
|
||||
might be useful for doing things like setting a timeout on the channel.
|
||||
|
||||
@return: the SSH channel
|
||||
@rtype: L{Channel}
|
||||
|
||||
@since: 1.7.1
|
||||
.. versionadded:: 1.7.1
|
||||
"""
|
||||
return self.sock
|
||||
|
||||
def listdir(self, path='.'):
|
||||
"""
|
||||
Return a list containing the names of the entries in the given C{path}.
|
||||
The list is in arbitrary order. It does not include the special
|
||||
entries C{'.'} and C{'..'} even if they are present in the folder.
|
||||
This method is meant to mirror C{os.listdir} as closely as possible.
|
||||
For a list of full L{SFTPAttributes} objects, see L{listdir_attr}.
|
||||
Return a list containing the names of the entries in the given ``path``.
|
||||
|
||||
@param path: path to list (defaults to C{'.'})
|
||||
@type path: str
|
||||
@return: list of filenames
|
||||
@rtype: list of str
|
||||
The list is in arbitrary order. It does not include the special
|
||||
entries ``'.'`` and ``'..'`` even if they are present in the folder.
|
||||
This method is meant to mirror ``os.listdir`` as closely as possible.
|
||||
For a list of full `.SFTPAttributes` objects, see `listdir_attr`.
|
||||
|
||||
:param str path: path to list (defaults to ``'.'``)
|
||||
"""
|
||||
return [f.filename for f in self.listdir_attr(path)]
|
||||
|
||||
def listdir_attr(self, path='.'):
|
||||
"""
|
||||
Return a list containing L{SFTPAttributes} objects corresponding to
|
||||
files in the given C{path}. The list is in arbitrary order. It does
|
||||
not include the special entries C{'.'} and C{'..'} even if they are
|
||||
Return a list containing `.SFTPAttributes` objects corresponding to
|
||||
files in the given ``path``. The list is in arbitrary order. It does
|
||||
not include the special entries ``'.'`` and ``'..'`` even if they are
|
||||
present in the folder.
|
||||
|
||||
The returned L{SFTPAttributes} objects will each have an additional
|
||||
field: C{longname}, which may contain a formatted string of the file's
|
||||
The returned `.SFTPAttributes` objects will each have an additional
|
||||
field: ``longname``, which may contain a formatted string of the file's
|
||||
attributes, in unix format. The content of this string will probably
|
||||
depend on the SFTP server implementation.
|
||||
|
||||
@param path: path to list (defaults to C{'.'})
|
||||
@type path: str
|
||||
@return: list of attributes
|
||||
@rtype: list of L{SFTPAttributes}
|
||||
:param str path: path to list (defaults to ``'.'``)
|
||||
:return: list of `.SFTPAttributes` objects
|
||||
|
||||
@since: 1.2
|
||||
.. versionadded:: 1.2
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'listdir(%r)' % path)
|
||||
t, msg = self._request(CMD_OPENDIR, path)
|
||||
if t != CMD_HANDLE:
|
||||
raise SFTPError('Expected handle')
|
||||
handle = msg.get_string()
|
||||
handle = msg.get_binary()
|
||||
filelist = []
|
||||
while True:
|
||||
try:
|
||||
t, msg = self._request(CMD_READDIR, handle)
|
||||
except EOFError, e:
|
||||
except EOFError:
|
||||
# done with handle
|
||||
break
|
||||
if t != CMD_NAME:
|
||||
raise SFTPError('Expected name response')
|
||||
count = msg.get_int()
|
||||
for i in range(count):
|
||||
filename = _to_unicode(msg.get_string())
|
||||
longname = _to_unicode(msg.get_string())
|
||||
filename = msg.get_text()
|
||||
longname = msg.get_text()
|
||||
attr = SFTPAttributes._from_msg(msg, filename, longname)
|
||||
if (filename != '.') and (filename != '..'):
|
||||
filelist.append(attr)
|
||||
|
@ -196,37 +199,34 @@ class SFTPClient (BaseSFTP):
|
|||
def open(self, filename, mode='r', bufsize=-1):
|
||||
"""
|
||||
Open a file on the remote server. The arguments are the same as for
|
||||
python's built-in C{file} (aka C{open}). A file-like object is
|
||||
returned, which closely mimics the behavior of a normal python file
|
||||
object.
|
||||
Python's built-in `python:file` (aka `python:open`). A file-like
|
||||
object is returned, which closely mimics the behavior of a normal
|
||||
Python file object, including the ability to be used as a context
|
||||
manager.
|
||||
|
||||
The mode indicates how the file is to be opened: C{'r'} for reading,
|
||||
C{'w'} for writing (truncating an existing file), C{'a'} for appending,
|
||||
C{'r+'} for reading/writing, C{'w+'} for reading/writing (truncating an
|
||||
existing file), C{'a+'} for reading/appending. The python C{'b'} flag
|
||||
is ignored, since SSH treats all files as binary. The C{'U'} flag is
|
||||
supported in a compatible way.
|
||||
The mode indicates how the file is to be opened: ``'r'`` for reading,
|
||||
``'w'`` for writing (truncating an existing file), ``'a'`` for
|
||||
appending, ``'r+'`` for reading/writing, ``'w+'`` for reading/writing
|
||||
(truncating an existing file), ``'a+'`` for reading/appending. The
|
||||
Python ``'b'`` flag is ignored, since SSH treats all files as binary.
|
||||
The ``'U'`` flag is supported in a compatible way.
|
||||
|
||||
Since 1.5.2, an C{'x'} flag indicates that the operation should only
|
||||
Since 1.5.2, an ``'x'`` flag indicates that the operation should only
|
||||
succeed if the file was created and did not previously exist. This has
|
||||
no direct mapping to python's file flags, but is commonly known as the
|
||||
C{O_EXCL} flag in posix.
|
||||
no direct mapping to Python's file flags, but is commonly known as the
|
||||
``O_EXCL`` flag in posix.
|
||||
|
||||
The file will be buffered in standard python style by default, but
|
||||
can be altered with the C{bufsize} parameter. C{0} turns off
|
||||
buffering, C{1} uses line buffering, and any number greater than 1
|
||||
(C{>1}) uses that specific buffer size.
|
||||
The file will be buffered in standard Python style by default, but
|
||||
can be altered with the ``bufsize`` parameter. ``0`` turns off
|
||||
buffering, ``1`` uses line buffering, and any number greater than 1
|
||||
(``>1``) uses that specific buffer size.
|
||||
|
||||
@param filename: name of the file to open
|
||||
@type filename: str
|
||||
@param mode: mode (python-style) to open in
|
||||
@type mode: str
|
||||
@param bufsize: desired buffering (-1 = default buffer size)
|
||||
@type bufsize: int
|
||||
@return: a file object representing the open file
|
||||
@rtype: SFTPFile
|
||||
:param str filename: name of the file to open
|
||||
:param str mode: mode (Python-style) to open in
|
||||
:param int bufsize: desired buffering (-1 = default buffer size)
|
||||
:return: an `.SFTPFile` object representing the open file
|
||||
|
||||
@raise IOError: if the file could not be opened.
|
||||
:raises IOError: if the file could not be opened.
|
||||
"""
|
||||
filename = self._adjust_cwd(filename)
|
||||
self._log(DEBUG, 'open(%r, %r)' % (filename, mode))
|
||||
|
@ -235,32 +235,31 @@ class SFTPClient (BaseSFTP):
|
|||
imode |= SFTP_FLAG_READ
|
||||
if ('w' in mode) or ('+' in mode) or ('a' in mode):
|
||||
imode |= SFTP_FLAG_WRITE
|
||||
if ('w' in mode):
|
||||
if 'w' in mode:
|
||||
imode |= SFTP_FLAG_CREATE | SFTP_FLAG_TRUNC
|
||||
if ('a' in mode):
|
||||
if 'a' in mode:
|
||||
imode |= SFTP_FLAG_CREATE | SFTP_FLAG_APPEND
|
||||
if ('x' in mode):
|
||||
if 'x' in mode:
|
||||
imode |= SFTP_FLAG_CREATE | SFTP_FLAG_EXCL
|
||||
attrblock = SFTPAttributes()
|
||||
t, msg = self._request(CMD_OPEN, filename, imode, attrblock)
|
||||
if t != CMD_HANDLE:
|
||||
raise SFTPError('Expected handle')
|
||||
handle = msg.get_string()
|
||||
handle = msg.get_binary()
|
||||
self._log(DEBUG, 'open(%r, %r) -> %s' % (filename, mode, hexlify(handle)))
|
||||
return SFTPFile(self, handle, mode, bufsize)
|
||||
|
||||
# python continues to vacillate about "open" vs "file"...
|
||||
# Python continues to vacillate about "open" vs "file"...
|
||||
file = open
|
||||
|
||||
def remove(self, path):
|
||||
"""
|
||||
Remove the file at the given path. This only works on files; for
|
||||
removing folders (directories), use L{rmdir}.
|
||||
removing folders (directories), use `rmdir`.
|
||||
|
||||
@param path: path (absolute or relative) of the file to remove
|
||||
@type path: str
|
||||
:param str path: path (absolute or relative) of the file to remove
|
||||
|
||||
@raise IOError: if the path refers to a folder (directory)
|
||||
:raises IOError: if the path refers to a folder (directory)
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'remove(%r)' % path)
|
||||
|
@ -270,14 +269,12 @@ class SFTPClient (BaseSFTP):
|
|||
|
||||
def rename(self, oldpath, newpath):
|
||||
"""
|
||||
Rename a file or folder from C{oldpath} to C{newpath}.
|
||||
Rename a file or folder from ``oldpath`` to ``newpath``.
|
||||
|
||||
@param oldpath: existing name of the file or folder
|
||||
@type oldpath: str
|
||||
@param newpath: new name for the file or folder
|
||||
@type newpath: str
|
||||
:param str oldpath: existing name of the file or folder
|
||||
:param str newpath: new name for the file or folder
|
||||
|
||||
@raise IOError: if C{newpath} is a folder, or something else goes
|
||||
:raises IOError: if ``newpath`` is a folder, or something else goes
|
||||
wrong
|
||||
"""
|
||||
oldpath = self._adjust_cwd(oldpath)
|
||||
|
@ -285,16 +282,14 @@ class SFTPClient (BaseSFTP):
|
|||
self._log(DEBUG, 'rename(%r, %r)' % (oldpath, newpath))
|
||||
self._request(CMD_RENAME, oldpath, newpath)
|
||||
|
||||
def mkdir(self, path, mode=0777):
|
||||
def mkdir(self, path, mode=o777):
|
||||
"""
|
||||
Create a folder (directory) named C{path} with numeric mode C{mode}.
|
||||
Create a folder (directory) named ``path`` with numeric mode ``mode``.
|
||||
The default mode is 0777 (octal). On some systems, mode is ignored.
|
||||
Where it is used, the current umask value is first masked out.
|
||||
|
||||
@param path: name of the folder to create
|
||||
@type path: str
|
||||
@param mode: permissions (posix-style) for the newly-created folder
|
||||
@type mode: int
|
||||
:param str path: name of the folder to create
|
||||
:param int mode: permissions (posix-style) for the newly-created folder
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'mkdir(%r, %r)' % (path, mode))
|
||||
|
@ -304,10 +299,9 @@ class SFTPClient (BaseSFTP):
|
|||
|
||||
def rmdir(self, path):
|
||||
"""
|
||||
Remove the folder named C{path}.
|
||||
Remove the folder named ``path``.
|
||||
|
||||
@param path: name of the folder to remove
|
||||
@type path: str
|
||||
:param str path: name of the folder to remove
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'rmdir(%r)' % path)
|
||||
|
@ -317,20 +311,20 @@ class SFTPClient (BaseSFTP):
|
|||
"""
|
||||
Retrieve information about a file on the remote system. The return
|
||||
value is an object whose attributes correspond to the attributes of
|
||||
python's C{stat} structure as returned by C{os.stat}, except that it
|
||||
Python's ``stat`` structure as returned by ``os.stat``, except that it
|
||||
contains fewer fields. An SFTP server may return as much or as little
|
||||
info as it wants, so the results may vary from server to server.
|
||||
|
||||
Unlike a python C{stat} object, the result may not be accessed as a
|
||||
tuple. This is mostly due to the author's slack factor.
|
||||
Unlike a Python `python:stat` object, the result may not be accessed as
|
||||
a tuple. This is mostly due to the author's slack factor.
|
||||
|
||||
The fields supported are: C{st_mode}, C{st_size}, C{st_uid}, C{st_gid},
|
||||
C{st_atime}, and C{st_mtime}.
|
||||
The fields supported are: ``st_mode``, ``st_size``, ``st_uid``,
|
||||
``st_gid``, ``st_atime``, and ``st_mtime``.
|
||||
|
||||
@param path: the filename to stat
|
||||
@type path: str
|
||||
@return: an object containing attributes about the given file
|
||||
@rtype: SFTPAttributes
|
||||
:param str path: the filename to stat
|
||||
:return:
|
||||
an `.SFTPAttributes` object containing attributes about the given
|
||||
file
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'stat(%r)' % path)
|
||||
|
@ -343,12 +337,12 @@ class SFTPClient (BaseSFTP):
|
|||
"""
|
||||
Retrieve information about a file on the remote system, without
|
||||
following symbolic links (shortcuts). This otherwise behaves exactly
|
||||
the same as L{stat}.
|
||||
the same as `stat`.
|
||||
|
||||
@param path: the filename to stat
|
||||
@type path: str
|
||||
@return: an object containing attributes about the given file
|
||||
@rtype: SFTPAttributes
|
||||
:param str path: the filename to stat
|
||||
:return:
|
||||
an `.SFTPAttributes` object containing attributes about the given
|
||||
file
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'lstat(%r)' % path)
|
||||
|
@ -359,30 +353,25 @@ class SFTPClient (BaseSFTP):
|
|||
|
||||
def symlink(self, source, dest):
|
||||
"""
|
||||
Create a symbolic link (shortcut) of the C{source} path at
|
||||
C{destination}.
|
||||
Create a symbolic link (shortcut) of the ``source`` path at
|
||||
``destination``.
|
||||
|
||||
@param source: path of the original file
|
||||
@type source: str
|
||||
@param dest: path of the newly created symlink
|
||||
@type dest: str
|
||||
:param str source: path of the original file
|
||||
:param str dest: path of the newly created symlink
|
||||
"""
|
||||
dest = self._adjust_cwd(dest)
|
||||
self._log(DEBUG, 'symlink(%r, %r)' % (source, dest))
|
||||
if type(source) is unicode:
|
||||
source = source.encode('utf-8')
|
||||
source = bytestring(source)
|
||||
self._request(CMD_SYMLINK, source, dest)
|
||||
|
||||
def chmod(self, path, mode):
|
||||
"""
|
||||
Change the mode (permissions) of a file. The permissions are
|
||||
unix-style and identical to those used by python's C{os.chmod}
|
||||
unix-style and identical to those used by Python's `os.chmod`
|
||||
function.
|
||||
|
||||
@param path: path of the file to change the permissions of
|
||||
@type path: str
|
||||
@param mode: new permissions
|
||||
@type mode: int
|
||||
:param str path: path of the file to change the permissions of
|
||||
:param int mode: new permissions
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'chmod(%r, %r)' % (path, mode))
|
||||
|
@ -392,17 +381,14 @@ class SFTPClient (BaseSFTP):
|
|||
|
||||
def chown(self, path, uid, gid):
|
||||
"""
|
||||
Change the owner (C{uid}) and group (C{gid}) of a file. As with
|
||||
python's C{os.chown} function, you must pass both arguments, so if you
|
||||
only want to change one, use L{stat} first to retrieve the current
|
||||
Change the owner (``uid``) and group (``gid``) of a file. As with
|
||||
Python's `os.chown` function, you must pass both arguments, so if you
|
||||
only want to change one, use `stat` first to retrieve the current
|
||||
owner and group.
|
||||
|
||||
@param path: path of the file to change the owner and group of
|
||||
@type path: str
|
||||
@param uid: new owner's uid
|
||||
@type uid: int
|
||||
@param gid: new group id
|
||||
@type gid: int
|
||||
:param str path: path of the file to change the owner and group of
|
||||
:param int uid: new owner's uid
|
||||
:param int gid: new group id
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'chown(%r, %r, %r)' % (path, uid, gid))
|
||||
|
@ -412,18 +398,17 @@ class SFTPClient (BaseSFTP):
|
|||
|
||||
def utime(self, path, times):
|
||||
"""
|
||||
Set the access and modified times of the file specified by C{path}. If
|
||||
C{times} is C{None}, then the file's access and modified times are set
|
||||
to the current time. Otherwise, C{times} must be a 2-tuple of numbers,
|
||||
of the form C{(atime, mtime)}, which is used to set the access and
|
||||
modified times, respectively. This bizarre API is mimicked from python
|
||||
Set the access and modified times of the file specified by ``path``. If
|
||||
``times`` is ``None``, then the file's access and modified times are set
|
||||
to the current time. Otherwise, ``times`` must be a 2-tuple of numbers,
|
||||
of the form ``(atime, mtime)``, which is used to set the access and
|
||||
modified times, respectively. This bizarre API is mimicked from Python
|
||||
for the sake of consistency -- I apologize.
|
||||
|
||||
@param path: path of the file to modify
|
||||
@type path: str
|
||||
@param times: C{None} or a tuple of (access time, modified time) in
|
||||
standard internet epoch time (seconds since 01 January 1970 GMT)
|
||||
@type times: tuple(int)
|
||||
:param str path: path of the file to modify
|
||||
:param tuple times:
|
||||
``None`` or a tuple of (access time, modified time) in standard
|
||||
internet epoch time (seconds since 01 January 1970 GMT)
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
if times is None:
|
||||
|
@ -435,14 +420,13 @@ class SFTPClient (BaseSFTP):
|
|||
|
||||
def truncate(self, path, size):
|
||||
"""
|
||||
Change the size of the file specified by C{path}. This usually extends
|
||||
or shrinks the size of the file, just like the C{truncate()} method on
|
||||
python file objects.
|
||||
Change the size of the file specified by ``path``. This usually
|
||||
extends or shrinks the size of the file, just like the `~file.truncate`
|
||||
method on Python file objects.
|
||||
|
||||
@param path: path of the file to modify
|
||||
@type path: str
|
||||
@param size: the new size of the file
|
||||
@type size: int or long
|
||||
:param str path: path of the file to modify
|
||||
:param size: the new size of the file
|
||||
:type size: int or long
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'truncate(%r, %r)' % (path, size))
|
||||
|
@ -453,13 +437,11 @@ class SFTPClient (BaseSFTP):
|
|||
def readlink(self, path):
|
||||
"""
|
||||
Return the target of a symbolic link (shortcut). You can use
|
||||
L{symlink} to create these. The result may be either an absolute or
|
||||
`symlink` to create these. The result may be either an absolute or
|
||||
relative pathname.
|
||||
|
||||
@param path: path of the symbolic link file
|
||||
@type path: str
|
||||
@return: target path
|
||||
@rtype: str
|
||||
:param str path: path of the symbolic link file
|
||||
:return: target path, as a `str`
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'readlink(%r)' % path)
|
||||
|
@ -477,15 +459,13 @@ class SFTPClient (BaseSFTP):
|
|||
"""
|
||||
Return the normalized path (on the server) of a given path. This
|
||||
can be used to quickly resolve symbolic links or determine what the
|
||||
server is considering to be the "current folder" (by passing C{'.'}
|
||||
as C{path}).
|
||||
server is considering to be the "current folder" (by passing ``'.'``
|
||||
as ``path``).
|
||||
|
||||
@param path: path to be normalized
|
||||
@type path: str
|
||||
@return: normalized form of the given path
|
||||
@rtype: str
|
||||
:param str path: path to be normalized
|
||||
:return: normalized form of the given path (as a `str`)
|
||||
|
||||
@raise IOError: if the path can't be resolved on the server
|
||||
:raises IOError: if the path can't be resolved on the server
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
self._log(DEBUG, 'normalize(%r)' % path)
|
||||
|
@ -495,89 +475,80 @@ class SFTPClient (BaseSFTP):
|
|||
count = msg.get_int()
|
||||
if count != 1:
|
||||
raise SFTPError('Realpath returned %d results' % count)
|
||||
return _to_unicode(msg.get_string())
|
||||
return msg.get_text()
|
||||
|
||||
def chdir(self, path):
|
||||
def chdir(self, path=None):
|
||||
"""
|
||||
Change the "current directory" of this SFTP session. Since SFTP
|
||||
doesn't really have the concept of a current working directory, this
|
||||
is emulated by paramiko. Once you use this method to set a working
|
||||
directory, all operations on this SFTPClient object will be relative
|
||||
to that path. You can pass in C{None} to stop using a current working
|
||||
doesn't really have the concept of a current working directory, this is
|
||||
emulated by Paramiko. Once you use this method to set a working
|
||||
directory, all operations on this `.SFTPClient` object will be relative
|
||||
to that path. You can pass in ``None`` to stop using a current working
|
||||
directory.
|
||||
|
||||
@param path: new current working directory
|
||||
@type path: str
|
||||
:param str path: new current working directory
|
||||
|
||||
@raise IOError: if the requested path doesn't exist on the server
|
||||
:raises IOError: if the requested path doesn't exist on the server
|
||||
|
||||
@since: 1.4
|
||||
.. versionadded:: 1.4
|
||||
"""
|
||||
if path is None:
|
||||
self._cwd = None
|
||||
return
|
||||
if not stat.S_ISDIR(self.stat(path).st_mode):
|
||||
raise SFTPError(errno.ENOTDIR, "%s: %s" % (os.strerror(errno.ENOTDIR), path))
|
||||
self._cwd = self.normalize(path).encode('utf-8')
|
||||
self._cwd = b(self.normalize(path))
|
||||
|
||||
def getcwd(self):
|
||||
"""
|
||||
Return the "current working directory" for this SFTP session, as
|
||||
emulated by paramiko. If no directory has been set with L{chdir},
|
||||
this method will return C{None}.
|
||||
emulated by Paramiko. If no directory has been set with `chdir`,
|
||||
this method will return ``None``.
|
||||
|
||||
@return: the current working directory on the server, or C{None}
|
||||
@rtype: str
|
||||
|
||||
@since: 1.4
|
||||
.. versionadded:: 1.4
|
||||
"""
|
||||
return self._cwd
|
||||
return self._cwd and u(self._cwd)
|
||||
|
||||
def put(self, localpath, remotepath, callback=None, confirm=True):
|
||||
def putfo(self, fl, remotepath, file_size=0, callback=None, confirm=True):
|
||||
"""
|
||||
Copy a local file (C{localpath}) to the SFTP server as C{remotepath}.
|
||||
Any exception raised by operations will be passed through. This
|
||||
method is primarily provided as a convenience.
|
||||
Copy the contents of an open file object (``fl``) to the SFTP server as
|
||||
``remotepath``. Any exception raised by operations will be passed
|
||||
through.
|
||||
|
||||
The SFTP operations use pipelining for speed.
|
||||
|
||||
@param localpath: the local file to copy
|
||||
@type localpath: str
|
||||
@param remotepath: the destination path on the SFTP server
|
||||
@type remotepath: str
|
||||
@param callback: optional callback function that accepts the bytes
|
||||
transferred so far and the total bytes to be transferred
|
||||
:param file fl: opened file or file-like object to copy
|
||||
:param str remotepath: the destination path on the SFTP server
|
||||
:param int file_size:
|
||||
optional size parameter passed to callback. If none is specified,
|
||||
size defaults to 0
|
||||
:param callable callback:
|
||||
optional callback function (form: ``func(int, int)``) that accepts
|
||||
the bytes transferred so far and the total bytes to be transferred
|
||||
(since 1.7.4)
|
||||
@type callback: function(int, int)
|
||||
@param confirm: whether to do a stat() on the file afterwards to
|
||||
confirm the file size (since 1.7.7)
|
||||
@type confirm: bool
|
||||
:param bool confirm:
|
||||
whether to do a stat() on the file afterwards to confirm the file
|
||||
size (since 1.7.7)
|
||||
|
||||
@return: an object containing attributes about the given file
|
||||
(since 1.7.4)
|
||||
@rtype: SFTPAttributes
|
||||
:return:
|
||||
an `.SFTPAttributes` object containing attributes about the given
|
||||
file.
|
||||
|
||||
@since: 1.4
|
||||
.. versionadded:: 1.4
|
||||
.. versionchanged:: 1.7.4
|
||||
Began returning rich attribute objects.
|
||||
"""
|
||||
file_size = os.stat(localpath).st_size
|
||||
fl = file(localpath, 'rb')
|
||||
try:
|
||||
fr = self.file(remotepath, 'wb')
|
||||
with self.file(remotepath, 'wb') as fr:
|
||||
fr.set_pipelined(True)
|
||||
size = 0
|
||||
try:
|
||||
while True:
|
||||
data = fl.read(32768)
|
||||
if len(data) == 0:
|
||||
break
|
||||
fr.write(data)
|
||||
size += len(data)
|
||||
if callback is not None:
|
||||
callback(size, file_size)
|
||||
finally:
|
||||
fr.close()
|
||||
finally:
|
||||
fl.close()
|
||||
if len(data) == 0:
|
||||
break
|
||||
if confirm:
|
||||
s = self.stat(remotepath)
|
||||
if s.st_size != size:
|
||||
|
@ -586,29 +557,57 @@ class SFTPClient (BaseSFTP):
|
|||
s = SFTPAttributes()
|
||||
return s
|
||||
|
||||
def get(self, remotepath, localpath, callback=None):
|
||||
def put(self, localpath, remotepath, callback=None, confirm=True):
|
||||
"""
|
||||
Copy a remote file (C{remotepath}) from the SFTP server to the local
|
||||
host as C{localpath}. Any exception raised by operations will be
|
||||
passed through. This method is primarily provided as a convenience.
|
||||
Copy a local file (``localpath``) to the SFTP server as ``remotepath``.
|
||||
Any exception raised by operations will be passed through. This
|
||||
method is primarily provided as a convenience.
|
||||
|
||||
@param remotepath: the remote file to copy
|
||||
@type remotepath: str
|
||||
@param localpath: the destination path on the local host
|
||||
@type localpath: str
|
||||
@param callback: optional callback function that accepts the bytes
|
||||
transferred so far and the total bytes to be transferred
|
||||
(since 1.7.4)
|
||||
@type callback: function(int, int)
|
||||
The SFTP operations use pipelining for speed.
|
||||
|
||||
@since: 1.4
|
||||
:param str localpath: the local file to copy
|
||||
:param str remotepath: the destination path on the SFTP server
|
||||
:param callable callback:
|
||||
optional callback function (form: ``func(int, int)``) that accepts
|
||||
the bytes transferred so far and the total bytes to be transferred
|
||||
:param bool confirm:
|
||||
whether to do a stat() on the file afterwards to confirm the file
|
||||
size
|
||||
|
||||
:return: an `.SFTPAttributes` object containing attributes about the given file
|
||||
|
||||
.. versionadded:: 1.4
|
||||
.. versionchanged:: 1.7.4
|
||||
``callback`` and rich attribute return value added.
|
||||
.. versionchanged:: 1.7.7
|
||||
``confirm`` param added.
|
||||
"""
|
||||
fr = self.file(remotepath, 'rb')
|
||||
file_size = os.stat(localpath).st_size
|
||||
with open(localpath, 'rb') as fl:
|
||||
return self.putfo(fl, remotepath, os.stat(localpath).st_size, callback, confirm)
|
||||
|
||||
def getfo(self, remotepath, fl, callback=None):
|
||||
"""
|
||||
Copy a remote file (``remotepath``) from the SFTP server and write to
|
||||
an open file or file-like object, ``fl``. Any exception raised by
|
||||
operations will be passed through. This method is primarily provided
|
||||
as a convenience.
|
||||
|
||||
:param object remotepath: opened file or file-like object to copy to
|
||||
:param str fl:
|
||||
the destination path on the local host or open file object
|
||||
:param callable callback:
|
||||
optional callback function (form: ``func(int, int)``) that accepts
|
||||
the bytes transferred so far and the total bytes to be transferred
|
||||
:return: the `number <int>` of bytes written to the opened file object
|
||||
|
||||
.. versionadded:: 1.4
|
||||
.. versionchanged:: 1.7.4
|
||||
Added the ``callable`` param.
|
||||
"""
|
||||
with self.open(remotepath, 'rb') as fr:
|
||||
file_size = self.stat(remotepath).st_size
|
||||
fr.prefetch()
|
||||
try:
|
||||
fl = file(localpath, 'wb')
|
||||
try:
|
||||
size = 0
|
||||
while True:
|
||||
data = fr.read(32768)
|
||||
|
@ -618,18 +617,33 @@ class SFTPClient (BaseSFTP):
|
|||
callback(size, file_size)
|
||||
if len(data) == 0:
|
||||
break
|
||||
finally:
|
||||
fl.close()
|
||||
finally:
|
||||
fr.close()
|
||||
return size
|
||||
|
||||
def get(self, remotepath, localpath, callback=None):
|
||||
"""
|
||||
Copy a remote file (``remotepath``) from the SFTP server to the local
|
||||
host as ``localpath``. Any exception raised by operations will be
|
||||
passed through. This method is primarily provided as a convenience.
|
||||
|
||||
:param str remotepath: the remote file to copy
|
||||
:param str localpath: the destination path on the local host
|
||||
:param callable callback:
|
||||
optional callback function (form: ``func(int, int)``) that accepts
|
||||
the bytes transferred so far and the total bytes to be transferred
|
||||
|
||||
.. versionadded:: 1.4
|
||||
.. versionchanged:: 1.7.4
|
||||
Added the ``callback`` param
|
||||
"""
|
||||
file_size = self.stat(remotepath).st_size
|
||||
with open(localpath, 'wb') as fl:
|
||||
size = self.getfo(remotepath, fl, callback)
|
||||
s = os.stat(localpath)
|
||||
if s.st_size != size:
|
||||
raise IOError('size mismatch in get! %d != %d' % (s.st_size, size))
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _request(self, t, *arg):
|
||||
num = self._async_request(type(None), t, *arg)
|
||||
return self._read_response(num)
|
||||
|
@ -641,11 +655,11 @@ class SFTPClient (BaseSFTP):
|
|||
msg = Message()
|
||||
msg.add_int(self.request_number)
|
||||
for item in arg:
|
||||
if isinstance(item, int):
|
||||
msg.add_int(item)
|
||||
elif isinstance(item, long):
|
||||
if isinstance(item, long):
|
||||
msg.add_int64(item)
|
||||
elif isinstance(item, str):
|
||||
elif isinstance(item, int):
|
||||
msg.add_int(item)
|
||||
elif isinstance(item, (string_types, bytes_types)):
|
||||
msg.add_string(item)
|
||||
elif isinstance(item, SFTPAttributes):
|
||||
item._pack(msg)
|
||||
|
@ -653,7 +667,7 @@ class SFTPClient (BaseSFTP):
|
|||
raise Exception('unknown type for %r type %r' % (item, type(item)))
|
||||
num = self.request_number
|
||||
self._expecting[num] = fileobj
|
||||
self._send_packet(t, str(msg))
|
||||
self._send_packet(t, msg)
|
||||
self.request_number += 1
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
@ -663,8 +677,8 @@ class SFTPClient (BaseSFTP):
|
|||
while True:
|
||||
try:
|
||||
t, data = self._read_packet()
|
||||
except EOFError, e:
|
||||
raise SSHException('Server connection dropped: %s' % (str(e),))
|
||||
except EOFError as e:
|
||||
raise SSHException('Server connection dropped: %s' % str(e))
|
||||
msg = Message(data)
|
||||
num = msg.get_int()
|
||||
if num not in self._expecting:
|
||||
|
@ -682,11 +696,11 @@ class SFTPClient (BaseSFTP):
|
|||
self._convert_status(msg)
|
||||
return t, msg
|
||||
if fileobj is not type(None):
|
||||
fileobj._async_response(t, msg)
|
||||
fileobj._async_response(t, msg, num)
|
||||
if waitfor is None:
|
||||
# just doing a single check
|
||||
break
|
||||
return (None, None)
|
||||
return None, None
|
||||
|
||||
def _finish_responses(self, fileobj):
|
||||
while fileobj in self._expecting.values():
|
||||
|
@ -698,7 +712,7 @@ class SFTPClient (BaseSFTP):
|
|||
Raises EOFError or IOError on error status; otherwise does nothing.
|
||||
"""
|
||||
code = msg.get_int()
|
||||
text = msg.get_string()
|
||||
text = msg.get_text()
|
||||
if code == SFTP_OK:
|
||||
return
|
||||
elif code == SFTP_EOF:
|
||||
|
@ -716,18 +730,19 @@ class SFTPClient (BaseSFTP):
|
|||
Return an adjusted path if we're emulating a "current working
|
||||
directory" for the server.
|
||||
"""
|
||||
if type(path) is unicode:
|
||||
path = path.encode('utf-8')
|
||||
path = b(path)
|
||||
if self._cwd is None:
|
||||
return path
|
||||
if (len(path) > 0) and (path[0] == '/'):
|
||||
if len(path) and path[0:1] == b_slash:
|
||||
# absolute path
|
||||
return path
|
||||
if self._cwd == '/':
|
||||
if self._cwd == b_slash:
|
||||
return self._cwd + path
|
||||
return self._cwd + '/' + path
|
||||
return self._cwd + b_slash + path
|
||||
|
||||
|
||||
class SFTP(SFTPClient):
|
||||
"an alias for L{SFTPClient} for backwards compatability"
|
||||
"""
|
||||
An alias for `.SFTPClient` for backwards compatability.
|
||||
"""
|
||||
pass
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -17,23 +17,31 @@
|
|||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{SFTPFile}
|
||||
SFTP file object
|
||||
"""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
from binascii import hexlify
|
||||
from collections import deque
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
from paramiko.common import DEBUG
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko.sftp import *
|
||||
from paramiko.file import BufferedFile
|
||||
from paramiko.py3compat import long
|
||||
from paramiko.sftp import CMD_CLOSE, CMD_READ, CMD_DATA, SFTPError, CMD_WRITE, \
|
||||
CMD_STATUS, CMD_FSTAT, CMD_ATTRS, CMD_FSETSTAT, CMD_EXTENDED
|
||||
from paramiko.sftp_attr import SFTPAttributes
|
||||
|
||||
|
||||
class SFTPFile (BufferedFile):
|
||||
"""
|
||||
Proxy object for a file on the remote server, in client mode SFTP.
|
||||
|
||||
Instances of this class may be used as context managers in the same way
|
||||
that built-in Python file objects are.
|
||||
"""
|
||||
|
||||
# Some sftp servers will choke if you send read/write requests larger than
|
||||
|
@ -49,13 +57,18 @@ class SFTPFile (BufferedFile):
|
|||
self._prefetching = False
|
||||
self._prefetch_done = False
|
||||
self._prefetch_data = {}
|
||||
self._prefetch_reads = []
|
||||
self._prefetch_extents = {}
|
||||
self._prefetch_lock = threading.Lock()
|
||||
self._saved_exception = None
|
||||
self._reqs = deque()
|
||||
|
||||
def __del__(self):
|
||||
self._close(async=True)
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the file.
|
||||
"""
|
||||
self._close(async=False)
|
||||
|
||||
def _close(self, async=False):
|
||||
|
@ -86,10 +99,10 @@ class SFTPFile (BufferedFile):
|
|||
pass
|
||||
|
||||
def _data_in_prefetch_requests(self, offset, size):
|
||||
k = [i for i in self._prefetch_reads if i[0] <= offset]
|
||||
k = [x for x in list(self._prefetch_extents.values()) if x[0] <= offset]
|
||||
if len(k) == 0:
|
||||
return False
|
||||
k.sort(lambda x, y: cmp(x[0], y[0]))
|
||||
k.sort(key=lambda x: x[0])
|
||||
buf_offset, buf_size = k[-1]
|
||||
if buf_offset + buf_size <= offset:
|
||||
# prefetch request ends before this one begins
|
||||
|
@ -160,8 +173,10 @@ class SFTPFile (BufferedFile):
|
|||
def _write(self, data):
|
||||
# may write less than requested if it would exceed max packet size
|
||||
chunk = min(len(data), self.MAX_REQUEST_SIZE)
|
||||
req = self.sftp._async_request(type(None), CMD_WRITE, self.handle, long(self._realpos), str(data[:chunk]))
|
||||
if not self.pipelined or self.sftp.sock.recv_ready():
|
||||
self._reqs.append(self.sftp._async_request(type(None), CMD_WRITE, self.handle, long(self._realpos), data[:chunk]))
|
||||
if not self.pipelined or (len(self._reqs) > 100 and self.sftp.sock.recv_ready()):
|
||||
while len(self._reqs):
|
||||
req = self._reqs.popleft()
|
||||
t, msg = self.sftp._read_response(req)
|
||||
if t != CMD_STATUS:
|
||||
raise SFTPError('Expected status')
|
||||
|
@ -171,34 +186,34 @@ class SFTPFile (BufferedFile):
|
|||
def settimeout(self, timeout):
|
||||
"""
|
||||
Set a timeout on read/write operations on the underlying socket or
|
||||
ssh L{Channel}.
|
||||
ssh `.Channel`.
|
||||
|
||||
@see: L{Channel.settimeout}
|
||||
@param timeout: seconds to wait for a pending read/write operation
|
||||
before raising C{socket.timeout}, or C{None} for no timeout
|
||||
@type timeout: float
|
||||
:param float timeout:
|
||||
seconds to wait for a pending read/write operation before raising
|
||||
``socket.timeout``, or ``None`` for no timeout
|
||||
|
||||
.. seealso:: `.Channel.settimeout`
|
||||
"""
|
||||
self.sftp.sock.settimeout(timeout)
|
||||
|
||||
def gettimeout(self):
|
||||
"""
|
||||
Returns the timeout in seconds (as a float) associated with the socket
|
||||
or ssh L{Channel} used for this file.
|
||||
Returns the timeout in seconds (as a `float`) associated with the
|
||||
socket or ssh `.Channel` used for this file.
|
||||
|
||||
@see: L{Channel.gettimeout}
|
||||
@rtype: float
|
||||
.. seealso:: `.Channel.gettimeout`
|
||||
"""
|
||||
return self.sftp.sock.gettimeout()
|
||||
|
||||
def setblocking(self, blocking):
|
||||
"""
|
||||
Set blocking or non-blocking mode on the underiying socket or ssh
|
||||
L{Channel}.
|
||||
`.Channel`.
|
||||
|
||||
@see: L{Channel.setblocking}
|
||||
@param blocking: 0 to set non-blocking mode; non-0 to set blocking
|
||||
mode.
|
||||
@type blocking: int
|
||||
:param int blocking:
|
||||
0 to set non-blocking mode; non-0 to set blocking mode.
|
||||
|
||||
.. seealso:: `.Channel.setblocking`
|
||||
"""
|
||||
self.sftp.sock.setblocking(blocking)
|
||||
|
||||
|
@ -211,16 +226,15 @@ class SFTPFile (BufferedFile):
|
|||
self._realpos = self._pos
|
||||
else:
|
||||
self._realpos = self._pos = self._get_size() + offset
|
||||
self._rbuffer = ''
|
||||
self._rbuffer = bytes()
|
||||
|
||||
def stat(self):
|
||||
"""
|
||||
Retrieve information about this file from the remote system. This is
|
||||
exactly like L{SFTP.stat}, except that it operates on an already-open
|
||||
file.
|
||||
exactly like `.SFTPClient.stat`, except that it operates on an
|
||||
already-open file.
|
||||
|
||||
@return: an object containing attributes about this file.
|
||||
@rtype: SFTPAttributes
|
||||
:return: an `.SFTPAttributes` object containing attributes about this file.
|
||||
"""
|
||||
t, msg = self.sftp._request(CMD_FSTAT, self.handle)
|
||||
if t != CMD_ATTRS:
|
||||
|
@ -230,11 +244,10 @@ class SFTPFile (BufferedFile):
|
|||
def chmod(self, mode):
|
||||
"""
|
||||
Change the mode (permissions) of this file. The permissions are
|
||||
unix-style and identical to those used by python's C{os.chmod}
|
||||
unix-style and identical to those used by Python's `os.chmod`
|
||||
function.
|
||||
|
||||
@param mode: new permissions
|
||||
@type mode: int
|
||||
:param int mode: new permissions
|
||||
"""
|
||||
self.sftp._log(DEBUG, 'chmod(%s, %r)' % (hexlify(self.handle), mode))
|
||||
attr = SFTPAttributes()
|
||||
|
@ -243,15 +256,13 @@ class SFTPFile (BufferedFile):
|
|||
|
||||
def chown(self, uid, gid):
|
||||
"""
|
||||
Change the owner (C{uid}) and group (C{gid}) of this file. As with
|
||||
python's C{os.chown} function, you must pass both arguments, so if you
|
||||
only want to change one, use L{stat} first to retrieve the current
|
||||
Change the owner (``uid``) and group (``gid``) of this file. As with
|
||||
Python's `os.chown` function, you must pass both arguments, so if you
|
||||
only want to change one, use `stat` first to retrieve the current
|
||||
owner and group.
|
||||
|
||||
@param uid: new owner's uid
|
||||
@type uid: int
|
||||
@param gid: new group id
|
||||
@type gid: int
|
||||
:param int uid: new owner's uid
|
||||
:param int gid: new group id
|
||||
"""
|
||||
self.sftp._log(DEBUG, 'chown(%s, %r, %r)' % (hexlify(self.handle), uid, gid))
|
||||
attr = SFTPAttributes()
|
||||
|
@ -261,15 +272,15 @@ class SFTPFile (BufferedFile):
|
|||
def utime(self, times):
|
||||
"""
|
||||
Set the access and modified times of this file. If
|
||||
C{times} is C{None}, then the file's access and modified times are set
|
||||
to the current time. Otherwise, C{times} must be a 2-tuple of numbers,
|
||||
of the form C{(atime, mtime)}, which is used to set the access and
|
||||
modified times, respectively. This bizarre API is mimicked from python
|
||||
``times`` is ``None``, then the file's access and modified times are set
|
||||
to the current time. Otherwise, ``times`` must be a 2-tuple of numbers,
|
||||
of the form ``(atime, mtime)``, which is used to set the access and
|
||||
modified times, respectively. This bizarre API is mimicked from Python
|
||||
for the sake of consistency -- I apologize.
|
||||
|
||||
@param times: C{None} or a tuple of (access time, modified time) in
|
||||
standard internet epoch time (seconds since 01 January 1970 GMT)
|
||||
@type times: tuple(int)
|
||||
:param tuple times:
|
||||
``None`` or a tuple of (access time, modified time) in standard
|
||||
internet epoch time (seconds since 01 January 1970 GMT)
|
||||
"""
|
||||
if times is None:
|
||||
times = (time.time(), time.time())
|
||||
|
@ -281,11 +292,11 @@ class SFTPFile (BufferedFile):
|
|||
def truncate(self, size):
|
||||
"""
|
||||
Change the size of this file. This usually extends
|
||||
or shrinks the size of the file, just like the C{truncate()} method on
|
||||
python file objects.
|
||||
or shrinks the size of the file, just like the ``truncate()`` method on
|
||||
Python file objects.
|
||||
|
||||
@param size: the new size of the file
|
||||
@type size: int or long
|
||||
:param size: the new size of the file
|
||||
:type size: int or long
|
||||
"""
|
||||
self.sftp._log(DEBUG, 'truncate(%s, %r)' % (hexlify(self.handle), size))
|
||||
attr = SFTPAttributes()
|
||||
|
@ -298,51 +309,53 @@ class SFTPFile (BufferedFile):
|
|||
to verify a successful upload or download, or for various rsync-like
|
||||
operations.
|
||||
|
||||
The file is hashed from C{offset}, for C{length} bytes. If C{length}
|
||||
is 0, the remainder of the file is hashed. Thus, if both C{offset}
|
||||
and C{length} are zero, the entire file is hashed.
|
||||
The file is hashed from ``offset``, for ``length`` bytes. If ``length``
|
||||
is 0, the remainder of the file is hashed. Thus, if both ``offset``
|
||||
and ``length`` are zero, the entire file is hashed.
|
||||
|
||||
Normally, C{block_size} will be 0 (the default), and this method will
|
||||
Normally, ``block_size`` will be 0 (the default), and this method will
|
||||
return a byte string representing the requested hash (for example, a
|
||||
string of length 16 for MD5, or 20 for SHA-1). If a non-zero
|
||||
C{block_size} is given, each chunk of the file (from C{offset} to
|
||||
C{offset + length}) of C{block_size} bytes is computed as a separate
|
||||
``block_size`` is given, each chunk of the file (from ``offset`` to
|
||||
``offset + length``) of ``block_size`` bytes is computed as a separate
|
||||
hash. The hash results are all concatenated and returned as a single
|
||||
string.
|
||||
|
||||
For example, C{check('sha1', 0, 1024, 512)} will return a string of
|
||||
For example, ``check('sha1', 0, 1024, 512)`` will return a string of
|
||||
length 40. The first 20 bytes will be the SHA-1 of the first 512 bytes
|
||||
of the file, and the last 20 bytes will be the SHA-1 of the next 512
|
||||
bytes.
|
||||
|
||||
@param hash_algorithm: the name of the hash algorithm to use (normally
|
||||
C{"sha1"} or C{"md5"})
|
||||
@type hash_algorithm: str
|
||||
@param offset: offset into the file to begin hashing (0 means to start
|
||||
from the beginning)
|
||||
@type offset: int or long
|
||||
@param length: number of bytes to hash (0 means continue to the end of
|
||||
the file)
|
||||
@type length: int or long
|
||||
@param block_size: number of bytes to hash per result (must not be less
|
||||
than 256; 0 means to compute only one hash of the entire segment)
|
||||
@type block_size: int
|
||||
@return: string of bytes representing the hash of each block,
|
||||
concatenated together
|
||||
@rtype: str
|
||||
:param str hash_algorithm:
|
||||
the name of the hash algorithm to use (normally ``"sha1"`` or
|
||||
``"md5"``)
|
||||
:param offset:
|
||||
offset into the file to begin hashing (0 means to start from the
|
||||
beginning)
|
||||
:type offset: int or long
|
||||
:param length:
|
||||
number of bytes to hash (0 means continue to the end of the file)
|
||||
:type length: int or long
|
||||
:param int block_size:
|
||||
number of bytes to hash per result (must not be less than 256; 0
|
||||
means to compute only one hash of the entire segment)
|
||||
:type block_size: int
|
||||
:return:
|
||||
`str` of bytes representing the hash of each block, concatenated
|
||||
together
|
||||
|
||||
@note: Many (most?) servers don't support this extension yet.
|
||||
|
||||
@raise IOError: if the server doesn't support the "check-file"
|
||||
:raises IOError: if the server doesn't support the "check-file"
|
||||
extension, or possibly doesn't support the hash algorithm
|
||||
requested
|
||||
|
||||
@since: 1.4
|
||||
.. note:: Many (most?) servers don't support this extension yet.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
"""
|
||||
t, msg = self.sftp._request(CMD_EXTENDED, 'check-file', self.handle,
|
||||
hash_algorithm, long(offset), long(length), block_size)
|
||||
ext = msg.get_string()
|
||||
alg = msg.get_string()
|
||||
ext = msg.get_text()
|
||||
alg = msg.get_text()
|
||||
data = msg.get_remainder()
|
||||
return data
|
||||
|
||||
|
@ -350,35 +363,35 @@ class SFTPFile (BufferedFile):
|
|||
"""
|
||||
Turn on/off the pipelining of write operations to this file. When
|
||||
pipelining is on, paramiko won't wait for the server response after
|
||||
each write operation. Instead, they're collected as they come in.
|
||||
At the first non-write operation (including L{close}), all remaining
|
||||
each write operation. Instead, they're collected as they come in. At
|
||||
the first non-write operation (including `.close`), all remaining
|
||||
server responses are collected. This means that if there was an error
|
||||
with one of your later writes, an exception might be thrown from
|
||||
within L{close} instead of L{write}.
|
||||
with one of your later writes, an exception might be thrown from within
|
||||
`.close` instead of `.write`.
|
||||
|
||||
By default, files are I{not} pipelined.
|
||||
By default, files are not pipelined.
|
||||
|
||||
@param pipelined: C{True} if pipelining should be turned on for this
|
||||
file; C{False} otherwise
|
||||
@type pipelined: bool
|
||||
:param bool pipelined:
|
||||
``True`` if pipelining should be turned on for this file; ``False``
|
||||
otherwise
|
||||
|
||||
@since: 1.5
|
||||
.. versionadded:: 1.5
|
||||
"""
|
||||
self.pipelined = pipelined
|
||||
|
||||
def prefetch(self):
|
||||
"""
|
||||
Pre-fetch the remaining contents of this file in anticipation of
|
||||
future L{read} calls. If reading the entire file, pre-fetching can
|
||||
Pre-fetch the remaining contents of this file in anticipation of future
|
||||
`.read` calls. If reading the entire file, pre-fetching can
|
||||
dramatically improve the download speed by avoiding roundtrip latency.
|
||||
The file's contents are incrementally buffered in a background thread.
|
||||
|
||||
The prefetched data is stored in a buffer until read via the L{read}
|
||||
The prefetched data is stored in a buffer until read via the `.read`
|
||||
method. Once data has been read, it's removed from the buffer. The
|
||||
data may be read in a random order (using L{seek}); chunks of the
|
||||
data may be read in a random order (using `.seek`); chunks of the
|
||||
buffer that haven't been read will continue to be buffered.
|
||||
|
||||
@since: 1.5.1
|
||||
.. versionadded:: 1.5.1
|
||||
"""
|
||||
size = self.stat().st_size
|
||||
# queue up async reads for the rest of the file
|
||||
|
@ -394,17 +407,17 @@ class SFTPFile (BufferedFile):
|
|||
def readv(self, chunks):
|
||||
"""
|
||||
Read a set of blocks from the file by (offset, length). This is more
|
||||
efficient than doing a series of L{seek} and L{read} calls, since the
|
||||
efficient than doing a series of `.seek` and `.read` calls, since the
|
||||
prefetch machinery is used to retrieve all the requested blocks at
|
||||
once.
|
||||
|
||||
@param chunks: a list of (offset, length) tuples indicating which
|
||||
sections of the file to read
|
||||
@type chunks: list(tuple(long, int))
|
||||
@return: a list of blocks read, in the same order as in C{chunks}
|
||||
@rtype: list(str)
|
||||
:param chunks:
|
||||
a list of (offset, length) tuples indicating which sections of the
|
||||
file to read
|
||||
:type chunks: list(tuple(long, int))
|
||||
:return: a list of blocks read, in the same order as in ``chunks``
|
||||
|
||||
@since: 1.5.4
|
||||
.. versionadded:: 1.5.4
|
||||
"""
|
||||
self.sftp._log(DEBUG, 'readv(%s, %r)' % (hexlify(self.handle), chunks))
|
||||
|
||||
|
@ -427,10 +440,8 @@ class SFTPFile (BufferedFile):
|
|||
self.seek(x[0])
|
||||
yield self.read(x[1])
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _get_size(self):
|
||||
try:
|
||||
return self.stat().st_size
|
||||
|
@ -440,7 +451,6 @@ class SFTPFile (BufferedFile):
|
|||
def _start_prefetch(self, chunks):
|
||||
self._prefetching = True
|
||||
self._prefetch_done = False
|
||||
self._prefetch_reads.extend(chunks)
|
||||
|
||||
t = threading.Thread(target=self._prefetch_thread, args=(chunks,))
|
||||
t.setDaemon(True)
|
||||
|
@ -450,27 +460,37 @@ class SFTPFile (BufferedFile):
|
|||
# do these read requests in a temporary thread because there may be
|
||||
# a lot of them, so it may block.
|
||||
for offset, length in chunks:
|
||||
self.sftp._async_request(self, CMD_READ, self.handle, long(offset), int(length))
|
||||
with self._prefetch_lock:
|
||||
num = self.sftp._async_request(self, CMD_READ, self.handle, long(offset), int(length))
|
||||
self._prefetch_extents[num] = (offset, length)
|
||||
|
||||
def _async_response(self, t, msg):
|
||||
def _async_response(self, t, msg, num):
|
||||
if t == CMD_STATUS:
|
||||
# save exception and re-raise it on next file operation
|
||||
try:
|
||||
self.sftp._convert_status(msg)
|
||||
except Exception, x:
|
||||
self._saved_exception = x
|
||||
except Exception as e:
|
||||
self._saved_exception = e
|
||||
return
|
||||
if t != CMD_DATA:
|
||||
raise SFTPError('Expected data')
|
||||
data = msg.get_string()
|
||||
offset, length = self._prefetch_reads.pop(0)
|
||||
with self._prefetch_lock:
|
||||
offset, length = self._prefetch_extents[num]
|
||||
self._prefetch_data[offset] = data
|
||||
if len(self._prefetch_reads) == 0:
|
||||
del self._prefetch_extents[num]
|
||||
if len(self._prefetch_extents) == 0:
|
||||
self._prefetch_done = True
|
||||
|
||||
def _check_exception(self):
|
||||
"if there's a saved exception, raise & clear it"
|
||||
"""if there's a saved exception, raise & clear it"""
|
||||
if self._saved_exception is not None:
|
||||
x = self._saved_exception
|
||||
self._saved_exception = None
|
||||
raise x
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.close()
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -21,9 +21,7 @@ Abstraction of an SFTP file handle (for server mode).
|
|||
"""
|
||||
|
||||
import os
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko.sftp import *
|
||||
from paramiko.sftp import SFTP_OP_UNSUPPORTED, SFTP_OK
|
||||
|
||||
|
||||
class SFTPHandle (object):
|
||||
|
@ -33,16 +31,15 @@ class SFTPHandle (object):
|
|||
by the client to refer to the underlying file.
|
||||
|
||||
Server implementations can (and should) subclass SFTPHandle to implement
|
||||
features of a file handle, like L{stat} or L{chattr}.
|
||||
features of a file handle, like `stat` or `chattr`.
|
||||
"""
|
||||
def __init__(self, flags=0):
|
||||
"""
|
||||
Create a new file handle representing a local file being served over
|
||||
SFTP. If C{flags} is passed in, it's used to determine if the file
|
||||
SFTP. If ``flags`` is passed in, it's used to determine if the file
|
||||
is open in append mode.
|
||||
|
||||
@param flags: optional flags as passed to L{SFTPServerInterface.open}
|
||||
@type flags: int
|
||||
:param int flags: optional flags as passed to `.SFTPServerInterface.open`
|
||||
"""
|
||||
self.__flags = flags
|
||||
self.__name = None
|
||||
|
@ -56,10 +53,10 @@ class SFTPHandle (object):
|
|||
Normally you would use this method to close the underlying OS level
|
||||
file object(s).
|
||||
|
||||
The default implementation checks for attributes on C{self} named
|
||||
C{readfile} and/or C{writefile}, and if either or both are present,
|
||||
their C{close()} methods are called. This means that if you are
|
||||
using the default implementations of L{read} and L{write}, this
|
||||
The default implementation checks for attributes on ``self`` named
|
||||
``readfile`` and/or ``writefile``, and if either or both are present,
|
||||
their ``close()`` methods are called. This means that if you are
|
||||
using the default implementations of `read` and `write`, this
|
||||
method's default implementation should be fine also.
|
||||
"""
|
||||
readfile = getattr(self, 'readfile', None)
|
||||
|
@ -71,24 +68,22 @@ class SFTPHandle (object):
|
|||
|
||||
def read(self, offset, length):
|
||||
"""
|
||||
Read up to C{length} bytes from this file, starting at position
|
||||
C{offset}. The offset may be a python long, since SFTP allows it
|
||||
Read up to ``length`` bytes from this file, starting at position
|
||||
``offset``. The offset may be a Python long, since SFTP allows it
|
||||
to be 64 bits.
|
||||
|
||||
If the end of the file has been reached, this method may return an
|
||||
empty string to signify EOF, or it may also return L{SFTP_EOF}.
|
||||
empty string to signify EOF, or it may also return `.SFTP_EOF`.
|
||||
|
||||
The default implementation checks for an attribute on C{self} named
|
||||
C{readfile}, and if present, performs the read operation on the python
|
||||
The default implementation checks for an attribute on ``self`` named
|
||||
``readfile``, and if present, performs the read operation on the Python
|
||||
file-like object found there. (This is meant as a time saver for the
|
||||
common case where you are wrapping a python file object.)
|
||||
common case where you are wrapping a Python file object.)
|
||||
|
||||
@param offset: position in the file to start reading from.
|
||||
@type offset: int or long
|
||||
@param length: number of bytes to attempt to read.
|
||||
@type length: int
|
||||
@return: data read from the file, or an SFTP error code.
|
||||
@rtype: str
|
||||
:param offset: position in the file to start reading from.
|
||||
:type offset: int or long
|
||||
:param int length: number of bytes to attempt to read.
|
||||
:return: data read from the file, or an SFTP error code, as a `str`.
|
||||
"""
|
||||
readfile = getattr(self, 'readfile', None)
|
||||
if readfile is None:
|
||||
|
@ -100,7 +95,7 @@ class SFTPHandle (object):
|
|||
readfile.seek(offset)
|
||||
self.__tell = offset
|
||||
data = readfile.read(length)
|
||||
except IOError, e:
|
||||
except IOError as e:
|
||||
self.__tell = None
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
self.__tell += len(data)
|
||||
|
@ -108,23 +103,22 @@ class SFTPHandle (object):
|
|||
|
||||
def write(self, offset, data):
|
||||
"""
|
||||
Write C{data} into this file at position C{offset}. Extending the
|
||||
file past its original end is expected. Unlike python's normal
|
||||
C{write()} methods, this method cannot do a partial write: it must
|
||||
write all of C{data} or else return an error.
|
||||
Write ``data`` into this file at position ``offset``. Extending the
|
||||
file past its original end is expected. Unlike Python's normal
|
||||
``write()`` methods, this method cannot do a partial write: it must
|
||||
write all of ``data`` or else return an error.
|
||||
|
||||
The default implementation checks for an attribute on C{self} named
|
||||
C{writefile}, and if present, performs the write operation on the
|
||||
python file-like object found there. The attribute is named
|
||||
differently from C{readfile} to make it easy to implement read-only
|
||||
The default implementation checks for an attribute on ``self`` named
|
||||
``writefile``, and if present, performs the write operation on the
|
||||
Python file-like object found there. The attribute is named
|
||||
differently from ``readfile`` to make it easy to implement read-only
|
||||
(or write-only) files, but if both attributes are present, they should
|
||||
refer to the same file.
|
||||
|
||||
@param offset: position in the file to start reading from.
|
||||
@type offset: int or long
|
||||
@param data: data to write into the file.
|
||||
@type data: str
|
||||
@return: an SFTP error code like L{SFTP_OK}.
|
||||
:param offset: position in the file to start reading from.
|
||||
:type offset: int or long
|
||||
:param str data: data to write into the file.
|
||||
:return: an SFTP error code like `.SFTP_OK`.
|
||||
"""
|
||||
writefile = getattr(self, 'writefile', None)
|
||||
if writefile is None:
|
||||
|
@ -139,7 +133,7 @@ class SFTPHandle (object):
|
|||
self.__tell = offset
|
||||
writefile.write(data)
|
||||
writefile.flush()
|
||||
except IOError, e:
|
||||
except IOError as e:
|
||||
self.__tell = None
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
if self.__tell is not None:
|
||||
|
@ -148,33 +142,30 @@ class SFTPHandle (object):
|
|||
|
||||
def stat(self):
|
||||
"""
|
||||
Return an L{SFTPAttributes} object referring to this open file, or an
|
||||
error code. This is equivalent to L{SFTPServerInterface.stat}, except
|
||||
Return an `.SFTPAttributes` object referring to this open file, or an
|
||||
error code. This is equivalent to `.SFTPServerInterface.stat`, except
|
||||
it's called on an open file instead of a path.
|
||||
|
||||
@return: an attributes object for the given file, or an SFTP error
|
||||
code (like L{SFTP_PERMISSION_DENIED}).
|
||||
@rtype: L{SFTPAttributes} I{or error code}
|
||||
:return:
|
||||
an attributes object for the given file, or an SFTP error code
|
||||
(like `.SFTP_PERMISSION_DENIED`).
|
||||
:rtype: `.SFTPAttributes` or error code
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def chattr(self, attr):
|
||||
"""
|
||||
Change the attributes of this file. The C{attr} object will contain
|
||||
Change the attributes of this file. The ``attr`` object will contain
|
||||
only those fields provided by the client in its request, so you should
|
||||
check for the presence of fields before using them.
|
||||
|
||||
@param attr: the attributes to change on this file.
|
||||
@type attr: L{SFTPAttributes}
|
||||
@return: an error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
:param .SFTPAttributes attr: the attributes to change on this file.
|
||||
:return: an `int` error code like `.SFTP_OK`.
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _set_files(self, files):
|
||||
"""
|
||||
Used by the SFTP server code to cache a directory listing. (In
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -22,46 +22,55 @@ Server-mode SFTP support.
|
|||
|
||||
import os
|
||||
import errno
|
||||
import sys
|
||||
from hashlib import md5, sha1
|
||||
|
||||
from Crypto.Hash import MD5, SHA
|
||||
from paramiko.common import *
|
||||
from paramiko import util
|
||||
from paramiko.sftp import BaseSFTP, Message, SFTP_FAILURE, \
|
||||
SFTP_PERMISSION_DENIED, SFTP_NO_SUCH_FILE
|
||||
from paramiko.sftp_si import SFTPServerInterface
|
||||
from paramiko.sftp_attr import SFTPAttributes
|
||||
from paramiko.common import DEBUG
|
||||
from paramiko.py3compat import long, string_types, bytes_types, b
|
||||
from paramiko.server import SubsystemHandler
|
||||
from paramiko.sftp import *
|
||||
from paramiko.sftp_si import *
|
||||
from paramiko.sftp_attr import *
|
||||
|
||||
|
||||
# known hash algorithms for the "check-file" extension
|
||||
from paramiko.sftp import CMD_HANDLE, SFTP_DESC, CMD_STATUS, SFTP_EOF, CMD_NAME, \
|
||||
SFTP_BAD_MESSAGE, CMD_EXTENDED_REPLY, SFTP_FLAG_READ, SFTP_FLAG_WRITE, \
|
||||
SFTP_FLAG_APPEND, SFTP_FLAG_CREATE, SFTP_FLAG_TRUNC, SFTP_FLAG_EXCL, \
|
||||
CMD_NAMES, CMD_OPEN, CMD_CLOSE, SFTP_OK, CMD_READ, CMD_DATA, CMD_WRITE, \
|
||||
CMD_REMOVE, CMD_RENAME, CMD_MKDIR, CMD_RMDIR, CMD_OPENDIR, CMD_READDIR, \
|
||||
CMD_STAT, CMD_ATTRS, CMD_LSTAT, CMD_FSTAT, CMD_SETSTAT, CMD_FSETSTAT, \
|
||||
CMD_READLINK, CMD_SYMLINK, CMD_REALPATH, CMD_EXTENDED, SFTP_OP_UNSUPPORTED
|
||||
|
||||
_hash_class = {
|
||||
'sha1': SHA,
|
||||
'md5': MD5,
|
||||
'sha1': sha1,
|
||||
'md5': md5,
|
||||
}
|
||||
|
||||
|
||||
class SFTPServer (BaseSFTP, SubsystemHandler):
|
||||
"""
|
||||
Server-side SFTP subsystem support. Since this is a L{SubsystemHandler},
|
||||
it can be (and is meant to be) set as the handler for C{"sftp"} requests.
|
||||
Use L{Transport.set_subsystem_handler} to activate this class.
|
||||
Server-side SFTP subsystem support. Since this is a `.SubsystemHandler`,
|
||||
it can be (and is meant to be) set as the handler for ``"sftp"`` requests.
|
||||
Use `.Transport.set_subsystem_handler` to activate this class.
|
||||
"""
|
||||
|
||||
def __init__(self, channel, name, server, sftp_si=SFTPServerInterface, *largs, **kwargs):
|
||||
"""
|
||||
The constructor for SFTPServer is meant to be called from within the
|
||||
L{Transport} as a subsystem handler. C{server} and any additional
|
||||
`.Transport` as a subsystem handler. ``server`` and any additional
|
||||
parameters or keyword parameters are passed from the original call to
|
||||
L{Transport.set_subsystem_handler}.
|
||||
`.Transport.set_subsystem_handler`.
|
||||
|
||||
@param channel: channel passed from the L{Transport}.
|
||||
@type channel: L{Channel}
|
||||
@param name: name of the requested subsystem.
|
||||
@type name: str
|
||||
@param server: the server object associated with this channel and
|
||||
subsystem
|
||||
@type server: L{ServerInterface}
|
||||
@param sftp_si: a subclass of L{SFTPServerInterface} to use for handling
|
||||
individual requests.
|
||||
@type sftp_si: class
|
||||
:param .Channel channel: channel passed from the `.Transport`.
|
||||
:param str name: name of the requested subsystem.
|
||||
:param .ServerInterface server:
|
||||
the server object associated with this channel and subsystem
|
||||
:param class sftp_si:
|
||||
a subclass of `.SFTPServerInterface` to use for handling individual
|
||||
requests.
|
||||
"""
|
||||
BaseSFTP.__init__(self)
|
||||
SubsystemHandler.__init__(self, channel, name, server)
|
||||
|
@ -92,7 +101,7 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
|
|||
except EOFError:
|
||||
self._log(DEBUG, 'EOF -- end of session')
|
||||
return
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
self._log(DEBUG, 'Exception on channel: ' + str(e))
|
||||
self._log(DEBUG, util.tb_strings())
|
||||
return
|
||||
|
@ -100,7 +109,7 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
|
|||
request_number = msg.get_int()
|
||||
try:
|
||||
self._process(t, request_number, msg)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
self._log(DEBUG, 'Exception in server processing: ' + str(e))
|
||||
self._log(DEBUG, util.tb_strings())
|
||||
# send some kind of failure message, at least
|
||||
|
@ -113,23 +122,21 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
|
|||
self.server.session_ended()
|
||||
super(SFTPServer, self).finish_subsystem()
|
||||
# close any file handles that were left open (so we can return them to the OS quickly)
|
||||
for f in self.file_table.itervalues():
|
||||
for f in self.file_table.values():
|
||||
f.close()
|
||||
for f in self.folder_table.itervalues():
|
||||
for f in self.folder_table.values():
|
||||
f.close()
|
||||
self.file_table = {}
|
||||
self.folder_table = {}
|
||||
|
||||
def convert_errno(e):
|
||||
"""
|
||||
Convert an errno value (as from an C{OSError} or C{IOError}) into a
|
||||
Convert an errno value (as from an ``OSError`` or ``IOError``) into a
|
||||
standard SFTP result code. This is a convenience function for trapping
|
||||
exceptions in server code and returning an appropriate result.
|
||||
|
||||
@param e: an errno code, as from C{OSError.errno}.
|
||||
@type e: int
|
||||
@return: an SFTP error code like L{SFTP_NO_SUCH_FILE}.
|
||||
@rtype: int
|
||||
:param int e: an errno code, as from ``OSError.errno``.
|
||||
:return: an `int` SFTP error code like ``SFTP_NO_SUCH_FILE``.
|
||||
"""
|
||||
if e == errno.EACCES:
|
||||
# permission denied
|
||||
|
@ -144,18 +151,16 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
|
|||
def set_file_attr(filename, attr):
|
||||
"""
|
||||
Change a file's attributes on the local filesystem. The contents of
|
||||
C{attr} are used to change the permissions, owner, group ownership,
|
||||
``attr`` are used to change the permissions, owner, group ownership,
|
||||
and/or modification & access time of the file, depending on which
|
||||
attributes are present in C{attr}.
|
||||
attributes are present in ``attr``.
|
||||
|
||||
This is meant to be a handy helper function for translating SFTP file
|
||||
requests into local file operations.
|
||||
|
||||
@param filename: name of the file to alter (should usually be an
|
||||
absolute path).
|
||||
@type filename: str
|
||||
@param attr: attributes to change.
|
||||
@type attr: L{SFTPAttributes}
|
||||
:param str filename:
|
||||
name of the file to alter (should usually be an absolute path).
|
||||
:param .SFTPAttributes attr: attributes to change.
|
||||
"""
|
||||
if sys.platform != 'win32':
|
||||
# mode operations are meaningless on win32
|
||||
|
@ -166,35 +171,34 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
|
|||
if attr._flags & attr.FLAG_AMTIME:
|
||||
os.utime(filename, (attr.st_atime, attr.st_mtime))
|
||||
if attr._flags & attr.FLAG_SIZE:
|
||||
open(filename, 'w+').truncate(attr.st_size)
|
||||
with open(filename, 'w+') as f:
|
||||
f.truncate(attr.st_size)
|
||||
set_file_attr = staticmethod(set_file_attr)
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _response(self, request_number, t, *arg):
|
||||
msg = Message()
|
||||
msg.add_int(request_number)
|
||||
for item in arg:
|
||||
if type(item) is int:
|
||||
msg.add_int(item)
|
||||
elif type(item) is long:
|
||||
if isinstance(item, long):
|
||||
msg.add_int64(item)
|
||||
elif type(item) is str:
|
||||
elif isinstance(item, int):
|
||||
msg.add_int(item)
|
||||
elif isinstance(item, (string_types, bytes_types)):
|
||||
msg.add_string(item)
|
||||
elif type(item) is SFTPAttributes:
|
||||
item._pack(msg)
|
||||
else:
|
||||
raise Exception('unknown type for ' + repr(item) + ' type ' + repr(type(item)))
|
||||
self._send_packet(t, str(msg))
|
||||
self._send_packet(t, msg)
|
||||
|
||||
def _send_handle_response(self, request_number, handle, folder=False):
|
||||
if not issubclass(type(handle), SFTPHandle):
|
||||
# must be error code
|
||||
self._send_status(request_number, handle)
|
||||
return
|
||||
handle._set_name('hx%d' % self.next_handle)
|
||||
handle._set_name(b('hx%d' % self.next_handle))
|
||||
self.next_handle += 1
|
||||
if folder:
|
||||
self.folder_table[handle._get_name()] = handle
|
||||
|
@ -232,16 +236,16 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
|
|||
msg.add_int(len(flist))
|
||||
for attr in flist:
|
||||
msg.add_string(attr.filename)
|
||||
msg.add_string(str(attr))
|
||||
msg.add_string(attr)
|
||||
attr._pack(msg)
|
||||
self._send_packet(CMD_NAME, str(msg))
|
||||
self._send_packet(CMD_NAME, msg)
|
||||
|
||||
def _check_file(self, request_number, msg):
|
||||
# this extension actually comes from v6 protocol, but since it's an
|
||||
# extension, i feel like we can reasonably support it backported.
|
||||
# it's very useful for verifying uploaded files or checking for
|
||||
# rsync-like differences between local and remote files.
|
||||
handle = msg.get_string()
|
||||
handle = msg.get_binary()
|
||||
alg_list = msg.get_list()
|
||||
start = msg.get_int64()
|
||||
length = msg.get_int64()
|
||||
|
@ -270,17 +274,17 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
|
|||
self._send_status(request_number, SFTP_FAILURE, 'Block size too small')
|
||||
return
|
||||
|
||||
sum_out = ''
|
||||
sum_out = bytes()
|
||||
offset = start
|
||||
while offset < start + length:
|
||||
blocklen = min(block_size, start + length - offset)
|
||||
# don't try to read more than about 64KB at a time
|
||||
chunklen = min(blocklen, 65536)
|
||||
count = 0
|
||||
hash_obj = alg.new()
|
||||
hash_obj = alg()
|
||||
while count < blocklen:
|
||||
data = f.read(offset, chunklen)
|
||||
if not type(data) is str:
|
||||
if not isinstance(data, bytes_types):
|
||||
self._send_status(request_number, data, 'Unable to hash file')
|
||||
return
|
||||
hash_obj.update(data)
|
||||
|
@ -293,10 +297,10 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
|
|||
msg.add_string('check-file')
|
||||
msg.add_string(algname)
|
||||
msg.add_bytes(sum_out)
|
||||
self._send_packet(CMD_EXTENDED_REPLY, str(msg))
|
||||
self._send_packet(CMD_EXTENDED_REPLY, msg)
|
||||
|
||||
def _convert_pflags(self, pflags):
|
||||
"convert SFTP-style open() flags to python's os.open() flags"
|
||||
"""convert SFTP-style open() flags to Python's os.open() flags"""
|
||||
if (pflags & SFTP_FLAG_READ) and (pflags & SFTP_FLAG_WRITE):
|
||||
flags = os.O_RDWR
|
||||
elif pflags & SFTP_FLAG_WRITE:
|
||||
|
@ -316,12 +320,12 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
|
|||
def _process(self, t, request_number, msg):
|
||||
self._log(DEBUG, 'Request: %s' % CMD_NAMES[t])
|
||||
if t == CMD_OPEN:
|
||||
path = msg.get_string()
|
||||
path = msg.get_text()
|
||||
flags = self._convert_pflags(msg.get_int())
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
self._send_handle_response(request_number, self.server.open(path, flags, attr))
|
||||
elif t == CMD_CLOSE:
|
||||
handle = msg.get_string()
|
||||
handle = msg.get_binary()
|
||||
if handle in self.folder_table:
|
||||
del self.folder_table[handle]
|
||||
self._send_status(request_number, SFTP_OK)
|
||||
|
@ -333,14 +337,14 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
|
|||
return
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
elif t == CMD_READ:
|
||||
handle = msg.get_string()
|
||||
handle = msg.get_binary()
|
||||
offset = msg.get_int64()
|
||||
length = msg.get_int()
|
||||
if handle not in self.file_table:
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
data = self.file_table[handle].read(offset, length)
|
||||
if type(data) is str:
|
||||
if isinstance(data, (bytes_types, string_types)):
|
||||
if len(data) == 0:
|
||||
self._send_status(request_number, SFTP_EOF)
|
||||
else:
|
||||
|
@ -348,54 +352,54 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
|
|||
else:
|
||||
self._send_status(request_number, data)
|
||||
elif t == CMD_WRITE:
|
||||
handle = msg.get_string()
|
||||
handle = msg.get_binary()
|
||||
offset = msg.get_int64()
|
||||
data = msg.get_string()
|
||||
data = msg.get_binary()
|
||||
if handle not in self.file_table:
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
self._send_status(request_number, self.file_table[handle].write(offset, data))
|
||||
elif t == CMD_REMOVE:
|
||||
path = msg.get_string()
|
||||
path = msg.get_text()
|
||||
self._send_status(request_number, self.server.remove(path))
|
||||
elif t == CMD_RENAME:
|
||||
oldpath = msg.get_string()
|
||||
newpath = msg.get_string()
|
||||
oldpath = msg.get_text()
|
||||
newpath = msg.get_text()
|
||||
self._send_status(request_number, self.server.rename(oldpath, newpath))
|
||||
elif t == CMD_MKDIR:
|
||||
path = msg.get_string()
|
||||
path = msg.get_text()
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
self._send_status(request_number, self.server.mkdir(path, attr))
|
||||
elif t == CMD_RMDIR:
|
||||
path = msg.get_string()
|
||||
path = msg.get_text()
|
||||
self._send_status(request_number, self.server.rmdir(path))
|
||||
elif t == CMD_OPENDIR:
|
||||
path = msg.get_string()
|
||||
path = msg.get_text()
|
||||
self._open_folder(request_number, path)
|
||||
return
|
||||
elif t == CMD_READDIR:
|
||||
handle = msg.get_string()
|
||||
handle = msg.get_binary()
|
||||
if handle not in self.folder_table:
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
folder = self.folder_table[handle]
|
||||
self._read_folder(request_number, folder)
|
||||
elif t == CMD_STAT:
|
||||
path = msg.get_string()
|
||||
path = msg.get_text()
|
||||
resp = self.server.stat(path)
|
||||
if issubclass(type(resp), SFTPAttributes):
|
||||
self._response(request_number, CMD_ATTRS, resp)
|
||||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_LSTAT:
|
||||
path = msg.get_string()
|
||||
path = msg.get_text()
|
||||
resp = self.server.lstat(path)
|
||||
if issubclass(type(resp), SFTPAttributes):
|
||||
self._response(request_number, CMD_ATTRS, resp)
|
||||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_FSTAT:
|
||||
handle = msg.get_string()
|
||||
handle = msg.get_binary()
|
||||
if handle not in self.file_table:
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
|
@ -405,34 +409,34 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
|
|||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_SETSTAT:
|
||||
path = msg.get_string()
|
||||
path = msg.get_text()
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
self._send_status(request_number, self.server.chattr(path, attr))
|
||||
elif t == CMD_FSETSTAT:
|
||||
handle = msg.get_string()
|
||||
handle = msg.get_binary()
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
if handle not in self.file_table:
|
||||
self._response(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
self._send_status(request_number, self.file_table[handle].chattr(attr))
|
||||
elif t == CMD_READLINK:
|
||||
path = msg.get_string()
|
||||
path = msg.get_text()
|
||||
resp = self.server.readlink(path)
|
||||
if type(resp) is str:
|
||||
if isinstance(resp, (bytes_types, string_types)):
|
||||
self._response(request_number, CMD_NAME, 1, resp, '', SFTPAttributes())
|
||||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_SYMLINK:
|
||||
# the sftp 2 draft is incorrect here! path always follows target_path
|
||||
target_path = msg.get_string()
|
||||
path = msg.get_string()
|
||||
target_path = msg.get_text()
|
||||
path = msg.get_text()
|
||||
self._send_status(request_number, self.server.symlink(target_path, path))
|
||||
elif t == CMD_REALPATH:
|
||||
path = msg.get_string()
|
||||
path = msg.get_text()
|
||||
rpath = self.server.canonicalize(path)
|
||||
self._response(request_number, CMD_NAME, 1, rpath, '', SFTPAttributes())
|
||||
elif t == CMD_EXTENDED:
|
||||
tag = msg.get_string()
|
||||
tag = msg.get_text()
|
||||
if tag == 'check-file':
|
||||
self._check_file(request_number, msg)
|
||||
else:
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -17,19 +17,18 @@
|
|||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{SFTPServerInterface} is an interface to override for SFTP server support.
|
||||
An interface to override for SFTP server support.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko.sftp import *
|
||||
import sys
|
||||
from paramiko.sftp import SFTP_OP_UNSUPPORTED
|
||||
|
||||
|
||||
class SFTPServerInterface (object):
|
||||
"""
|
||||
This class defines an interface for controlling the behavior of paramiko
|
||||
when using the L{SFTPServer} subsystem to provide an SFTP server.
|
||||
when using the `.SFTPServer` subsystem to provide an SFTP server.
|
||||
|
||||
Methods on this class are called from the SFTP session's thread, so you can
|
||||
block as long as necessary without affecting other sessions (even other
|
||||
|
@ -46,9 +45,8 @@ class SFTPServerInterface (object):
|
|||
Create a new SFTPServerInterface object. This method does nothing by
|
||||
default and is meant to be overridden by subclasses.
|
||||
|
||||
@param server: the server object associated with this channel and
|
||||
SFTP subsystem
|
||||
@type server: L{ServerInterface}
|
||||
:param .ServerInterface server:
|
||||
the server object associated with this channel and SFTP subsystem
|
||||
"""
|
||||
super(SFTPServerInterface, self).__init__(*largs, **kwargs)
|
||||
|
||||
|
@ -64,7 +62,7 @@ class SFTPServerInterface (object):
|
|||
"""
|
||||
The SFTP server session has just ended, either cleanly or via an
|
||||
exception. This method is meant to be overridden to perform any
|
||||
necessary cleanup before this C{SFTPServerInterface} object is
|
||||
necessary cleanup before this `.SFTPServerInterface` object is
|
||||
destroyed.
|
||||
"""
|
||||
pass
|
||||
|
@ -72,103 +70,105 @@ class SFTPServerInterface (object):
|
|||
def open(self, path, flags, attr):
|
||||
"""
|
||||
Open a file on the server and create a handle for future operations
|
||||
on that file. On success, a new object subclassed from L{SFTPHandle}
|
||||
on that file. On success, a new object subclassed from `.SFTPHandle`
|
||||
should be returned. This handle will be used for future operations
|
||||
on the file (read, write, etc). On failure, an error code such as
|
||||
L{SFTP_PERMISSION_DENIED} should be returned.
|
||||
`.SFTP_PERMISSION_DENIED` should be returned.
|
||||
|
||||
C{flags} contains the requested mode for opening (read-only,
|
||||
write-append, etc) as a bitset of flags from the C{os} module:
|
||||
- C{os.O_RDONLY}
|
||||
- C{os.O_WRONLY}
|
||||
- C{os.O_RDWR}
|
||||
- C{os.O_APPEND}
|
||||
- C{os.O_CREAT}
|
||||
- C{os.O_TRUNC}
|
||||
- C{os.O_EXCL}
|
||||
(One of C{os.O_RDONLY}, C{os.O_WRONLY}, or C{os.O_RDWR} will always
|
||||
``flags`` contains the requested mode for opening (read-only,
|
||||
write-append, etc) as a bitset of flags from the ``os`` module:
|
||||
|
||||
- ``os.O_RDONLY``
|
||||
- ``os.O_WRONLY``
|
||||
- ``os.O_RDWR``
|
||||
- ``os.O_APPEND``
|
||||
- ``os.O_CREAT``
|
||||
- ``os.O_TRUNC``
|
||||
- ``os.O_EXCL``
|
||||
|
||||
(One of ``os.O_RDONLY``, ``os.O_WRONLY``, or ``os.O_RDWR`` will always
|
||||
be set.)
|
||||
|
||||
The C{attr} object contains requested attributes of the file if it
|
||||
The ``attr`` object contains requested attributes of the file if it
|
||||
has to be created. Some or all attribute fields may be missing if
|
||||
the client didn't specify them.
|
||||
|
||||
@note: The SFTP protocol defines all files to be in "binary" mode.
|
||||
There is no equivalent to python's "text" mode.
|
||||
.. note:: The SFTP protocol defines all files to be in "binary" mode.
|
||||
There is no equivalent to Python's "text" mode.
|
||||
|
||||
@param path: the requested path (relative or absolute) of the file
|
||||
to be opened.
|
||||
@type path: str
|
||||
@param flags: flags or'd together from the C{os} module indicating the
|
||||
requested mode for opening the file.
|
||||
@type flags: int
|
||||
@param attr: requested attributes of the file if it is newly created.
|
||||
@type attr: L{SFTPAttributes}
|
||||
@return: a new L{SFTPHandle} I{or error code}.
|
||||
@rtype L{SFTPHandle}
|
||||
:param str path:
|
||||
the requested path (relative or absolute) of the file to be opened.
|
||||
:param int flags:
|
||||
flags or'd together from the ``os`` module indicating the requested
|
||||
mode for opening the file.
|
||||
:param .SFTPAttributes attr:
|
||||
requested attributes of the file if it is newly created.
|
||||
:return: a new `.SFTPHandle` or error code.
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def list_folder(self, path):
|
||||
"""
|
||||
Return a list of files within a given folder. The C{path} will use
|
||||
posix notation (C{"/"} separates folder names) and may be an absolute
|
||||
Return a list of files within a given folder. The ``path`` will use
|
||||
posix notation (``"/"`` separates folder names) and may be an absolute
|
||||
or relative path.
|
||||
|
||||
The list of files is expected to be a list of L{SFTPAttributes}
|
||||
The list of files is expected to be a list of `.SFTPAttributes`
|
||||
objects, which are similar in structure to the objects returned by
|
||||
C{os.stat}. In addition, each object should have its C{filename}
|
||||
``os.stat``. In addition, each object should have its ``filename``
|
||||
field filled in, since this is important to a directory listing and
|
||||
not normally present in C{os.stat} results. The method
|
||||
L{SFTPAttributes.from_stat} will usually do what you want.
|
||||
not normally present in ``os.stat`` results. The method
|
||||
`.SFTPAttributes.from_stat` will usually do what you want.
|
||||
|
||||
In case of an error, you should return one of the C{SFTP_*} error
|
||||
codes, such as L{SFTP_PERMISSION_DENIED}.
|
||||
In case of an error, you should return one of the ``SFTP_*`` error
|
||||
codes, such as `.SFTP_PERMISSION_DENIED`.
|
||||
|
||||
@param path: the requested path (relative or absolute) to be listed.
|
||||
@type path: str
|
||||
@return: a list of the files in the given folder, using
|
||||
L{SFTPAttributes} objects.
|
||||
@rtype: list of L{SFTPAttributes} I{or error code}
|
||||
:param str path: the requested path (relative or absolute) to be listed.
|
||||
:return:
|
||||
a list of the files in the given folder, using `.SFTPAttributes`
|
||||
objects.
|
||||
|
||||
@note: You should normalize the given C{path} first (see the
|
||||
C{os.path} module) and check appropriate permissions before returning
|
||||
the list of files. Be careful of malicious clients attempting to use
|
||||
relative paths to escape restricted folders, if you're doing a direct
|
||||
translation from the SFTP server path to your local filesystem.
|
||||
.. note::
|
||||
You should normalize the given ``path`` first (see the `os.path`
|
||||
module) and check appropriate permissions before returning the list
|
||||
of files. Be careful of malicious clients attempting to use
|
||||
relative paths to escape restricted folders, if you're doing a
|
||||
direct translation from the SFTP server path to your local
|
||||
filesystem.
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def stat(self, path):
|
||||
"""
|
||||
Return an L{SFTPAttributes} object for a path on the server, or an
|
||||
Return an `.SFTPAttributes` object for a path on the server, or an
|
||||
error code. If your server supports symbolic links (also known as
|
||||
"aliases"), you should follow them. (L{lstat} is the corresponding
|
||||
"aliases"), you should follow them. (`lstat` is the corresponding
|
||||
call that doesn't follow symlinks/aliases.)
|
||||
|
||||
@param path: the requested path (relative or absolute) to fetch
|
||||
file statistics for.
|
||||
@type path: str
|
||||
@return: an attributes object for the given file, or an SFTP error
|
||||
code (like L{SFTP_PERMISSION_DENIED}).
|
||||
@rtype: L{SFTPAttributes} I{or error code}
|
||||
:param str path:
|
||||
the requested path (relative or absolute) to fetch file statistics
|
||||
for.
|
||||
:return:
|
||||
an `.SFTPAttributes` object for the given file, or an SFTP error
|
||||
code (like `.SFTP_PERMISSION_DENIED`).
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def lstat(self, path):
|
||||
"""
|
||||
Return an L{SFTPAttributes} object for a path on the server, or an
|
||||
Return an `.SFTPAttributes` object for a path on the server, or an
|
||||
error code. If your server supports symbolic links (also known as
|
||||
"aliases"), you should I{not} follow them -- instead, you should
|
||||
return data on the symlink or alias itself. (L{stat} is the
|
||||
"aliases"), you should not follow them -- instead, you should
|
||||
return data on the symlink or alias itself. (`stat` is the
|
||||
corresponding call that follows symlinks/aliases.)
|
||||
|
||||
@param path: the requested path (relative or absolute) to fetch
|
||||
file statistics for.
|
||||
@type path: str
|
||||
@return: an attributes object for the given file, or an SFTP error
|
||||
code (like L{SFTP_PERMISSION_DENIED}).
|
||||
@rtype: L{SFTPAttributes} I{or error code}
|
||||
:param str path:
|
||||
the requested path (relative or absolute) to fetch file statistics
|
||||
for.
|
||||
:type path: str
|
||||
:return:
|
||||
an `.SFTPAttributes` object for the given file, or an SFTP error
|
||||
code (like `.SFTP_PERMISSION_DENIED`).
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
|
@ -176,11 +176,9 @@ class SFTPServerInterface (object):
|
|||
"""
|
||||
Delete a file, if possible.
|
||||
|
||||
@param path: the requested path (relative or absolute) of the file
|
||||
to delete.
|
||||
@type path: str
|
||||
@return: an SFTP error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
:param str path:
|
||||
the requested path (relative or absolute) of the file to delete.
|
||||
:return: an SFTP error code `int` like `.SFTP_OK`.
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
|
@ -192,83 +190,74 @@ class SFTPServerInterface (object):
|
|||
probably a good idea to implement "move" in this method too, even for
|
||||
files that cross disk partition boundaries, if at all possible.
|
||||
|
||||
@note: You should return an error if a file with the same name as
|
||||
C{newpath} already exists. (The rename operation should be
|
||||
.. note:: You should return an error if a file with the same name as
|
||||
``newpath`` already exists. (The rename operation should be
|
||||
non-desctructive.)
|
||||
|
||||
@param oldpath: the requested path (relative or absolute) of the
|
||||
existing file.
|
||||
@type oldpath: str
|
||||
@param newpath: the requested new path of the file.
|
||||
@type newpath: str
|
||||
@return: an SFTP error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
:param str oldpath:
|
||||
the requested path (relative or absolute) of the existing file.
|
||||
:param str newpath: the requested new path of the file.
|
||||
:return: an SFTP error code `int` like `.SFTP_OK`.
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def mkdir(self, path, attr):
|
||||
"""
|
||||
Create a new directory with the given attributes. The C{attr}
|
||||
Create a new directory with the given attributes. The ``attr``
|
||||
object may be considered a "hint" and ignored.
|
||||
|
||||
The C{attr} object will contain only those fields provided by the
|
||||
client in its request, so you should use C{hasattr} to check for
|
||||
the presense of fields before using them. In some cases, the C{attr}
|
||||
The ``attr`` object will contain only those fields provided by the
|
||||
client in its request, so you should use ``hasattr`` to check for
|
||||
the presense of fields before using them. In some cases, the ``attr``
|
||||
object may be completely empty.
|
||||
|
||||
@param path: requested path (relative or absolute) of the new
|
||||
folder.
|
||||
@type path: str
|
||||
@param attr: requested attributes of the new folder.
|
||||
@type attr: L{SFTPAttributes}
|
||||
@return: an SFTP error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
:param str path:
|
||||
requested path (relative or absolute) of the new folder.
|
||||
:param .SFTPAttributes attr: requested attributes of the new folder.
|
||||
:return: an SFTP error code `int` like `.SFTP_OK`.
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def rmdir(self, path):
|
||||
"""
|
||||
Remove a directory if it exists. The C{path} should refer to an
|
||||
Remove a directory if it exists. The ``path`` should refer to an
|
||||
existing, empty folder -- otherwise this method should return an
|
||||
error.
|
||||
|
||||
@param path: requested path (relative or absolute) of the folder
|
||||
to remove.
|
||||
@type path: str
|
||||
@return: an SFTP error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
:param str path:
|
||||
requested path (relative or absolute) of the folder to remove.
|
||||
:return: an SFTP error code `int` like `.SFTP_OK`.
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def chattr(self, path, attr):
|
||||
"""
|
||||
Change the attributes of a file. The C{attr} object will contain
|
||||
Change the attributes of a file. The ``attr`` object will contain
|
||||
only those fields provided by the client in its request, so you
|
||||
should check for the presence of fields before using them.
|
||||
|
||||
@param path: requested path (relative or absolute) of the file to
|
||||
change.
|
||||
@type path: str
|
||||
@param attr: requested attributes to change on the file.
|
||||
@type attr: L{SFTPAttributes}
|
||||
@return: an error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
:param str path:
|
||||
requested path (relative or absolute) of the file to change.
|
||||
:param attr:
|
||||
requested attributes to change on the file (an `.SFTPAttributes`
|
||||
object)
|
||||
:return: an error code `int` like `.SFTP_OK`.
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def canonicalize(self, path):
|
||||
"""
|
||||
Return the canonical form of a path on the server. For example,
|
||||
if the server's home folder is C{/home/foo}, the path
|
||||
C{"../betty"} would be canonicalized to C{"/home/betty"}. Note
|
||||
if the server's home folder is ``/home/foo``, the path
|
||||
``"../betty"`` would be canonicalized to ``"/home/betty"``. Note
|
||||
the obvious security issues: if you're serving files only from a
|
||||
specific folder, you probably don't want this method to reveal path
|
||||
names outside that folder.
|
||||
|
||||
You may find the python methods in C{os.path} useful, especially
|
||||
C{os.path.normpath} and C{os.path.realpath}.
|
||||
You may find the Python methods in ``os.path`` useful, especially
|
||||
``os.path.normpath`` and ``os.path.realpath``.
|
||||
|
||||
The default implementation returns C{os.path.normpath('/' + path)}.
|
||||
The default implementation returns ``os.path.normpath('/' + path)``.
|
||||
"""
|
||||
if os.path.isabs(path):
|
||||
out = os.path.normpath(path)
|
||||
|
@ -285,26 +274,23 @@ class SFTPServerInterface (object):
|
|||
If the specified path doesn't refer to a symbolic link, an error
|
||||
should be returned.
|
||||
|
||||
@param path: path (relative or absolute) of the symbolic link.
|
||||
@type path: str
|
||||
@return: the target path of the symbolic link, or an error code like
|
||||
L{SFTP_NO_SUCH_FILE}.
|
||||
@rtype: str I{or error code}
|
||||
:param str path: path (relative or absolute) of the symbolic link.
|
||||
:return:
|
||||
the target `str` path of the symbolic link, or an error code like
|
||||
`.SFTP_NO_SUCH_FILE`.
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def symlink(self, target_path, path):
|
||||
"""
|
||||
Create a symbolic link on the server, as new pathname C{path},
|
||||
with C{target_path} as the target of the link.
|
||||
Create a symbolic link on the server, as new pathname ``path``,
|
||||
with ``target_path`` as the target of the link.
|
||||
|
||||
@param target_path: path (relative or absolute) of the target for
|
||||
this new symbolic link.
|
||||
@type target_path: str
|
||||
@param path: path (relative or absolute) of the symbolic link to
|
||||
create.
|
||||
@type path: str
|
||||
@return: an error code like C{SFTP_OK}.
|
||||
@rtype: int
|
||||
:param str target_path:
|
||||
path (relative or absolute) of the target for this new symbolic
|
||||
link.
|
||||
:param str path:
|
||||
path (relative or absolute) of the symbolic link to create.
|
||||
:return: an error code `int` like ``SFTP_OK``.
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -16,10 +16,6 @@
|
|||
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Exceptions defined by paramiko.
|
||||
"""
|
||||
|
||||
|
||||
class SSHException (Exception):
|
||||
"""
|
||||
|
@ -34,7 +30,7 @@ class AuthenticationException (SSHException):
|
|||
possible to retry with different credentials. (Other classes specify more
|
||||
specific reasons.)
|
||||
|
||||
@since: 1.6
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -52,18 +48,19 @@ class BadAuthenticationType (AuthenticationException):
|
|||
the server isn't allowing that type. (It may only allow public-key, for
|
||||
example.)
|
||||
|
||||
@ivar allowed_types: list of allowed authentication types provided by the
|
||||
server (possible values are: C{"none"}, C{"password"}, and
|
||||
C{"publickey"}).
|
||||
@type allowed_types: list
|
||||
:ivar list allowed_types:
|
||||
list of allowed authentication types provided by the server (possible
|
||||
values are: ``"none"``, ``"password"``, and ``"publickey"``).
|
||||
|
||||
@since: 1.1
|
||||
.. versionadded:: 1.1
|
||||
"""
|
||||
allowed_types = []
|
||||
|
||||
def __init__(self, explanation, types):
|
||||
AuthenticationException.__init__(self, explanation)
|
||||
self.allowed_types = types
|
||||
# for unpickling
|
||||
self.args = (explanation, types, )
|
||||
|
||||
def __str__(self):
|
||||
return SSHException.__str__(self) + ' (allowed_types=%r)' % self.allowed_types
|
||||
|
@ -78,50 +75,50 @@ class PartialAuthentication (AuthenticationException):
|
|||
def __init__(self, types):
|
||||
AuthenticationException.__init__(self, 'partial authentication')
|
||||
self.allowed_types = types
|
||||
# for unpickling
|
||||
self.args = (types, )
|
||||
|
||||
|
||||
class ChannelException (SSHException):
|
||||
"""
|
||||
Exception raised when an attempt to open a new L{Channel} fails.
|
||||
Exception raised when an attempt to open a new `.Channel` fails.
|
||||
|
||||
@ivar code: the error code returned by the server
|
||||
@type code: int
|
||||
:ivar int code: the error code returned by the server
|
||||
|
||||
@since: 1.6
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
def __init__(self, code, text):
|
||||
SSHException.__init__(self, text)
|
||||
self.code = code
|
||||
# for unpickling
|
||||
self.args = (code, text, )
|
||||
|
||||
|
||||
class BadHostKeyException (SSHException):
|
||||
"""
|
||||
The host key given by the SSH server did not match what we were expecting.
|
||||
|
||||
@ivar hostname: the hostname of the SSH server
|
||||
@type hostname: str
|
||||
@ivar key: the host key presented by the server
|
||||
@type key: L{PKey}
|
||||
@ivar expected_key: the host key expected
|
||||
@type expected_key: L{PKey}
|
||||
:ivar str hostname: the hostname of the SSH server
|
||||
:ivar PKey got_key: the host key presented by the server
|
||||
:ivar PKey expected_key: the host key expected
|
||||
|
||||
@since: 1.6
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
def __init__(self, hostname, got_key, expected_key):
|
||||
SSHException.__init__(self, 'Host key for server %s does not match!' % hostname)
|
||||
self.hostname = hostname
|
||||
self.key = got_key
|
||||
self.expected_key = expected_key
|
||||
# for unpickling
|
||||
self.args = (hostname, got_key, expected_key, )
|
||||
|
||||
|
||||
class ProxyCommandFailure (SSHException):
|
||||
"""
|
||||
The "ProxyCommand" found in the .ssh/config file returned an error.
|
||||
|
||||
@ivar command: The command line that is generating this exception.
|
||||
@type command: str
|
||||
@ivar error: The error captured from the proxy command output.
|
||||
@type error: str
|
||||
:ivar str command: The command line that is generating this exception.
|
||||
:ivar str error: The error captured from the proxy command output.
|
||||
"""
|
||||
def __init__(self, command, error):
|
||||
SSHException.__init__(self,
|
||||
|
@ -130,3 +127,5 @@ class ProxyCommandFailure (SSHException):
|
|||
)
|
||||
)
|
||||
self.error = error
|
||||
# for unpickling
|
||||
self.args = (command, error, )
|
||||
|
|
File diff suppressed because it is too large
Load Diff
181
paramiko/util.py
181
paramiko/util.py
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -29,78 +29,65 @@ import sys
|
|||
import struct
|
||||
import traceback
|
||||
import threading
|
||||
import logging
|
||||
|
||||
from paramiko.common import *
|
||||
from paramiko.common import DEBUG, zero_byte, xffffffff, max_byte
|
||||
from paramiko.py3compat import PY2, long, byte_ord, b, byte_chr
|
||||
from paramiko.config import SSHConfig
|
||||
|
||||
|
||||
# Change by RogerB - python < 2.3 doesn't have enumerate so we implement it
|
||||
if sys.version_info < (2,3):
|
||||
class enumerate:
|
||||
def __init__ (self, sequence):
|
||||
self.sequence = sequence
|
||||
def __iter__ (self):
|
||||
count = 0
|
||||
for item in self.sequence:
|
||||
yield (count, item)
|
||||
count += 1
|
||||
|
||||
|
||||
def inflate_long(s, always_positive=False):
|
||||
"turns a normalized byte string into a long-int (adapted from Crypto.Util.number)"
|
||||
out = 0L
|
||||
"""turns a normalized byte string into a long-int (adapted from Crypto.Util.number)"""
|
||||
out = long(0)
|
||||
negative = 0
|
||||
if not always_positive and (len(s) > 0) and (ord(s[0]) >= 0x80):
|
||||
if not always_positive and (len(s) > 0) and (byte_ord(s[0]) >= 0x80):
|
||||
negative = 1
|
||||
if len(s) % 4:
|
||||
filler = '\x00'
|
||||
filler = zero_byte
|
||||
if negative:
|
||||
filler = '\xff'
|
||||
filler = max_byte
|
||||
# never convert this to ``s +=`` because this is a string, not a number
|
||||
# noinspection PyAugmentAssignment
|
||||
s = filler * (4 - len(s) % 4) + s
|
||||
for i in range(0, len(s), 4):
|
||||
out = (out << 32) + struct.unpack('>I', s[i:i+4])[0]
|
||||
if negative:
|
||||
out -= (1L << (8 * len(s)))
|
||||
out -= (long(1) << (8 * len(s)))
|
||||
return out
|
||||
|
||||
deflate_zero = zero_byte if PY2 else 0
|
||||
deflate_ff = max_byte if PY2 else 0xff
|
||||
|
||||
|
||||
def deflate_long(n, add_sign_padding=True):
|
||||
"turns a long-int into a normalized byte string (adapted from Crypto.Util.number)"
|
||||
"""turns a long-int into a normalized byte string (adapted from Crypto.Util.number)"""
|
||||
# after much testing, this algorithm was deemed to be the fastest
|
||||
s = ''
|
||||
s = bytes()
|
||||
n = long(n)
|
||||
while (n != 0) and (n != -1):
|
||||
s = struct.pack('>I', n & 0xffffffffL) + s
|
||||
n = n >> 32
|
||||
s = struct.pack('>I', n & xffffffff) + s
|
||||
n >>= 32
|
||||
# strip off leading zeros, FFs
|
||||
for i in enumerate(s):
|
||||
if (n == 0) and (i[1] != '\000'):
|
||||
if (n == 0) and (i[1] != deflate_zero):
|
||||
break
|
||||
if (n == -1) and (i[1] != '\xff'):
|
||||
if (n == -1) and (i[1] != deflate_ff):
|
||||
break
|
||||
else:
|
||||
# degenerate case, n was either 0 or -1
|
||||
i = (0,)
|
||||
if n == 0:
|
||||
s = '\000'
|
||||
s = zero_byte
|
||||
else:
|
||||
s = '\xff'
|
||||
s = max_byte
|
||||
s = s[i[0]:]
|
||||
if add_sign_padding:
|
||||
if (n == 0) and (ord(s[0]) >= 0x80):
|
||||
s = '\x00' + s
|
||||
if (n == -1) and (ord(s[0]) < 0x80):
|
||||
s = '\xff' + s
|
||||
if (n == 0) and (byte_ord(s[0]) >= 0x80):
|
||||
s = zero_byte + s
|
||||
if (n == -1) and (byte_ord(s[0]) < 0x80):
|
||||
s = max_byte + s
|
||||
return s
|
||||
|
||||
def format_binary_weird(data):
|
||||
out = ''
|
||||
for i in enumerate(data):
|
||||
out += '%02X' % ord(i[1])
|
||||
if i[0] % 2:
|
||||
out += ' '
|
||||
if i[0] % 16 == 15:
|
||||
out += '\n'
|
||||
return out
|
||||
|
||||
def format_binary(data, prefix=''):
|
||||
x = 0
|
||||
|
@ -112,31 +99,37 @@ def format_binary(data, prefix=''):
|
|||
out.append(format_binary_line(data[x:]))
|
||||
return [prefix + x for x in out]
|
||||
|
||||
|
||||
def format_binary_line(data):
|
||||
left = ' '.join(['%02X' % ord(c) for c in data])
|
||||
right = ''.join([('.%c..' % c)[(ord(c)+63)//95] for c in data])
|
||||
left = ' '.join(['%02X' % byte_ord(c) for c in data])
|
||||
right = ''.join([('.%c..' % c)[(byte_ord(c)+63)//95] for c in data])
|
||||
return '%-50s %s' % (left, right)
|
||||
|
||||
|
||||
def hexify(s):
|
||||
return hexlify(s).upper()
|
||||
|
||||
|
||||
def unhexify(s):
|
||||
return unhexlify(s)
|
||||
|
||||
|
||||
def safe_string(s):
|
||||
out = ''
|
||||
for c in s:
|
||||
if (ord(c) >= 32) and (ord(c) <= 127):
|
||||
if (byte_ord(c) >= 32) and (byte_ord(c) <= 127):
|
||||
out += c
|
||||
else:
|
||||
out += '%%%02X' % ord(c)
|
||||
out += '%%%02X' % byte_ord(c)
|
||||
return out
|
||||
|
||||
# ''.join([['%%%02X' % ord(c), c][(ord(c) >= 32) and (ord(c) <= 127)] for c in s])
|
||||
|
||||
def bit_length(n):
|
||||
norm = deflate_long(n, 0)
|
||||
hbyte = ord(norm[0])
|
||||
try:
|
||||
return n.bitlength()
|
||||
except AttributeError:
|
||||
norm = deflate_long(n, False)
|
||||
hbyte = byte_ord(norm[0])
|
||||
if hbyte == 0:
|
||||
return 1
|
||||
bitlen = len(norm) * 8
|
||||
|
@ -145,36 +138,34 @@ def bit_length(n):
|
|||
bitlen -= 1
|
||||
return bitlen
|
||||
|
||||
|
||||
def tb_strings():
|
||||
return ''.join(traceback.format_exception(*sys.exc_info())).split('\n')
|
||||
|
||||
def generate_key_bytes(hashclass, salt, key, nbytes):
|
||||
|
||||
def generate_key_bytes(hash_alg, salt, key, nbytes):
|
||||
"""
|
||||
Given a password, passphrase, or other human-source key, scramble it
|
||||
through a secure hash into some keyworthy bytes. This specific algorithm
|
||||
is used for encrypting/decrypting private key files.
|
||||
|
||||
@param hashclass: class from L{Crypto.Hash} that can be used as a secure
|
||||
hashing function (like C{MD5} or C{SHA}).
|
||||
@type hashclass: L{Crypto.Hash}
|
||||
@param salt: data to salt the hash with.
|
||||
@type salt: string
|
||||
@param key: human-entered password or passphrase.
|
||||
@type key: string
|
||||
@param nbytes: number of bytes to generate.
|
||||
@type nbytes: int
|
||||
@return: key data
|
||||
@rtype: string
|
||||
:param function hash_alg: A function which creates a new hash object, such
|
||||
as ``hashlib.sha256``.
|
||||
:param salt: data to salt the hash with.
|
||||
:type salt: byte string
|
||||
:param str key: human-entered password or passphrase.
|
||||
:param int nbytes: number of bytes to generate.
|
||||
:return: Key data `str`
|
||||
"""
|
||||
keydata = ''
|
||||
digest = ''
|
||||
keydata = bytes()
|
||||
digest = bytes()
|
||||
if len(salt) > 8:
|
||||
salt = salt[:8]
|
||||
while nbytes > 0:
|
||||
hash_obj = hashclass.new()
|
||||
hash_obj = hash_alg()
|
||||
if len(digest) > 0:
|
||||
hash_obj.update(digest)
|
||||
hash_obj.update(key)
|
||||
hash_obj.update(b(key))
|
||||
hash_obj.update(salt)
|
||||
digest = hash_obj.digest()
|
||||
size = min(nbytes, len(digest))
|
||||
|
@ -182,42 +173,45 @@ def generate_key_bytes(hashclass, salt, key, nbytes):
|
|||
nbytes -= size
|
||||
return keydata
|
||||
|
||||
|
||||
def load_host_keys(filename):
|
||||
"""
|
||||
Read a file of known SSH host keys, in the format used by openssh, and
|
||||
return a compound dict of C{hostname -> keytype ->} L{PKey <paramiko.pkey.PKey>}.
|
||||
The hostname may be an IP address or DNS name. The keytype will be either
|
||||
C{"ssh-rsa"} or C{"ssh-dss"}.
|
||||
return a compound dict of ``hostname -> keytype ->`` `PKey
|
||||
<paramiko.pkey.PKey>`. The hostname may be an IP address or DNS name. The
|
||||
keytype will be either ``"ssh-rsa"`` or ``"ssh-dss"``.
|
||||
|
||||
This type of file unfortunately doesn't exist on Windows, but on posix,
|
||||
it will usually be stored in C{os.path.expanduser("~/.ssh/known_hosts")}.
|
||||
it will usually be stored in ``os.path.expanduser("~/.ssh/known_hosts")``.
|
||||
|
||||
Since 1.5.3, this is just a wrapper around L{HostKeys}.
|
||||
Since 1.5.3, this is just a wrapper around `.HostKeys`.
|
||||
|
||||
@param filename: name of the file to read host keys from
|
||||
@type filename: str
|
||||
@return: dict of host keys, indexed by hostname and then keytype
|
||||
@rtype: dict(hostname, dict(keytype, L{PKey <paramiko.pkey.PKey>}))
|
||||
:param str filename: name of the file to read host keys from
|
||||
:return:
|
||||
nested dict of `.PKey` objects, indexed by hostname and then keytype
|
||||
"""
|
||||
from paramiko.hostkeys import HostKeys
|
||||
return HostKeys(filename)
|
||||
|
||||
|
||||
def parse_ssh_config(file_obj):
|
||||
"""
|
||||
Provided only as a backward-compatible wrapper around L{SSHConfig}.
|
||||
Provided only as a backward-compatible wrapper around `.SSHConfig`.
|
||||
"""
|
||||
config = SSHConfig()
|
||||
config.parse(file_obj)
|
||||
return config
|
||||
|
||||
|
||||
def lookup_ssh_host_config(hostname, config):
|
||||
"""
|
||||
Provided only as a backward-compatible wrapper around L{SSHConfig}.
|
||||
Provided only as a backward-compatible wrapper around `.SSHConfig`.
|
||||
"""
|
||||
return config.lookup(hostname)
|
||||
|
||||
|
||||
def mod_inverse(x, m):
|
||||
# it's crazy how small python can make this function.
|
||||
# it's crazy how small Python can make this function.
|
||||
u1, u2, u3 = 1, 0, m
|
||||
v1, v2, v3 = 0, 1, x
|
||||
|
||||
|
@ -233,6 +227,8 @@ def mod_inverse(x, m):
|
|||
_g_thread_ids = {}
|
||||
_g_thread_counter = 0
|
||||
_g_thread_lock = threading.Lock()
|
||||
|
||||
|
||||
def get_thread_id():
|
||||
global _g_thread_ids, _g_thread_counter, _g_thread_lock
|
||||
tid = id(threading.currentThread())
|
||||
|
@ -247,8 +243,9 @@ def get_thread_id():
|
|||
_g_thread_lock.release()
|
||||
return ret
|
||||
|
||||
|
||||
def log_to_file(filename, level=DEBUG):
|
||||
"send paramiko logs to a logfile, if they're not already going somewhere"
|
||||
"""send paramiko logs to a logfile, if they're not already going somewhere"""
|
||||
l = logging.getLogger("paramiko")
|
||||
if len(l.handlers) > 0:
|
||||
return
|
||||
|
@ -259,6 +256,7 @@ def log_to_file(filename, level=DEBUG):
|
|||
'%Y%m%d-%H:%M:%S'))
|
||||
l.addHandler(lh)
|
||||
|
||||
|
||||
# make only one filter object, so it doesn't get applied more than once
|
||||
class PFilter (object):
|
||||
def filter(self, record):
|
||||
|
@ -266,46 +264,59 @@ class PFilter (object):
|
|||
return True
|
||||
_pfilter = PFilter()
|
||||
|
||||
|
||||
def get_logger(name):
|
||||
l = logging.getLogger(name)
|
||||
l.addFilter(_pfilter)
|
||||
return l
|
||||
|
||||
|
||||
def retry_on_signal(function):
|
||||
"""Retries function until it doesn't raise an EINTR error"""
|
||||
while True:
|
||||
try:
|
||||
return function()
|
||||
except EnvironmentError, e:
|
||||
except EnvironmentError as e:
|
||||
if e.errno != errno.EINTR:
|
||||
raise
|
||||
|
||||
|
||||
class Counter (object):
|
||||
"""Stateful counter for CTR mode crypto"""
|
||||
def __init__(self, nbits, initial_value=1L, overflow=0L):
|
||||
def __init__(self, nbits, initial_value=long(1), overflow=long(0)):
|
||||
self.blocksize = nbits / 8
|
||||
self.overflow = overflow
|
||||
# start with value - 1 so we don't have to store intermediate values when counting
|
||||
# could the iv be 0?
|
||||
if initial_value == 0:
|
||||
self.value = array.array('c', '\xFF' * self.blocksize)
|
||||
self.value = array.array('c', max_byte * self.blocksize)
|
||||
else:
|
||||
x = deflate_long(initial_value - 1, add_sign_padding=False)
|
||||
self.value = array.array('c', '\x00' * (self.blocksize - len(x)) + x)
|
||||
self.value = array.array('c', zero_byte * (self.blocksize - len(x)) + x)
|
||||
|
||||
def __call__(self):
|
||||
"""Increament the counter and return the new value"""
|
||||
i = self.blocksize - 1
|
||||
while i > -1:
|
||||
c = self.value[i] = chr((ord(self.value[i]) + 1) % 256)
|
||||
if c != '\x00':
|
||||
c = self.value[i] = byte_chr((byte_ord(self.value[i]) + 1) % 256)
|
||||
if c != zero_byte:
|
||||
return self.value.tostring()
|
||||
i -= 1
|
||||
# counter reset
|
||||
x = deflate_long(self.overflow, add_sign_padding=False)
|
||||
self.value = array.array('c', '\x00' * (self.blocksize - len(x)) + x)
|
||||
self.value = array.array('c', zero_byte * (self.blocksize - len(x)) + x)
|
||||
return self.value.tostring()
|
||||
|
||||
def new(cls, nbits, initial_value=1L, overflow=0L):
|
||||
def new(cls, nbits, initial_value=long(1), overflow=long(0)):
|
||||
return cls(nbits, initial_value=initial_value, overflow=overflow)
|
||||
new = classmethod(new)
|
||||
|
||||
|
||||
def constant_time_bytes_eq(a, b):
|
||||
if len(a) != len(b):
|
||||
return False
|
||||
res = 0
|
||||
# noinspection PyUnresolvedReferences
|
||||
for i in (xrange if PY2 else range)(len(a)):
|
||||
res |= byte_ord(a[i]) ^ byte_ord(b[i])
|
||||
return res == 0
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -21,28 +21,19 @@
|
|||
Functions for communicating with Pageant, the basic windows ssh agent program.
|
||||
"""
|
||||
|
||||
import os
|
||||
import struct
|
||||
import tempfile
|
||||
import mmap
|
||||
import array
|
||||
import platform
|
||||
import ctypes.wintypes
|
||||
import platform
|
||||
import struct
|
||||
from paramiko.util import *
|
||||
|
||||
# if you're on windows, you should have one of these, i guess?
|
||||
# ctypes is part of standard library since Python 2.5
|
||||
_has_win32all = False
|
||||
_has_ctypes = False
|
||||
try:
|
||||
# win32gui is preferred over win32ui to avoid MFC dependencies
|
||||
import win32gui
|
||||
_has_win32all = True
|
||||
import _thread as thread # Python 3.x
|
||||
except ImportError:
|
||||
try:
|
||||
import ctypes
|
||||
_has_ctypes = True
|
||||
except ImportError:
|
||||
pass
|
||||
import thread # Python 2.5-2.7
|
||||
|
||||
from . import _winapi
|
||||
|
||||
|
||||
_AGENT_COPYDATA_ID = 0x804e50ba
|
||||
_AGENT_MAX_MSGLEN = 8192
|
||||
|
@ -52,16 +43,7 @@ win32con_WM_COPYDATA = 74
|
|||
|
||||
|
||||
def _get_pageant_window_object():
|
||||
if _has_win32all:
|
||||
try:
|
||||
hwnd = win32gui.FindWindow('Pageant', 'Pageant')
|
||||
return hwnd
|
||||
except win32gui.error:
|
||||
pass
|
||||
elif _has_ctypes:
|
||||
# Return 0 if there is no Pageant window.
|
||||
return ctypes.windll.user32.FindWindowA('Pageant', 'Pageant')
|
||||
return None
|
||||
|
||||
|
||||
def can_talk_to_agent():
|
||||
|
@ -71,11 +53,12 @@ def can_talk_to_agent():
|
|||
This checks both if we have the required libraries (win32all or ctypes)
|
||||
and if there is a Pageant currently running.
|
||||
"""
|
||||
if (_has_win32all or _has_ctypes) and _get_pageant_window_object():
|
||||
return True
|
||||
return False
|
||||
return bool(_get_pageant_window_object())
|
||||
|
||||
|
||||
ULONG_PTR = ctypes.c_uint64 if platform.architecture()[0] == '64bit' else ctypes.c_uint32
|
||||
|
||||
|
||||
class COPYDATASTRUCT(ctypes.Structure):
|
||||
"""
|
||||
ctypes implementation of
|
||||
|
@ -87,48 +70,41 @@ class COPYDATASTRUCT(ctypes.Structure):
|
|||
('data_loc', ctypes.c_void_p),
|
||||
]
|
||||
|
||||
|
||||
def _query_pageant(msg):
|
||||
"""
|
||||
Communication with the Pageant process is done through a shared
|
||||
memory-mapped file.
|
||||
"""
|
||||
hwnd = _get_pageant_window_object()
|
||||
if not hwnd:
|
||||
# Raise a failure to connect exception, pageant isn't running anymore!
|
||||
return None
|
||||
|
||||
# Write our pageant request string into the file (pageant will read this to determine what to do)
|
||||
filename = tempfile.mktemp('.pag')
|
||||
map_filename = os.path.basename(filename)
|
||||
# create a name for the mmap
|
||||
map_name = 'PageantRequest%08x' % thread.get_ident()
|
||||
|
||||
f = open(filename, 'w+b')
|
||||
f.write(msg )
|
||||
# Ensure the rest of the file is empty, otherwise pageant will read this
|
||||
f.write('\0' * (_AGENT_MAX_MSGLEN - len(msg)))
|
||||
# Create the shared file map that pageant will use to read from
|
||||
pymap = mmap.mmap(f.fileno(), _AGENT_MAX_MSGLEN, tagname=map_filename, access=mmap.ACCESS_WRITE)
|
||||
try:
|
||||
pymap = _winapi.MemoryMap(map_name, _AGENT_MAX_MSGLEN,
|
||||
_winapi.get_security_attributes_for_user(),
|
||||
)
|
||||
with pymap:
|
||||
pymap.write(msg)
|
||||
# Create an array buffer containing the mapped filename
|
||||
char_buffer = array.array("c", map_filename + '\0')
|
||||
char_buffer = array.array("c", b(map_name) + zero_byte)
|
||||
char_buffer_address, char_buffer_size = char_buffer.buffer_info()
|
||||
# Create a string to use for the SendMessage function call
|
||||
cds = COPYDATASTRUCT(_AGENT_COPYDATA_ID, char_buffer_size, char_buffer_address)
|
||||
cds = COPYDATASTRUCT(_AGENT_COPYDATA_ID, char_buffer_size,
|
||||
char_buffer_address)
|
||||
|
||||
if _has_win32all:
|
||||
# win32gui.SendMessage should also allow the same pattern as
|
||||
# ctypes, but let's keep it like this for now...
|
||||
response = win32gui.SendMessage(hwnd, win32con_WM_COPYDATA, ctypes.sizeof(cds), ctypes.addressof(cds))
|
||||
elif _has_ctypes:
|
||||
response = ctypes.windll.user32.SendMessageA(hwnd, win32con_WM_COPYDATA, ctypes.sizeof(cds), ctypes.byref(cds))
|
||||
else:
|
||||
response = 0
|
||||
response = ctypes.windll.user32.SendMessageA(hwnd,
|
||||
win32con_WM_COPYDATA, ctypes.sizeof(cds), ctypes.byref(cds))
|
||||
|
||||
if response > 0:
|
||||
pymap.seek(0)
|
||||
datalen = pymap.read(4)
|
||||
retlen = struct.unpack('>I', datalen)[0]
|
||||
return datalen + pymap.read(retlen)
|
||||
return None
|
||||
finally:
|
||||
pymap.close()
|
||||
f.close()
|
||||
# Remove the file, it was temporary only
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
class PageantConnection(object):
|
||||
|
|
27
setup.py
27
setup.py
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -40,7 +40,10 @@ import sys
|
|||
try:
|
||||
from setuptools import setup
|
||||
kw = {
|
||||
'install_requires': 'pycrypto >= 2.1, != 2.4',
|
||||
'install_requires': [
|
||||
'pycrypto >= 2.1, != 2.4',
|
||||
'ecdsa',
|
||||
],
|
||||
}
|
||||
except ImportError:
|
||||
from distutils.core import setup
|
||||
|
@ -51,21 +54,31 @@ if sys.platform == 'darwin':
|
|||
setup_helper.install_custom_make_tarball()
|
||||
|
||||
|
||||
setup(name = "paramiko",
|
||||
version = "1.9.0",
|
||||
setup(
|
||||
name = "paramiko",
|
||||
version = "1.14.0",
|
||||
description = "SSH2 protocol library",
|
||||
long_description = longdesc,
|
||||
author = "Jeff Forcier",
|
||||
author_email = "jeff@bitprophet.org",
|
||||
url = "https://github.com/paramiko/paramiko/",
|
||||
packages = [ 'paramiko' ],
|
||||
license = 'LGPL',
|
||||
platforms = 'Posix; MacOS X; Windows',
|
||||
classifiers = [ 'Development Status :: 5 - Production/Stable',
|
||||
classifiers = [
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
|
||||
'Operating System :: OS Independent',
|
||||
'Topic :: Internet',
|
||||
'Topic :: Security :: Cryptography' ],
|
||||
long_description = longdesc,
|
||||
'Topic :: Security :: Cryptography',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
],
|
||||
**kw
|
||||
)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
SSH Agents
|
||||
==========
|
||||
|
||||
.. automodule:: paramiko.agent
|
||||
:inherited-members:
|
||||
:no-special-members:
|
|
@ -0,0 +1,4 @@
|
|||
Buffered pipes
|
||||
==============
|
||||
|
||||
.. automodule:: paramiko.buffered_pipe
|
|
@ -0,0 +1,4 @@
|
|||
Channel
|
||||
=======
|
||||
|
||||
.. automodule:: paramiko.channel
|
|
@ -0,0 +1,5 @@
|
|||
Client
|
||||
======
|
||||
|
||||
.. automodule:: paramiko.client
|
||||
:member-order: bysource
|
|
@ -0,0 +1,5 @@
|
|||
Configuration
|
||||
=============
|
||||
|
||||
.. automodule:: paramiko.config
|
||||
:member-order: bysource
|
|
@ -0,0 +1,4 @@
|
|||
Buffered files
|
||||
==============
|
||||
|
||||
.. automodule:: paramiko.file
|
|
@ -0,0 +1,5 @@
|
|||
Host keys / ``known_hosts`` files
|
||||
=================================
|
||||
|
||||
.. automodule:: paramiko.hostkeys
|
||||
:member-order: bysource
|
|
@ -0,0 +1,6 @@
|
|||
Key handling
|
||||
============
|
||||
|
||||
.. automodule:: paramiko.pkey
|
||||
.. automodule:: paramiko.dsskey
|
||||
.. automodule:: paramiko.rsakey
|
|
@ -0,0 +1,4 @@
|
|||
Message
|
||||
=======
|
||||
|
||||
.. automodule:: paramiko.message
|
|
@ -0,0 +1,4 @@
|
|||
Packetizer
|
||||
==========
|
||||
|
||||
.. automodule:: paramiko.packet
|
|
@ -0,0 +1,4 @@
|
|||
Cross-platform pipe implementations
|
||||
===================================
|
||||
|
||||
.. automodule:: paramiko.pipe
|
|
@ -0,0 +1,4 @@
|
|||
``ProxyCommand`` support
|
||||
========================
|
||||
|
||||
.. automodule:: paramiko.proxy
|
|
@ -0,0 +1,5 @@
|
|||
Server implementation
|
||||
=====================
|
||||
|
||||
.. automodule:: paramiko.server
|
||||
:member-order: bysource
|
|
@ -0,0 +1,13 @@
|
|||
SFTP
|
||||
====
|
||||
|
||||
.. automodule:: paramiko.sftp
|
||||
.. automodule:: paramiko.sftp_client
|
||||
.. automodule:: paramiko.sftp_server
|
||||
.. automodule:: paramiko.sftp_attr
|
||||
.. automodule:: paramiko.sftp_file
|
||||
:inherited-members:
|
||||
:no-special-members:
|
||||
:show-inheritance:
|
||||
.. automodule:: paramiko.sftp_handle
|
||||
.. automodule:: paramiko.sftp_si
|
|
@ -0,0 +1,4 @@
|
|||
Exceptions
|
||||
==========
|
||||
|
||||
.. automodule:: paramiko.ssh_exception
|
|
@ -0,0 +1,5 @@
|
|||
Transport
|
||||
=========
|
||||
|
||||
.. automodule:: paramiko.transport
|
||||
:member-order: bysource
|
|
@ -0,0 +1,16 @@
|
|||
# Obtain shared config values
|
||||
import os, sys
|
||||
sys.path.append(os.path.abspath('..'))
|
||||
sys.path.append(os.path.abspath('../..'))
|
||||
from shared_conf import *
|
||||
|
||||
# Enable autodoc, intersphinx
|
||||
extensions.extend(['sphinx.ext.autodoc'])
|
||||
|
||||
# Autodoc settings
|
||||
autodoc_default_flags = ['members', 'special-members']
|
||||
|
||||
# Sister-site links to WWW
|
||||
html_theme_options['extra_nav_links'] = {
|
||||
"Main website": 'http://www.paramiko.org',
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
====================================
|
||||
Welcome to Paramiko's documentation!
|
||||
====================================
|
||||
|
||||
This site covers Paramiko's usage & API documentation. For basic info on what
|
||||
Paramiko is, including its public changelog & how the project is maintained,
|
||||
please see `the main project website <http://paramiko.org>`_.
|
||||
|
||||
|
||||
API documentation
|
||||
=================
|
||||
|
||||
The high-level client API starts with creation of an `.SSHClient` object. For
|
||||
more direct control, pass a socket (or socket-like object) to a `.Transport`,
|
||||
and use `start_server <.Transport.start_server>` or `start_client
|
||||
<.Transport.start_client>` to negotiate with the remote host as either a server
|
||||
or client.
|
||||
|
||||
As a client, you are responsible for authenticating using a password or private
|
||||
key, and checking the server's host key. (Key signature and verification is
|
||||
done by paramiko, but you will need to provide private keys and check that the
|
||||
content of a public key matches what you expected to see.)
|
||||
|
||||
As a server, you are responsible for deciding which users, passwords, and keys
|
||||
to allow, and what kind of channels to allow.
|
||||
|
||||
Once you have finished, either side may request flow-controlled `channels
|
||||
<.Channel>` to the other side, which are Python objects that act like sockets,
|
||||
but send and receive data over the encrypted session.
|
||||
|
||||
For details, please see the following tables of contents (which are organized
|
||||
by area of interest.)
|
||||
|
||||
|
||||
Core SSH protocol classes
|
||||
-------------------------
|
||||
|
||||
.. toctree::
|
||||
api/channel
|
||||
api/client
|
||||
api/message
|
||||
api/packet
|
||||
api/transport
|
||||
|
||||
|
||||
Authentication & keys
|
||||
---------------------
|
||||
|
||||
.. toctree::
|
||||
api/agent
|
||||
api/hostkeys
|
||||
api/keys
|
||||
|
||||
|
||||
Other primary functions
|
||||
-----------------------
|
||||
|
||||
.. toctree::
|
||||
api/config
|
||||
api/proxy
|
||||
api/server
|
||||
api/sftp
|
||||
|
||||
|
||||
Miscellany
|
||||
----------
|
||||
|
||||
.. toctree::
|
||||
api/buffered_pipe
|
||||
api/file
|
||||
api/pipe
|
||||
api/ssh_exception
|
|
@ -0,0 +1,41 @@
|
|||
from datetime import datetime
|
||||
|
||||
import alabaster
|
||||
|
||||
|
||||
# Alabaster theme + mini-extension
|
||||
html_theme_path = [alabaster.get_path()]
|
||||
extensions = ['alabaster', 'sphinx.ext.intersphinx']
|
||||
# Paths relative to invoking conf.py - not this shared file
|
||||
html_theme = 'alabaster'
|
||||
html_theme_options = {
|
||||
'description': "A Python implementation of SSHv2.",
|
||||
'github_user': 'paramiko',
|
||||
'github_repo': 'paramiko',
|
||||
'gittip_user': 'bitprophet',
|
||||
'analytics_id': 'UA-18486793-2',
|
||||
'travis_button': True,
|
||||
}
|
||||
html_sidebars = {
|
||||
'**': [
|
||||
'about.html',
|
||||
'navigation.html',
|
||||
'searchbox.html',
|
||||
'donate.html',
|
||||
]
|
||||
}
|
||||
|
||||
# Everything intersphinx's to Python
|
||||
intersphinx_mapping = {
|
||||
'python': ('http://docs.python.org/2.6', None),
|
||||
}
|
||||
|
||||
# Regular settings
|
||||
project = 'Paramiko'
|
||||
year = datetime.now().year
|
||||
copyright = '%d Jeff Forcier' % year
|
||||
master_doc = 'index'
|
||||
templates_path = ['_templates']
|
||||
exclude_trees = ['_build']
|
||||
source_suffix = '.rst'
|
||||
default_role = 'obj'
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<atom:link href="{{ atom }}" rel="self" type="application/rss+xml" />
|
||||
<title>{{ title }}</title>
|
||||
<link>{{ link }}</link>
|
||||
<description>{{ description }}</description>
|
||||
<pubDate>{{ date }}</pubDate>
|
||||
{% for link, title, desc, date in posts %}
|
||||
<item>
|
||||
<link>{{ link }}</link>
|
||||
<guid>{{ link }}</guid>
|
||||
<title><![CDATA[{{ title }}]]></title>
|
||||
<description><![CDATA[{{ desc }}]]></description>
|
||||
<pubDate>{{ date }}</pubDate>
|
||||
</item>
|
||||
{% endfor %}
|
||||
</channel>
|
||||
</rss>
|
|
@ -0,0 +1,187 @@
|
|||
=========
|
||||
Changelog
|
||||
=========
|
||||
|
||||
* :release:`1.14.0 <2014-05-07>`
|
||||
* :release:`1.13.1 <2014-05-07>`
|
||||
* :release:`1.12.4 <2014-05-07>`
|
||||
* :release:`1.11.6 <2014-05-07>`
|
||||
* :bug:`-` `paramiko.file.BufferedFile.read` incorrectly returned text strings
|
||||
after the Python 3 migration, despite bytes being more appropriate for file
|
||||
contents (which may be binary or of an unknown encoding.) This has been
|
||||
addressed.
|
||||
|
||||
.. note::
|
||||
`paramiko.file.BufferedFile.readline` continues to return strings, not
|
||||
bytes, as "lines" only make sense for textual data. It assumes UTF-8 by
|
||||
default.
|
||||
|
||||
This should fix `this issue raised on the Obnam mailing list
|
||||
<http://comments.gmane.org/gmane.comp.sysutils.backup.obnam/252>`_. Thanks
|
||||
to Antoine Brenner for the patch.
|
||||
* :bug:`-` Added self.args for exception classes. Used for unpickling. Related
|
||||
to (`Fabric #986 <https://github.com/fabric/fabric/issues/986>`_, `Fabric
|
||||
#714 <https://github.com/fabric/fabric/issues/714>`_). Thanks to Alex
|
||||
Plugaru.
|
||||
* :bug:`-` Fix logging error in sftp_client for filenames containing the '%'
|
||||
character. Thanks to Antoine Brenner.
|
||||
* :bug:`308` Fix regression in dsskey.py that caused sporadic signature
|
||||
verification failures. Thanks to Chris Rose.
|
||||
* :support:`299` Use deterministic signatures for ECDSA keys for improved
|
||||
security. Thanks to Alex Gaynor.
|
||||
* :support:`297` Replace PyCrypto's ``Random`` with `os.urandom` for improved
|
||||
speed and security. Thanks again to Alex.
|
||||
* :support:`295` Swap out a bunch of PyCrypto hash functions with use of
|
||||
`hashlib`. Thanks to Alex Gaynor.
|
||||
* :support:`290` (also :issue:`292`) Add support for building universal
|
||||
(Python 2+3 compatible) wheel files during the release process. Courtesy of
|
||||
Alex Gaynor.
|
||||
* :support:`284` Add Python language trove identifiers to ``setup.py``. Thanks
|
||||
to Alex Gaynor for catch & patch.
|
||||
* :bug:`235` Improve string type testing in a handful of spots (e.g. ``s/if
|
||||
type(x) is str/if isinstance(x, basestring)/g``.) Thanks to ``@ksamuel`` for
|
||||
the report.
|
||||
* :release:`1.13.0 <2014-03-13>`
|
||||
* :release:`1.12.3 <2014-03-13>`
|
||||
* :release:`1.11.5 <2014-03-13>`
|
||||
* :release:`1.10.7 <2014-03-13>`
|
||||
* :feature:`16` **Python 3 support!** Our test suite passes under Python 3, and
|
||||
it (& Fabric's test suite) continues to pass under Python 2. **Python 2.5 is
|
||||
no longer supported with this change!**
|
||||
|
||||
The merged code was built on many contributors' efforts, both code &
|
||||
feedback. In no particular order, we thank Daniel Goertzen, Ivan Kolodyazhny,
|
||||
Tomi Pieviläinen, Jason R. Coombs, Jan N. Schulze, ``@Lazik``, Dorian Pula,
|
||||
Scott Maxwell, Tshepang Lekhonkhobe, Aaron Meurer, and Dave Halter.
|
||||
* :support:`256 backported` Convert API documentation to Sphinx, yielding a new
|
||||
API docs website to replace the old Epydoc one. Thanks to Olle Lundberg for
|
||||
the initial conversion work.
|
||||
* :bug:`-` Use constant-time hash comparison operations where possible, to
|
||||
protect against `timing-based attacks
|
||||
<http://codahale.com/a-lesson-in-timing-attacks/>`_. Thanks to Alex Gaynor
|
||||
for the patch.
|
||||
* :release:`1.12.2 <2014-02-14>`
|
||||
* :release:`1.11.4 <2014-02-14>`
|
||||
* :release:`1.10.6 <2014-02-14>`
|
||||
* :feature:`58` Allow client code to access the stored SSH server banner via
|
||||
`Transport.get_banner <paramiko.transport.Transport.get_banner>`. Thanks to
|
||||
``@Jhoanor`` for the patch.
|
||||
* :bug:`252` (`Fabric #1020 <https://github.com/fabric/fabric/issues/1020>`_)
|
||||
Enhanced the implementation of ``ProxyCommand`` to avoid a deadlock/hang
|
||||
condition that frequently occurs at ``Transport`` shutdown time. Thanks to
|
||||
Mateusz Kobos, Matthijs van der Vleuten and Guillaume Zitta for the original
|
||||
reports and to Marius Gedminas for helping test nontrivial use cases.
|
||||
* :bug:`268` Fix some missed renames of ``ProxyCommand`` related error classes.
|
||||
Thanks to Marius Gedminas for catch & patch.
|
||||
* :bug:`34` (PR :issue:`35`) Fix SFTP prefetching incompatibility with some
|
||||
SFTP servers regarding request/response ordering. Thanks to Richard
|
||||
Kettlewell.
|
||||
* :bug:`193` (and its attentant PRs :issue:`230` & :issue:`253`) Fix SSH agent
|
||||
problems present on Windows. Thanks to David Hobbs for initial report and to
|
||||
Aarni Koskela & Olle Lundberg for the patches.
|
||||
* :release:`1.12.1 <2014-01-08>`
|
||||
* :release:`1.11.3 <2014-01-08>`
|
||||
* :release:`1.10.5 <2014-01-08>`
|
||||
* :bug:`225 (1.12+)` Note ecdsa requirement in README. Thanks to Amaury
|
||||
Rodriguez for the catch.
|
||||
* :bug:`176` Fix AttributeError bugs in known_hosts file (re)loading. Thanks
|
||||
to Nathan Scowcroft for the patch & Martin Blumenstingl for the initial test
|
||||
case.
|
||||
* :release:`1.12.0 <2013-09-27>`
|
||||
* :release:`1.11.2 <2013-09-27>`
|
||||
* :release:`1.10.4 <2013-09-27>`
|
||||
* :feature:`152` Add tentative support for ECDSA keys. **This adds the ecdsa
|
||||
module as a new dependency of Paramiko.** The module is available at
|
||||
`warner/python-ecdsa on Github <https://github.com/warner/python-ecdsa>`_ and
|
||||
`ecdsa on PyPI <https://pypi.python.org/pypi/ecdsa>`_.
|
||||
|
||||
* Note that you might still run into problems with key negotiation --
|
||||
Paramiko picks the first key that the server offers, which might not be
|
||||
what you have in your known_hosts file.
|
||||
* Mega thanks to Ethan Glasser-Camp for the patch.
|
||||
|
||||
* :feature:`136` Add server-side support for the SSH protocol's 'env' command.
|
||||
Thanks to Benjamin Pollack for the patch.
|
||||
* :bug:`156 (1.11+)` Fix potential deadlock condition when using Channel
|
||||
objects as sockets (e.g. when using SSH gatewaying). Thanks to Steven Noonan
|
||||
and Frank Arnold for catch & patch.
|
||||
* :bug:`179` Fix a missing variable causing errors when an ssh_config file has
|
||||
a non-default AddressFamily set. Thanks to Ed Marshall & Tomaz Muraus for
|
||||
catch & patch.
|
||||
* :bug:`200` Fix an exception-causing typo in ``demo_simple.py``. Thanks to Alex
|
||||
Buchanan for catch & Dave Foster for patch.
|
||||
* :bug:`199` Typo fix in the license header cross-project. Thanks to Armin
|
||||
Ronacher for catch & patch.
|
||||
* :release:`1.11.1 <2013-09-20>`
|
||||
* :release:`1.10.3 <2013-09-20>`
|
||||
* :bug:`162` Clean up HMAC module import to avoid deadlocks in certain uses of
|
||||
SSHClient. Thanks to Gernot Hillier for the catch & suggested fix.
|
||||
* :bug:`36` Fix the port-forwarding demo to avoid file descriptor errors.
|
||||
Thanks to Jonathan Halcrow for catch & patch.
|
||||
* :bug:`168` Update config handling to properly handle multiple 'localforward'
|
||||
and 'remoteforward' keys. Thanks to Emre Yılmaz for the patch.
|
||||
* :release:`1.11.0 <2013-07-26>`
|
||||
* :release:`1.10.2 <2013-07-26>`
|
||||
* :bug:`98 major` On Windows, when interacting with the PuTTY PAgeant, Paramiko
|
||||
now creates the shared memory map with explicit Security Attributes of the
|
||||
user, which is the same technique employed by the canonical PuTTY library to
|
||||
avoid permissions issues when Paramiko is running under a different UAC
|
||||
context than the PuTTY Ageant process. Thanks to Jason R. Coombs for the
|
||||
patch.
|
||||
* :support:`100` Remove use of PyWin32 in ``win_pageant`` module. Module was
|
||||
already dependent on ctypes for constructing appropriate structures and had
|
||||
ctypes implementations of all functionality. Thanks to Jason R. Coombs for
|
||||
the patch.
|
||||
* :bug:`87 major` Ensure updates to ``known_hosts`` files account for any
|
||||
updates to said files after Paramiko initially read them. (Includes related
|
||||
fix to guard against duplicate entries during subsequent ``known_hosts``
|
||||
loads.) Thanks to ``@sunweaver`` for the contribution.
|
||||
* :bug:`153` (also :issue:`67`) Warn on parse failure when reading known_hosts
|
||||
file. Thanks to ``@glasserc`` for patch.
|
||||
* :bug:`146` Indentation fixes for readability. Thanks to Abhinav Upadhyay for
|
||||
catch & patch.
|
||||
* :release:`1.10.1 <2013-04-05>`
|
||||
* :bug:`142` (`Fabric #811 <https://github.com/fabric/fabric/issues/811>`_)
|
||||
SFTP put of empty file will still return the attributes of the put file.
|
||||
Thanks to Jason R. Coombs for the patch.
|
||||
* :bug:`154` (`Fabric #876 <https://github.com/fabric/fabric/issues/876>`_)
|
||||
Forwarded SSH agent connections left stale local pipes lying around, which
|
||||
could cause local (and sometimes remote or network) resource starvation when
|
||||
running many agent-using remote commands. Thanks to Kevin Tegtmeier for catch
|
||||
& patch.
|
||||
* :release:`1.10.0 <2013-03-01>`
|
||||
* :feature:`66` Batch SFTP writes to help speed up file transfers. Thanks to
|
||||
Olle Lundberg for the patch.
|
||||
* :bug:`133 major` Fix handling of window-change events to be on-spec and not
|
||||
attempt to wait for a response from the remote sshd; this fixes problems with
|
||||
less common targets such as some Cisco devices. Thanks to Phillip Heller for
|
||||
catch & patch.
|
||||
* :feature:`93` Overhaul SSH config parsing to be in line with ``man
|
||||
ssh_config`` (& the behavior of ``ssh`` itself), including addition of parameter
|
||||
expansion within config values. Thanks to Olle Lundberg for the patch.
|
||||
* :feature:`110` Honor SSH config ``AddressFamily`` setting when looking up
|
||||
local host's FQDN. Thanks to John Hensley for the patch.
|
||||
* :feature:`128` Defer FQDN resolution until needed, when parsing SSH config
|
||||
files. Thanks to Parantapa Bhattacharya for catch & patch.
|
||||
* :bug:`102 major` Forego random padding for packets when running under
|
||||
``*-ctr`` ciphers. This corrects some slowdowns on platforms where random
|
||||
byte generation is inefficient (e.g. Windows). Thanks to ``@warthog618`` for
|
||||
catch & patch, and Michael van der Kolff for code/technique review.
|
||||
* :feature:`127` Turn ``SFTPFile`` into a context manager. Thanks to Michael
|
||||
Williamson for the patch.
|
||||
* :feature:`116` Limit ``Message.get_bytes`` to an upper bound of 1MB to protect
|
||||
against potential DoS vectors. Thanks to ``@mvschaik`` for catch & patch.
|
||||
* :feature:`115` Add convenience ``get_pty`` kwarg to ``Client.exec_command`` so
|
||||
users not manually controlling a channel object can still toggle PTY
|
||||
creation. Thanks to Michael van der Kolff for the patch.
|
||||
* :feature:`71` Add ``SFTPClient.putfo`` and ``.getfo`` methods to allow direct
|
||||
uploading/downloading of file-like objects. Thanks to Eric Buehl for the
|
||||
patch.
|
||||
* :feature:`113` Add ``timeout`` parameter to ``SSHClient.exec_command`` for
|
||||
easier setting of the command's internal channel object's timeout. Thanks to
|
||||
Cernov Vladimir for the patch.
|
||||
* :support:`94` Remove duplication of SSH port constant. Thanks to Olle
|
||||
Lundberg for the catch.
|
||||
* :feature:`80` Expose the internal "is closed" property of the file transfer
|
||||
class ``BufferedFile`` as ``.closed``, better conforming to Python's file
|
||||
interface. Thanks to ``@smunaut`` and James Hiscock for catch & patch.
|
|
@ -0,0 +1,28 @@
|
|||
# Obtain shared config values
|
||||
import sys
|
||||
import os
|
||||
from os.path import abspath, join, dirname
|
||||
|
||||
sys.path.append(abspath(join(dirname(__file__), '..')))
|
||||
from shared_conf import *
|
||||
|
||||
# Releases changelog extension
|
||||
extensions.append('releases')
|
||||
# Paramiko 1.x tags start with 'v'. Meh.
|
||||
releases_release_uri = "https://github.com/paramiko/paramiko/tree/v%s"
|
||||
releases_issue_uri = "https://github.com/paramiko/paramiko/issues/%s"
|
||||
|
||||
# Intersphinx for referencing API/usage docs
|
||||
extensions.append('sphinx.ext.intersphinx')
|
||||
# Default is 'local' building, but reference the public docs site when building
|
||||
# under RTD.
|
||||
target = join(dirname(__file__), '..', 'docs', '_build')
|
||||
if os.environ.get('READTHEDOCS') == 'True':
|
||||
# TODO: switch to docs.paramiko.org post go-live of sphinx API docs
|
||||
target = 'http://docs.paramiko.org/en/latest/'
|
||||
intersphinx_mapping['docs'] = (target, None)
|
||||
|
||||
# Sister-site links to API docs
|
||||
html_theme_options['extra_nav_links'] = {
|
||||
"API Docs": 'http://docs.paramiko.org',
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
=======
|
||||
Contact
|
||||
=======
|
||||
|
||||
You can get in touch with the developer & user community in any of the
|
||||
following ways:
|
||||
|
||||
* IRC: ``#paramiko`` on Freenode
|
||||
* Mailing list: ``paramiko@librelist.com`` (see `the LibreList homepage
|
||||
<http://librelist.com>`_ for usage details).
|
||||
* This website - a blog section is forthcoming.
|
|
@ -0,0 +1,22 @@
|
|||
============
|
||||
Contributing
|
||||
============
|
||||
|
||||
How to get the code
|
||||
===================
|
||||
|
||||
Our primary Git repository is on Github at `paramiko/paramiko
|
||||
<https://github.com/paramiko/paramiko>`_; please follow their instructions for
|
||||
cloning to your local system. (If you intend to submit patches/pull requests,
|
||||
we recommend forking first, then cloning your fork. Github has excellent
|
||||
documentation for all this.)
|
||||
|
||||
|
||||
How to submit bug reports or new code
|
||||
=====================================
|
||||
|
||||
Please see `this project-agnostic contribution guide
|
||||
<http://contribution-guide.org>`_ - we follow it explicitly.
|
||||
|
||||
Our current changelog is located in ``sites/www/changelog.rst`` - the top
|
||||
level files like ``ChangeLog.*`` and ``NEWS`` are historical only.
|
|
@ -0,0 +1,9 @@
|
|||
===================================
|
||||
Frequently Asked/Answered Questions
|
||||
===================================
|
||||
|
||||
Which version should I use? I see multiple active releases.
|
||||
===========================================================
|
||||
|
||||
Please see :ref:`the installation docs <release-lines>` which have an explicit
|
||||
section about this topic.
|
|
@ -0,0 +1,36 @@
|
|||
Welcome to Paramiko!
|
||||
====================
|
||||
|
||||
Paramiko is a Python (2.6+, 3.3+) implementation of the SSHv2 protocol [#]_,
|
||||
providing both client and server functionality. While it leverages a Python C
|
||||
extension for low level cryptography (`PyCrypto <http://pycrypto.org>`_),
|
||||
Paramiko itself is a pure Python interface around SSH networking concepts.
|
||||
|
||||
This website covers project information for Paramiko such as the changelog,
|
||||
contribution guidelines, development roadmap, news/blog, and so forth. Detailed
|
||||
usage and API documentation can be found at our code documentation site,
|
||||
`docs.paramiko.org <http://docs.paramiko.org>`_.
|
||||
|
||||
Please see the sidebar to the left to begin.
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
changelog
|
||||
FAQs <faq>
|
||||
installing
|
||||
contributing
|
||||
contact
|
||||
|
||||
|
||||
.. rubric:: Footnotes
|
||||
|
||||
.. [#]
|
||||
SSH is defined in RFCs
|
||||
`4251 <http://www.rfc-editor.org/rfc/rfc4251.txt>`_,
|
||||
`4252 <http://www.rfc-editor.org/rfc/rfc4252.txt>`_,
|
||||
`4253 <http://www.rfc-editor.org/rfc/rfc4253.txt>`_, and
|
||||
`4254 <http://www.rfc-editor.org/rfc/rfc4254.txt>`_;
|
||||
the primary working implementation of the protocol is the `OpenSSH project
|
||||
<http://openssh.org>`_. Paramiko implements a large portion of the SSH
|
||||
feature set, but there are occasional gaps.
|
|
@ -0,0 +1,101 @@
|
|||
==========
|
||||
Installing
|
||||
==========
|
||||
|
||||
.. _paramiko-itself:
|
||||
|
||||
Paramiko itself
|
||||
===============
|
||||
|
||||
The recommended way to get Paramiko is to **install the latest stable release**
|
||||
via `pip <http://pip-installer.org>`_::
|
||||
|
||||
$ pip install paramiko
|
||||
|
||||
.. note::
|
||||
Users who want the bleeding edge can install the development version via
|
||||
``pip install paramiko==dev``.
|
||||
|
||||
We currently support **Python 2.6, 2.7 and 3.3** (Python **3.2** should also
|
||||
work but has a less-strong compatibility guarantee from us.) Users on Python
|
||||
2.5 or older are urged to upgrade.
|
||||
|
||||
Paramiko has two dependencies: the pure-Python ECDSA module ``ecdsa``, and the
|
||||
PyCrypto C extension. ``ecdsa`` is easily installable from wherever you
|
||||
obtained Paramiko's package; PyCrypto may require more work. Read on for
|
||||
details.
|
||||
|
||||
.. _release-lines:
|
||||
|
||||
Release lines
|
||||
-------------
|
||||
|
||||
Users desiring stability may wish to pin themselves to a specific release line
|
||||
once they first start using Paramiko; to assist in this, we guarantee bugfixes
|
||||
for at least the last 2-3 releases including the latest stable one. This currently means Paramiko **1.11** through **1.13**.
|
||||
|
||||
If you're unsure which version to install, we have suggestions:
|
||||
|
||||
* **Completely new users** should always default to the **latest stable
|
||||
release** (as above, whatever is newest / whatever shows up with ``pip
|
||||
install paramiko``.)
|
||||
* **Users upgrading from a much older version** (e.g. the 1.7.x line) should
|
||||
probably get the **oldest actively supported line** (see the paragraph above
|
||||
this list for what that currently is.)
|
||||
* **Everybody else** is hopefully already "on" a given version and can
|
||||
carefully upgrade to whichever version they care to, when their release line
|
||||
stops being supported.
|
||||
|
||||
|
||||
PyCrypto
|
||||
========
|
||||
|
||||
`PyCrypto <https://www.dlitz.net/software/pycrypto/>`_ provides the low-level
|
||||
(C-based) encryption algorithms we need to implement the SSH protocol. There
|
||||
are a couple gotchas associated with installing PyCrypto: its compatibility
|
||||
with Python's package tools, and the fact that it is a C-based extension.
|
||||
|
||||
C extension
|
||||
-----------
|
||||
|
||||
Unless you are installing from a precompiled source such as a Debian apt
|
||||
repository or RedHat RPM, or using :ref:`pypm <pypm>`, you will also need the
|
||||
ability to build Python C-based modules from source in order to install
|
||||
PyCrypto. Users on **Unix-based platforms** such as Ubuntu or Mac OS X will
|
||||
need the traditional C build toolchain installed (e.g. Developer Tools / XCode
|
||||
Tools on the Mac, or the ``build-essential`` package on Ubuntu or Debian Linux
|
||||
-- basically, anything with ``gcc``, ``make`` and so forth) as well as the
|
||||
Python development libraries, often named ``python-dev`` or similar.
|
||||
|
||||
For **Windows** users we recommend using :ref:`pypm`, installing a C
|
||||
development environment such as `Cygwin <http://cygwin.com>`_ or obtaining a
|
||||
precompiled Win32 PyCrypto package from `voidspace's Python modules page
|
||||
<http://www.voidspace.org.uk/python/modules.shtml#pycrypto>`_.
|
||||
|
||||
.. note::
|
||||
Some Windows users whose Python is 64-bit have found that the PyCrypto
|
||||
dependency ``winrandom`` may not install properly, leading to ImportErrors.
|
||||
In this scenario, you'll probably need to compile ``winrandom`` yourself
|
||||
via e.g. MS Visual Studio. See `Fabric #194
|
||||
<https://github.com/fabric/fabric/issues/194>`_ for info.
|
||||
|
||||
|
||||
.. _pypm:
|
||||
|
||||
ActivePython and PyPM
|
||||
=====================
|
||||
|
||||
Windows users who already have ActiveState's `ActivePython
|
||||
<http://www.activestate.com/activepython/downloads>`_ distribution installed
|
||||
may find Paramiko is best installed with `its package manager, PyPM
|
||||
<http://code.activestate.com/pypm/>`_. Below is example output from an
|
||||
installation of Paramiko via ``pypm``::
|
||||
|
||||
C:\> pypm install paramiko
|
||||
The following packages will be installed into "%APPDATA%\Python" (2.7):
|
||||
paramiko-1.7.8 pycrypto-2.4
|
||||
Get: [pypm-free.activestate.com] paramiko 1.7.8
|
||||
Get: [pypm-free.activestate.com] pycrypto 2.4
|
||||
Installing paramiko-1.7.8
|
||||
Installing pycrypto-2.4
|
||||
C:\>
|
|
@ -0,0 +1,49 @@
|
|||
from os import mkdir
|
||||
from os.path import join
|
||||
from shutil import rmtree, copytree
|
||||
|
||||
from invoke import Collection, ctask as task
|
||||
from invocations import docs as _docs
|
||||
from invocations.packaging import publish
|
||||
|
||||
|
||||
d = 'sites'
|
||||
|
||||
# Usage doc/API site (published as docs.paramiko.org)
|
||||
docs_path = join(d, 'docs')
|
||||
docs_build = join(docs_path, '_build')
|
||||
docs = Collection.from_module(_docs, name='docs', config={
|
||||
'sphinx.source': docs_path,
|
||||
'sphinx.target': docs_build,
|
||||
})
|
||||
|
||||
# Main/about/changelog site ((www.)?paramiko.org)
|
||||
www_path = join(d, 'www')
|
||||
www = Collection.from_module(_docs, name='www', config={
|
||||
'sphinx.source': www_path,
|
||||
'sphinx.target': join(www_path, '_build'),
|
||||
})
|
||||
|
||||
|
||||
# Until we move to spec-based testing
|
||||
@task
|
||||
def test(ctx):
|
||||
ctx.run("python test.py --verbose")
|
||||
|
||||
@task
|
||||
def coverage(ctx):
|
||||
ctx.run("coverage run --source=paramiko test.py --verbose")
|
||||
|
||||
|
||||
# Until we stop bundling docs w/ releases. Need to discover use cases first.
|
||||
@task('docs') # Will invoke the API doc site build
|
||||
def release(ctx):
|
||||
# Move the built docs into where Epydocs used to live
|
||||
target = 'docs'
|
||||
rmtree(target, ignore_errors=True)
|
||||
copytree(docs_build, target)
|
||||
# Publish
|
||||
publish(ctx, wheel=True)
|
||||
|
||||
|
||||
ns = Collection(test, coverage, release, docs=docs, www=www)
|
33
test.py
33
test.py
|
@ -9,7 +9,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -29,22 +29,21 @@ import unittest
|
|||
from optparse import OptionParser
|
||||
import paramiko
|
||||
import threading
|
||||
from paramiko.py3compat import PY2
|
||||
|
||||
sys.path.append('tests')
|
||||
|
||||
from test_message import MessageTest
|
||||
from test_file import BufferedFileTest
|
||||
from test_buffered_pipe import BufferedPipeTest
|
||||
from test_util import UtilTest
|
||||
from test_hostkeys import HostKeysTest
|
||||
from test_pkey import KeyTest
|
||||
from test_kex import KexTest
|
||||
from test_packetizer import PacketizerTest
|
||||
from test_auth import AuthTest
|
||||
from test_transport import TransportTest
|
||||
from test_sftp import SFTPTest
|
||||
from test_sftp_big import BigSFTPTest
|
||||
from test_client import SSHClientTest
|
||||
from tests.test_message import MessageTest
|
||||
from tests.test_file import BufferedFileTest
|
||||
from tests.test_buffered_pipe import BufferedPipeTest
|
||||
from tests.test_util import UtilTest
|
||||
from tests.test_hostkeys import HostKeysTest
|
||||
from tests.test_pkey import KeyTest
|
||||
from tests.test_kex import KexTest
|
||||
from tests.test_packetizer import PacketizerTest
|
||||
from tests.test_auth import AuthTest
|
||||
from tests.test_transport import TransportTest
|
||||
from tests.test_client import SSHClientTest
|
||||
|
||||
default_host = 'localhost'
|
||||
default_user = os.environ.get('USER', 'nobody')
|
||||
|
@ -109,12 +108,15 @@ def main():
|
|||
paramiko.util.log_to_file('test.log')
|
||||
|
||||
if options.use_sftp:
|
||||
from tests.test_sftp import SFTPTest
|
||||
if options.use_loopback_sftp:
|
||||
SFTPTest.init_loopback()
|
||||
else:
|
||||
SFTPTest.init(options.hostname, options.username, options.keyfile, options.password)
|
||||
if not options.use_big_file:
|
||||
SFTPTest.set_big_file_test(False)
|
||||
if options.use_big_file:
|
||||
from tests.test_sftp_big import BigSFTPTest
|
||||
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(MessageTest))
|
||||
|
@ -147,7 +149,10 @@ def main():
|
|||
# TODO: make that not a problem, jeez
|
||||
for thread in threading.enumerate():
|
||||
if thread is not threading.currentThread():
|
||||
if PY2:
|
||||
thread._Thread__stop()
|
||||
else:
|
||||
thread._stop()
|
||||
# Exit correctly
|
||||
if not result.wasSuccessful():
|
||||
sys.exit(1)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -21,6 +21,7 @@
|
|||
"""
|
||||
|
||||
import threading, socket
|
||||
from paramiko.common import asbytes
|
||||
|
||||
|
||||
class LoopSocket (object):
|
||||
|
@ -31,7 +32,7 @@ class LoopSocket (object):
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.__in_buffer = ''
|
||||
self.__in_buffer = bytes()
|
||||
self.__lock = threading.Lock()
|
||||
self.__cv = threading.Condition(self.__lock)
|
||||
self.__timeout = None
|
||||
|
@ -41,11 +42,12 @@ class LoopSocket (object):
|
|||
self.__unlink()
|
||||
try:
|
||||
self.__lock.acquire()
|
||||
self.__in_buffer = ''
|
||||
self.__in_buffer = bytes()
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
def send(self, data):
|
||||
data = asbytes(data)
|
||||
if self.__mate is None:
|
||||
# EOF
|
||||
raise EOFError()
|
||||
|
@ -57,7 +59,7 @@ class LoopSocket (object):
|
|||
try:
|
||||
if self.__mate is None:
|
||||
# EOF
|
||||
return ''
|
||||
return bytes()
|
||||
if len(self.__in_buffer) == 0:
|
||||
self.__cv.wait(self.__timeout)
|
||||
if len(self.__in_buffer) == 0:
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -23,6 +23,7 @@ A stub SFTP server for loopback SFTP testing.
|
|||
import os
|
||||
from paramiko import ServerInterface, SFTPServerInterface, SFTPServer, SFTPAttributes, \
|
||||
SFTPHandle, SFTP_OK, AUTH_SUCCESSFUL, OPEN_SUCCEEDED
|
||||
from paramiko.common import o666
|
||||
|
||||
|
||||
class StubServer (ServerInterface):
|
||||
|
@ -38,7 +39,7 @@ class StubSFTPHandle (SFTPHandle):
|
|||
def stat(self):
|
||||
try:
|
||||
return SFTPAttributes.from_stat(os.fstat(self.readfile.fileno()))
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
|
||||
def chattr(self, attr):
|
||||
|
@ -47,7 +48,7 @@ class StubSFTPHandle (SFTPHandle):
|
|||
try:
|
||||
SFTPServer.set_file_attr(self.filename, attr)
|
||||
return SFTP_OK
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
|
||||
|
||||
|
@ -69,21 +70,21 @@ class StubSFTPServer (SFTPServerInterface):
|
|||
attr.filename = fname
|
||||
out.append(attr)
|
||||
return out
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
|
||||
def stat(self, path):
|
||||
path = self._realpath(path)
|
||||
try:
|
||||
return SFTPAttributes.from_stat(os.stat(path))
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
|
||||
def lstat(self, path):
|
||||
path = self._realpath(path)
|
||||
try:
|
||||
return SFTPAttributes.from_stat(os.lstat(path))
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
|
||||
def open(self, path, flags, attr):
|
||||
|
@ -97,8 +98,8 @@ class StubSFTPServer (SFTPServerInterface):
|
|||
else:
|
||||
# os.open() defaults to 0777 which is
|
||||
# an odd default mode for files
|
||||
fd = os.open(path, flags, 0666)
|
||||
except OSError, e:
|
||||
fd = os.open(path, flags, o666)
|
||||
except OSError as e:
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
if (flags & os.O_CREAT) and (attr is not None):
|
||||
attr._flags &= ~attr.FLAG_PERMISSIONS
|
||||
|
@ -118,7 +119,7 @@ class StubSFTPServer (SFTPServerInterface):
|
|||
fstr = 'rb'
|
||||
try:
|
||||
f = os.fdopen(fd, fstr)
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
fobj = StubSFTPHandle(flags)
|
||||
fobj.filename = path
|
||||
|
@ -130,7 +131,7 @@ class StubSFTPServer (SFTPServerInterface):
|
|||
path = self._realpath(path)
|
||||
try:
|
||||
os.remove(path)
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
return SFTP_OK
|
||||
|
||||
|
@ -139,7 +140,7 @@ class StubSFTPServer (SFTPServerInterface):
|
|||
newpath = self._realpath(newpath)
|
||||
try:
|
||||
os.rename(oldpath, newpath)
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
return SFTP_OK
|
||||
|
||||
|
@ -149,7 +150,7 @@ class StubSFTPServer (SFTPServerInterface):
|
|||
os.mkdir(path)
|
||||
if attr is not None:
|
||||
SFTPServer.set_file_attr(path, attr)
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
return SFTP_OK
|
||||
|
||||
|
@ -157,7 +158,7 @@ class StubSFTPServer (SFTPServerInterface):
|
|||
path = self._realpath(path)
|
||||
try:
|
||||
os.rmdir(path)
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
return SFTP_OK
|
||||
|
||||
|
@ -165,7 +166,7 @@ class StubSFTPServer (SFTPServerInterface):
|
|||
path = self._realpath(path)
|
||||
try:
|
||||
SFTPServer.set_file_attr(path, attr)
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
return SFTP_OK
|
||||
|
||||
|
@ -185,7 +186,7 @@ class StubSFTPServer (SFTPServerInterface):
|
|||
target_path = '<error>'
|
||||
try:
|
||||
os.symlink(target_path, path)
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
return SFTP_OK
|
||||
|
||||
|
@ -193,7 +194,7 @@ class StubSFTPServer (SFTPServerInterface):
|
|||
path = self._realpath(path)
|
||||
try:
|
||||
symlink = os.readlink(path)
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
# if it's absolute, remove the root
|
||||
if os.path.isabs(symlink):
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -25,17 +25,20 @@ import threading
|
|||
import unittest
|
||||
|
||||
from paramiko import Transport, ServerInterface, RSAKey, DSSKey, \
|
||||
SSHException, BadAuthenticationType, InteractiveQuery, ChannelException, \
|
||||
BadAuthenticationType, InteractiveQuery, \
|
||||
AuthenticationException
|
||||
from paramiko import AUTH_FAILED, AUTH_PARTIALLY_SUCCESSFUL, AUTH_SUCCESSFUL
|
||||
from paramiko import OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
||||
from loop import LoopSocket
|
||||
from paramiko.py3compat import u
|
||||
from tests.loop import LoopSocket
|
||||
from tests.util import test_path
|
||||
|
||||
_pwd = u('\u2022')
|
||||
|
||||
|
||||
class NullServer (ServerInterface):
|
||||
paranoid_did_password = False
|
||||
paranoid_did_public_key = False
|
||||
paranoid_key = DSSKey.from_private_key_file('tests/test_dss.key')
|
||||
paranoid_key = DSSKey.from_private_key_file(test_path('test_dss.key'))
|
||||
|
||||
def get_allowed_auths(self, username):
|
||||
if username == 'slowdive':
|
||||
|
@ -64,7 +67,7 @@ class NullServer (ServerInterface):
|
|||
if self.paranoid_did_public_key:
|
||||
return AUTH_SUCCESSFUL
|
||||
return AUTH_PARTIALLY_SUCCESSFUL
|
||||
if (username == 'utf8') and (password == u'\u2022'):
|
||||
if (username == 'utf8') and (password == _pwd):
|
||||
return AUTH_SUCCESSFUL
|
||||
if (username == 'non-utf8') and (password == '\xff'):
|
||||
return AUTH_SUCCESSFUL
|
||||
|
@ -110,18 +113,18 @@ class AuthTest (unittest.TestCase):
|
|||
self.sockc.close()
|
||||
|
||||
def start_server(self):
|
||||
host_key = RSAKey.from_private_key_file('tests/test_rsa.key')
|
||||
self.public_host_key = RSAKey(data=str(host_key))
|
||||
host_key = RSAKey.from_private_key_file(test_path('test_rsa.key'))
|
||||
self.public_host_key = RSAKey(data=host_key.asbytes())
|
||||
self.ts.add_server_key(host_key)
|
||||
self.event = threading.Event()
|
||||
self.server = NullServer()
|
||||
self.assert_(not self.event.isSet())
|
||||
self.assertTrue(not self.event.isSet())
|
||||
self.ts.start_server(self.event, self.server)
|
||||
|
||||
def verify_finished(self):
|
||||
self.event.wait(1.0)
|
||||
self.assert_(self.event.isSet())
|
||||
self.assert_(self.ts.is_active())
|
||||
self.assertTrue(self.event.isSet())
|
||||
self.assertTrue(self.ts.is_active())
|
||||
|
||||
def test_1_bad_auth_type(self):
|
||||
"""
|
||||
|
@ -132,11 +135,11 @@ class AuthTest (unittest.TestCase):
|
|||
try:
|
||||
self.tc.connect(hostkey=self.public_host_key,
|
||||
username='unknown', password='error')
|
||||
self.assert_(False)
|
||||
self.assertTrue(False)
|
||||
except:
|
||||
etype, evalue, etb = sys.exc_info()
|
||||
self.assertEquals(BadAuthenticationType, etype)
|
||||
self.assertEquals(['publickey'], evalue.allowed_types)
|
||||
self.assertEqual(BadAuthenticationType, etype)
|
||||
self.assertEqual(['publickey'], evalue.allowed_types)
|
||||
|
||||
def test_2_bad_password(self):
|
||||
"""
|
||||
|
@ -147,10 +150,10 @@ class AuthTest (unittest.TestCase):
|
|||
self.tc.connect(hostkey=self.public_host_key)
|
||||
try:
|
||||
self.tc.auth_password(username='slowdive', password='error')
|
||||
self.assert_(False)
|
||||
self.assertTrue(False)
|
||||
except:
|
||||
etype, evalue, etb = sys.exc_info()
|
||||
self.assert_(issubclass(etype, AuthenticationException))
|
||||
self.assertTrue(issubclass(etype, AuthenticationException))
|
||||
self.tc.auth_password(username='slowdive', password='pygmalion')
|
||||
self.verify_finished()
|
||||
|
||||
|
@ -161,10 +164,10 @@ class AuthTest (unittest.TestCase):
|
|||
self.start_server()
|
||||
self.tc.connect(hostkey=self.public_host_key)
|
||||
remain = self.tc.auth_password(username='paranoid', password='paranoid')
|
||||
self.assertEquals(['publickey'], remain)
|
||||
key = DSSKey.from_private_key_file('tests/test_dss.key')
|
||||
self.assertEqual(['publickey'], remain)
|
||||
key = DSSKey.from_private_key_file(test_path('test_dss.key'))
|
||||
remain = self.tc.auth_publickey(username='paranoid', key=key)
|
||||
self.assertEquals([], remain)
|
||||
self.assertEqual([], remain)
|
||||
self.verify_finished()
|
||||
|
||||
def test_4_interactive_auth(self):
|
||||
|
@ -180,9 +183,9 @@ class AuthTest (unittest.TestCase):
|
|||
self.got_prompts = prompts
|
||||
return ['cat']
|
||||
remain = self.tc.auth_interactive('commie', handler)
|
||||
self.assertEquals(self.got_title, 'password')
|
||||
self.assertEquals(self.got_prompts, [('Password', False)])
|
||||
self.assertEquals([], remain)
|
||||
self.assertEqual(self.got_title, 'password')
|
||||
self.assertEqual(self.got_prompts, [('Password', False)])
|
||||
self.assertEqual([], remain)
|
||||
self.verify_finished()
|
||||
|
||||
def test_5_interactive_auth_fallback(self):
|
||||
|
@ -193,7 +196,7 @@ class AuthTest (unittest.TestCase):
|
|||
self.start_server()
|
||||
self.tc.connect(hostkey=self.public_host_key)
|
||||
remain = self.tc.auth_password('commie', 'cat')
|
||||
self.assertEquals([], remain)
|
||||
self.assertEqual([], remain)
|
||||
self.verify_finished()
|
||||
|
||||
def test_6_auth_utf8(self):
|
||||
|
@ -202,8 +205,8 @@ class AuthTest (unittest.TestCase):
|
|||
"""
|
||||
self.start_server()
|
||||
self.tc.connect(hostkey=self.public_host_key)
|
||||
remain = self.tc.auth_password('utf8', u'\u2022')
|
||||
self.assertEquals([], remain)
|
||||
remain = self.tc.auth_password('utf8', _pwd)
|
||||
self.assertEqual([], remain)
|
||||
self.verify_finished()
|
||||
|
||||
def test_7_auth_non_utf8(self):
|
||||
|
@ -214,7 +217,7 @@ class AuthTest (unittest.TestCase):
|
|||
self.start_server()
|
||||
self.tc.connect(hostkey=self.public_host_key)
|
||||
remain = self.tc.auth_password('non-utf8', '\xff')
|
||||
self.assertEquals([], remain)
|
||||
self.assertEqual([], remain)
|
||||
self.verify_finished()
|
||||
|
||||
def test_8_auth_gets_disconnected(self):
|
||||
|
@ -228,4 +231,4 @@ class AuthTest (unittest.TestCase):
|
|||
remain = self.tc.auth_password('bad-server', 'hello')
|
||||
except:
|
||||
etype, evalue, etb = sys.exc_info()
|
||||
self.assert_(issubclass(etype, AuthenticationException))
|
||||
self.assertTrue(issubclass(etype, AuthenticationException))
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -22,61 +22,60 @@ Some unit tests for BufferedPipe.
|
|||
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
from paramiko.buffered_pipe import BufferedPipe, PipeTimeout
|
||||
from paramiko import pipe
|
||||
|
||||
from util import ParamikoTest
|
||||
from tests.util import ParamikoTest
|
||||
|
||||
|
||||
def delay_thread(pipe):
|
||||
pipe.feed('a')
|
||||
def delay_thread(p):
|
||||
p.feed('a')
|
||||
time.sleep(0.5)
|
||||
pipe.feed('b')
|
||||
pipe.close()
|
||||
p.feed('b')
|
||||
p.close()
|
||||
|
||||
|
||||
def close_thread(pipe):
|
||||
def close_thread(p):
|
||||
time.sleep(0.2)
|
||||
pipe.close()
|
||||
p.close()
|
||||
|
||||
|
||||
class BufferedPipeTest(ParamikoTest):
|
||||
def test_1_buffered_pipe(self):
|
||||
p = BufferedPipe()
|
||||
self.assert_(not p.read_ready())
|
||||
self.assertTrue(not p.read_ready())
|
||||
p.feed('hello.')
|
||||
self.assert_(p.read_ready())
|
||||
self.assertTrue(p.read_ready())
|
||||
data = p.read(6)
|
||||
self.assertEquals('hello.', data)
|
||||
self.assertEqual(b'hello.', data)
|
||||
|
||||
p.feed('plus/minus')
|
||||
self.assertEquals('plu', p.read(3))
|
||||
self.assertEquals('s/m', p.read(3))
|
||||
self.assertEquals('inus', p.read(4))
|
||||
self.assertEqual(b'plu', p.read(3))
|
||||
self.assertEqual(b's/m', p.read(3))
|
||||
self.assertEqual(b'inus', p.read(4))
|
||||
|
||||
p.close()
|
||||
self.assert_(not p.read_ready())
|
||||
self.assertEquals('', p.read(1))
|
||||
self.assertTrue(not p.read_ready())
|
||||
self.assertEqual(b'', p.read(1))
|
||||
|
||||
def test_2_delay(self):
|
||||
p = BufferedPipe()
|
||||
self.assert_(not p.read_ready())
|
||||
self.assertTrue(not p.read_ready())
|
||||
threading.Thread(target=delay_thread, args=(p,)).start()
|
||||
self.assertEquals('a', p.read(1, 0.1))
|
||||
self.assertEqual(b'a', p.read(1, 0.1))
|
||||
try:
|
||||
p.read(1, 0.1)
|
||||
self.assert_(False)
|
||||
self.assertTrue(False)
|
||||
except PipeTimeout:
|
||||
pass
|
||||
self.assertEquals('b', p.read(1, 1.0))
|
||||
self.assertEquals('', p.read(1))
|
||||
self.assertEqual(b'b', p.read(1, 1.0))
|
||||
self.assertEqual(b'', p.read(1))
|
||||
|
||||
def test_3_close_while_reading(self):
|
||||
p = BufferedPipe()
|
||||
threading.Thread(target=close_thread, args=(p,)).start()
|
||||
data = p.read(1, 1.0)
|
||||
self.assertEquals('', data)
|
||||
self.assertEqual(b'', data)
|
||||
|
||||
def test_4_or_pipe(self):
|
||||
p = pipe.make_pipe()
|
||||
|
@ -90,4 +89,3 @@ class BufferedPipeTest(ParamikoTest):
|
|||
self.assertTrue(p._set)
|
||||
p2.clear()
|
||||
self.assertFalse(p._set)
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -21,13 +21,15 @@ Some unit tests for SSHClient.
|
|||
"""
|
||||
|
||||
import socket
|
||||
from tempfile import mkstemp
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
import weakref
|
||||
from binascii import hexlify
|
||||
|
||||
import warnings
|
||||
import os
|
||||
from tests.util import test_path
|
||||
import paramiko
|
||||
from paramiko.common import PY2
|
||||
|
||||
|
||||
class NullServer (paramiko.ServerInterface):
|
||||
|
@ -43,7 +45,7 @@ class NullServer (paramiko.ServerInterface):
|
|||
return paramiko.AUTH_FAILED
|
||||
|
||||
def check_auth_publickey(self, username, key):
|
||||
if (key.get_name() == 'ssh-dss') and (hexlify(key.get_fingerprint()) == '4478f0b9a23cc5182009ff755bc1d26c'):
|
||||
if (key.get_name() == 'ssh-dss') and key.get_fingerprint() == b'\x44\x78\xf0\xb9\xa2\x3c\xc5\x18\x20\x09\xff\x75\x5b\xc1\xd2\x6c':
|
||||
return paramiko.AUTH_SUCCESSFUL
|
||||
return paramiko.AUTH_FAILED
|
||||
|
||||
|
@ -64,8 +66,6 @@ class SSHClientTest (unittest.TestCase):
|
|||
self.sockl.listen(1)
|
||||
self.addr, self.port = self.sockl.getsockname()
|
||||
self.event = threading.Event()
|
||||
thread = threading.Thread(target=self._run)
|
||||
thread.start()
|
||||
|
||||
def tearDown(self):
|
||||
for attr in "tc ts socks sockl".split():
|
||||
|
@ -75,28 +75,28 @@ class SSHClientTest (unittest.TestCase):
|
|||
def _run(self):
|
||||
self.socks, addr = self.sockl.accept()
|
||||
self.ts = paramiko.Transport(self.socks)
|
||||
host_key = paramiko.RSAKey.from_private_key_file('tests/test_rsa.key')
|
||||
host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key'))
|
||||
self.ts.add_server_key(host_key)
|
||||
server = NullServer()
|
||||
self.ts.start_server(self.event, server)
|
||||
|
||||
|
||||
def test_1_client(self):
|
||||
"""
|
||||
verify that the SSHClient stuff works too.
|
||||
"""
|
||||
host_key = paramiko.RSAKey.from_private_key_file('tests/test_rsa.key')
|
||||
public_host_key = paramiko.RSAKey(data=str(host_key))
|
||||
threading.Thread(target=self._run).start()
|
||||
host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key'))
|
||||
public_host_key = paramiko.RSAKey(data=host_key.asbytes())
|
||||
|
||||
self.tc = paramiko.SSHClient()
|
||||
self.tc.get_host_keys().add('[%s]:%d' % (self.addr, self.port), 'ssh-rsa', public_host_key)
|
||||
self.tc.connect(self.addr, self.port, username='slowdive', password='pygmalion')
|
||||
|
||||
self.event.wait(1.0)
|
||||
self.assert_(self.event.isSet())
|
||||
self.assert_(self.ts.is_active())
|
||||
self.assertEquals('slowdive', self.ts.get_username())
|
||||
self.assertEquals(True, self.ts.is_authenticated())
|
||||
self.assertTrue(self.event.isSet())
|
||||
self.assertTrue(self.ts.is_active())
|
||||
self.assertEqual('slowdive', self.ts.get_username())
|
||||
self.assertEqual(True, self.ts.is_authenticated())
|
||||
|
||||
stdin, stdout, stderr = self.tc.exec_command('yes')
|
||||
schan = self.ts.accept(1.0)
|
||||
|
@ -105,10 +105,10 @@ class SSHClientTest (unittest.TestCase):
|
|||
schan.send_stderr('This is on stderr.\n')
|
||||
schan.close()
|
||||
|
||||
self.assertEquals('Hello there.\n', stdout.readline())
|
||||
self.assertEquals('', stdout.readline())
|
||||
self.assertEquals('This is on stderr.\n', stderr.readline())
|
||||
self.assertEquals('', stderr.readline())
|
||||
self.assertEqual('Hello there.\n', stdout.readline())
|
||||
self.assertEqual('', stdout.readline())
|
||||
self.assertEqual('This is on stderr.\n', stderr.readline())
|
||||
self.assertEqual('', stderr.readline())
|
||||
|
||||
stdin.close()
|
||||
stdout.close()
|
||||
|
@ -118,18 +118,19 @@ class SSHClientTest (unittest.TestCase):
|
|||
"""
|
||||
verify that SSHClient works with a DSA key.
|
||||
"""
|
||||
host_key = paramiko.RSAKey.from_private_key_file('tests/test_rsa.key')
|
||||
public_host_key = paramiko.RSAKey(data=str(host_key))
|
||||
threading.Thread(target=self._run).start()
|
||||
host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key'))
|
||||
public_host_key = paramiko.RSAKey(data=host_key.asbytes())
|
||||
|
||||
self.tc = paramiko.SSHClient()
|
||||
self.tc.get_host_keys().add('[%s]:%d' % (self.addr, self.port), 'ssh-rsa', public_host_key)
|
||||
self.tc.connect(self.addr, self.port, username='slowdive', key_filename='tests/test_dss.key')
|
||||
self.tc.connect(self.addr, self.port, username='slowdive', key_filename=test_path('test_dss.key'))
|
||||
|
||||
self.event.wait(1.0)
|
||||
self.assert_(self.event.isSet())
|
||||
self.assert_(self.ts.is_active())
|
||||
self.assertEquals('slowdive', self.ts.get_username())
|
||||
self.assertEquals(True, self.ts.is_authenticated())
|
||||
self.assertTrue(self.event.isSet())
|
||||
self.assertTrue(self.ts.is_active())
|
||||
self.assertEqual('slowdive', self.ts.get_username())
|
||||
self.assertEqual(True, self.ts.is_authenticated())
|
||||
|
||||
stdin, stdout, stderr = self.tc.exec_command('yes')
|
||||
schan = self.ts.accept(1.0)
|
||||
|
@ -138,10 +139,10 @@ class SSHClientTest (unittest.TestCase):
|
|||
schan.send_stderr('This is on stderr.\n')
|
||||
schan.close()
|
||||
|
||||
self.assertEquals('Hello there.\n', stdout.readline())
|
||||
self.assertEquals('', stdout.readline())
|
||||
self.assertEquals('This is on stderr.\n', stderr.readline())
|
||||
self.assertEquals('', stderr.readline())
|
||||
self.assertEqual('Hello there.\n', stdout.readline())
|
||||
self.assertEqual('', stdout.readline())
|
||||
self.assertEqual('This is on stderr.\n', stderr.readline())
|
||||
self.assertEqual('', stderr.readline())
|
||||
|
||||
stdin.close()
|
||||
stdout.close()
|
||||
|
@ -151,62 +152,103 @@ class SSHClientTest (unittest.TestCase):
|
|||
"""
|
||||
verify that SSHClient accepts and tries multiple key files.
|
||||
"""
|
||||
host_key = paramiko.RSAKey.from_private_key_file('tests/test_rsa.key')
|
||||
public_host_key = paramiko.RSAKey(data=str(host_key))
|
||||
threading.Thread(target=self._run).start()
|
||||
host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key'))
|
||||
public_host_key = paramiko.RSAKey(data=host_key.asbytes())
|
||||
|
||||
self.tc = paramiko.SSHClient()
|
||||
self.tc.get_host_keys().add('[%s]:%d' % (self.addr, self.port), 'ssh-rsa', public_host_key)
|
||||
self.tc.connect(self.addr, self.port, username='slowdive', key_filename=[ 'tests/test_rsa.key', 'tests/test_dss.key' ])
|
||||
self.tc.connect(self.addr, self.port, username='slowdive', key_filename=[test_path('test_rsa.key'), test_path('test_dss.key')])
|
||||
|
||||
self.event.wait(1.0)
|
||||
self.assert_(self.event.isSet())
|
||||
self.assert_(self.ts.is_active())
|
||||
self.assertEquals('slowdive', self.ts.get_username())
|
||||
self.assertEquals(True, self.ts.is_authenticated())
|
||||
self.assertTrue(self.event.isSet())
|
||||
self.assertTrue(self.ts.is_active())
|
||||
self.assertEqual('slowdive', self.ts.get_username())
|
||||
self.assertEqual(True, self.ts.is_authenticated())
|
||||
|
||||
def test_4_auto_add_policy(self):
|
||||
"""
|
||||
verify that SSHClient's AutoAddPolicy works.
|
||||
"""
|
||||
host_key = paramiko.RSAKey.from_private_key_file('tests/test_rsa.key')
|
||||
public_host_key = paramiko.RSAKey(data=str(host_key))
|
||||
threading.Thread(target=self._run).start()
|
||||
host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key'))
|
||||
public_host_key = paramiko.RSAKey(data=host_key.asbytes())
|
||||
|
||||
self.tc = paramiko.SSHClient()
|
||||
self.tc.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
self.assertEquals(0, len(self.tc.get_host_keys()))
|
||||
self.assertEqual(0, len(self.tc.get_host_keys()))
|
||||
self.tc.connect(self.addr, self.port, username='slowdive', password='pygmalion')
|
||||
|
||||
self.event.wait(1.0)
|
||||
self.assert_(self.event.isSet())
|
||||
self.assert_(self.ts.is_active())
|
||||
self.assertEquals('slowdive', self.ts.get_username())
|
||||
self.assertEquals(True, self.ts.is_authenticated())
|
||||
self.assertEquals(1, len(self.tc.get_host_keys()))
|
||||
self.assertEquals(public_host_key, self.tc.get_host_keys()['[%s]:%d' % (self.addr, self.port)]['ssh-rsa'])
|
||||
self.assertTrue(self.event.isSet())
|
||||
self.assertTrue(self.ts.is_active())
|
||||
self.assertEqual('slowdive', self.ts.get_username())
|
||||
self.assertEqual(True, self.ts.is_authenticated())
|
||||
self.assertEqual(1, len(self.tc.get_host_keys()))
|
||||
self.assertEqual(public_host_key, self.tc.get_host_keys()['[%s]:%d' % (self.addr, self.port)]['ssh-rsa'])
|
||||
|
||||
def test_5_cleanup(self):
|
||||
def test_5_save_host_keys(self):
|
||||
"""
|
||||
verify that SSHClient correctly saves a known_hosts file.
|
||||
"""
|
||||
warnings.filterwarnings('ignore', 'tempnam.*')
|
||||
|
||||
host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key'))
|
||||
public_host_key = paramiko.RSAKey(data=host_key.asbytes())
|
||||
fd, localname = mkstemp()
|
||||
os.close(fd)
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
self.assertEquals(0, len(client.get_host_keys()))
|
||||
|
||||
host_id = '[%s]:%d' % (self.addr, self.port)
|
||||
|
||||
client.get_host_keys().add(host_id, 'ssh-rsa', public_host_key)
|
||||
self.assertEquals(1, len(client.get_host_keys()))
|
||||
self.assertEquals(public_host_key, client.get_host_keys()[host_id]['ssh-rsa'])
|
||||
|
||||
client.save_host_keys(localname)
|
||||
|
||||
with open(localname) as fd:
|
||||
assert host_id in fd.read()
|
||||
|
||||
os.unlink(localname)
|
||||
|
||||
def test_6_cleanup(self):
|
||||
"""
|
||||
verify that when an SSHClient is collected, its transport (and the
|
||||
transport's packetizer) is closed.
|
||||
"""
|
||||
host_key = paramiko.RSAKey.from_private_key_file('tests/test_rsa.key')
|
||||
public_host_key = paramiko.RSAKey(data=str(host_key))
|
||||
# Unclear why this is borked on Py3, but it is, and does not seem worth
|
||||
# pursuing at the moment.
|
||||
if not PY2:
|
||||
return
|
||||
threading.Thread(target=self._run).start()
|
||||
host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key'))
|
||||
public_host_key = paramiko.RSAKey(data=host_key.asbytes())
|
||||
|
||||
self.tc = paramiko.SSHClient()
|
||||
self.tc.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
self.assertEquals(0, len(self.tc.get_host_keys()))
|
||||
self.assertEqual(0, len(self.tc.get_host_keys()))
|
||||
self.tc.connect(self.addr, self.port, username='slowdive', password='pygmalion')
|
||||
|
||||
self.event.wait(1.0)
|
||||
self.assert_(self.event.isSet())
|
||||
self.assert_(self.ts.is_active())
|
||||
self.assertTrue(self.event.isSet())
|
||||
self.assertTrue(self.ts.is_active())
|
||||
|
||||
p = weakref.ref(self.tc._transport.packetizer)
|
||||
self.assert_(p() is not None)
|
||||
self.assertTrue(p() is not None)
|
||||
self.tc.close()
|
||||
del self.tc
|
||||
# hrm, sometimes p isn't cleared right away. why is that?
|
||||
st = time.time()
|
||||
while (time.time() - st < 5.0) and (p() is not None):
|
||||
time.sleep(0.1)
|
||||
self.assert_(p() is None)
|
||||
|
||||
# hrm, sometimes p isn't cleared right away. why is that?
|
||||
#st = time.time()
|
||||
#while (time.time() - st < 5.0) and (p() is not None):
|
||||
# time.sleep(0.1)
|
||||
|
||||
# instead of dumbly waiting for the GC to collect, force a collection
|
||||
# to see whether the SSHClient object is deallocated correctly
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
self.assertTrue(p() is None)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIKB6ty3yVyKEnfF/zprx0qwC76MsMlHY4HXCnqho2eKioAoGCCqGSM49
|
||||
AwEHoUQDQgAElI9mbdlaS+T9nHxY/59lFnn80EEecZDBHq4gLpccY8Mge5ZTMiMD
|
||||
ADRvOqQ5R98Sxst765CAqXmRtz8vwoD96g==
|
||||
-----END EC PRIVATE KEY-----
|
|
@ -0,0 +1,8 @@
|
|||
-----BEGIN EC PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-128-CBC,EEB56BC745EDB2DE04FC3FE1F8DA387E
|
||||
|
||||
wdt7QTCa6ahTJLaEPH7NhHyBcxhzrzf93d4UwQOuAhkM6//jKD4lF9fErHBW0f3B
|
||||
ExberCU3UxfEF3xX2thXiLw47JgeOCeQUlqRFx92p36k6YmfNGX6W8CsZ3d+XodF
|
||||
Z+pb6m285CiSX+W95NenFMexXFsIpntiCvTifTKJ8os=
|
||||
-----END EC PRIVATE KEY-----
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -22,6 +22,7 @@ Some unit tests for the BufferedFile abstraction.
|
|||
|
||||
import unittest
|
||||
from paramiko.file import BufferedFile
|
||||
from paramiko.common import linefeed_byte, crlf, cr_byte
|
||||
|
||||
|
||||
class LoopbackFile (BufferedFile):
|
||||
|
@ -31,7 +32,7 @@ class LoopbackFile (BufferedFile):
|
|||
def __init__(self, mode='r', bufsize=-1):
|
||||
BufferedFile.__init__(self)
|
||||
self._set_mode(mode, bufsize)
|
||||
self.buffer = ''
|
||||
self.buffer = bytes()
|
||||
|
||||
def _read(self, size):
|
||||
if len(self.buffer) == 0:
|
||||
|
@ -52,8 +53,8 @@ class BufferedFileTest (unittest.TestCase):
|
|||
def test_1_simple(self):
|
||||
f = LoopbackFile('r')
|
||||
try:
|
||||
f.write('hi')
|
||||
self.assert_(False, 'no exception on write to read-only file')
|
||||
f.write(b'hi')
|
||||
self.assertTrue(False, 'no exception on write to read-only file')
|
||||
except:
|
||||
pass
|
||||
f.close()
|
||||
|
@ -61,14 +62,14 @@ class BufferedFileTest (unittest.TestCase):
|
|||
f = LoopbackFile('w')
|
||||
try:
|
||||
f.read(1)
|
||||
self.assert_(False, 'no exception to read from write-only file')
|
||||
self.assertTrue(False, 'no exception to read from write-only file')
|
||||
except:
|
||||
pass
|
||||
f.close()
|
||||
|
||||
def test_2_readline(self):
|
||||
f = LoopbackFile('r+U')
|
||||
f.write('First line.\nSecond line.\r\nThird line.\nFinal line non-terminated.')
|
||||
f.write(b'First line.\nSecond line.\r\nThird line.\nFinal line non-terminated.')
|
||||
self.assertEqual(f.readline(), 'First line.\n')
|
||||
# universal newline mode should convert this linefeed:
|
||||
self.assertEqual(f.readline(), 'Second line.\n')
|
||||
|
@ -80,31 +81,31 @@ class BufferedFileTest (unittest.TestCase):
|
|||
f.close()
|
||||
try:
|
||||
f.readline()
|
||||
self.assert_(False, 'no exception on readline of closed file')
|
||||
self.assertTrue(False, 'no exception on readline of closed file')
|
||||
except IOError:
|
||||
pass
|
||||
self.assert_('\n' in f.newlines)
|
||||
self.assert_('\r\n' in f.newlines)
|
||||
self.assert_('\r' not in f.newlines)
|
||||
self.assertTrue(linefeed_byte in f.newlines)
|
||||
self.assertTrue(crlf in f.newlines)
|
||||
self.assertTrue(cr_byte not in f.newlines)
|
||||
|
||||
def test_3_lf(self):
|
||||
"""
|
||||
try to trick the linefeed detector.
|
||||
"""
|
||||
f = LoopbackFile('r+U')
|
||||
f.write('First line.\r')
|
||||
f.write(b'First line.\r')
|
||||
self.assertEqual(f.readline(), 'First line.\n')
|
||||
f.write('\nSecond.\r\n')
|
||||
f.write(b'\nSecond.\r\n')
|
||||
self.assertEqual(f.readline(), 'Second.\n')
|
||||
f.close()
|
||||
self.assertEqual(f.newlines, '\r\n')
|
||||
self.assertEqual(f.newlines, crlf)
|
||||
|
||||
def test_4_write(self):
|
||||
"""
|
||||
verify that write buffering is on.
|
||||
"""
|
||||
f = LoopbackFile('r+', 1)
|
||||
f.write('Complete line.\nIncomplete line.')
|
||||
f.write(b'Complete line.\nIncomplete line.')
|
||||
self.assertEqual(f.readline(), 'Complete line.\n')
|
||||
self.assertEqual(f.readline(), '')
|
||||
f.write('..\n')
|
||||
|
@ -117,12 +118,12 @@ class BufferedFileTest (unittest.TestCase):
|
|||
"""
|
||||
f = LoopbackFile('r+', 512)
|
||||
f.write('Not\nquite\n512 bytes.\n')
|
||||
self.assertEqual(f.read(1), '')
|
||||
self.assertEqual(f.read(1), b'')
|
||||
f.flush()
|
||||
self.assertEqual(f.read(5), 'Not\nq')
|
||||
self.assertEqual(f.read(10), 'uite\n512 b')
|
||||
self.assertEqual(f.read(9), 'ytes.\n')
|
||||
self.assertEqual(f.read(3), '')
|
||||
self.assertEqual(f.read(5), b'Not\nq')
|
||||
self.assertEqual(f.read(10), b'uite\n512 b')
|
||||
self.assertEqual(f.read(9), b'ytes.\n')
|
||||
self.assertEqual(f.read(3), b'')
|
||||
f.close()
|
||||
|
||||
def test_6_buffering(self):
|
||||
|
@ -130,12 +131,12 @@ class BufferedFileTest (unittest.TestCase):
|
|||
verify that flushing happens automatically on buffer crossing.
|
||||
"""
|
||||
f = LoopbackFile('r+', 16)
|
||||
f.write('Too small.')
|
||||
self.assertEqual(f.read(4), '')
|
||||
f.write(' ')
|
||||
self.assertEqual(f.read(4), '')
|
||||
f.write('Enough.')
|
||||
self.assertEqual(f.read(20), 'Too small. Enough.')
|
||||
f.write(b'Too small.')
|
||||
self.assertEqual(f.read(4), b'')
|
||||
f.write(b' ')
|
||||
self.assertEqual(f.read(4), b'')
|
||||
f.write(b'Enough.')
|
||||
self.assertEqual(f.read(20), b'Too small. Enough.')
|
||||
f.close()
|
||||
|
||||
def test_7_read_all(self):
|
||||
|
@ -143,9 +144,14 @@ class BufferedFileTest (unittest.TestCase):
|
|||
verify that read(-1) returns everything left in the file.
|
||||
"""
|
||||
f = LoopbackFile('r+', 16)
|
||||
f.write('The first thing you need to do is open your eyes. ')
|
||||
f.write('Then, you need to close them again.\n')
|
||||
f.write(b'The first thing you need to do is open your eyes. ')
|
||||
f.write(b'Then, you need to close them again.\n')
|
||||
s = f.read(-1)
|
||||
self.assertEqual(s, 'The first thing you need to do is open your eyes. Then, you ' +
|
||||
'need to close them again.\n')
|
||||
self.assertEqual(s, b'The first thing you need to do is open your eyes. Then, you ' +
|
||||
b'need to close them again.\n')
|
||||
f.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
from unittest import main
|
||||
main()
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -20,11 +20,11 @@
|
|||
Some unit tests for HostKeys.
|
||||
"""
|
||||
|
||||
import base64
|
||||
from binascii import hexlify
|
||||
import os
|
||||
import unittest
|
||||
import paramiko
|
||||
from paramiko.py3compat import decodebytes
|
||||
|
||||
|
||||
test_hosts_file = """\
|
||||
|
@ -36,12 +36,12 @@ BGQ3GQ/Fc7SX6gkpXkwcZryoi4kNFhHu5LvHcZPdxXV1D+uTMfGS1eyd2Yz/DoNWXNAl8TI0cAsW\
|
|||
5ymME3bQ4J/k1IKxCtz/bAlAqFgKoc+EolMziDYqWIATtW0rYTJvzGAzTmMj80/QpsFH+Pc2M=
|
||||
"""
|
||||
|
||||
keyblob = """\
|
||||
keyblob = b"""\
|
||||
AAAAB3NzaC1yc2EAAAABIwAAAIEA8bP1ZA7DCZDB9J0s50l31MBGQ3GQ/Fc7SX6gkpXkwcZryoi4k\
|
||||
NFhHu5LvHcZPdxXV1D+uTMfGS1eyd2Yz/DoNWXNAl8TI0cAsW5ymME3bQ4J/k1IKxCtz/bAlAqFgK\
|
||||
oc+EolMziDYqWIATtW0rYTJvzGAzTmMj80/QpsFH+Pc2M="""
|
||||
|
||||
keyblob_dss = """\
|
||||
keyblob_dss = b"""\
|
||||
AAAAB3NzaC1kc3MAAACBAOeBpgNnfRzr/twmAQRu2XwWAp3CFtrVnug6s6fgwj/oLjYbVtjAy6pl/\
|
||||
h0EKCWx2rf1IetyNsTxWrniA9I6HeDj65X1FyDkg6g8tvCnaNB8Xp/UUhuzHuGsMIipRxBxw9LF60\
|
||||
8EqZcj1E3ytktoW5B5OcjrkEoz3xG7C+rpIjYvAAAAFQDwz4UnmsGiSNu5iqjn3uTzwUpshwAAAIE\
|
||||
|
@ -55,51 +55,50 @@ Ngw3qIch/WgRmMHy4kBq1SsXMjQCte1So6HBMvBPIW5SiMTmjCfZZiw4AYHK+B/JaOwaG9yRg2Ejg\
|
|||
class HostKeysTest (unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
f = open('hostfile.temp', 'w')
|
||||
with open('hostfile.temp', 'w') as f:
|
||||
f.write(test_hosts_file)
|
||||
f.close()
|
||||
|
||||
def tearDown(self):
|
||||
os.unlink('hostfile.temp')
|
||||
|
||||
def test_1_load(self):
|
||||
hostdict = paramiko.HostKeys('hostfile.temp')
|
||||
self.assertEquals(2, len(hostdict))
|
||||
self.assertEquals(1, len(hostdict.values()[0]))
|
||||
self.assertEquals(1, len(hostdict.values()[1]))
|
||||
self.assertEqual(2, len(hostdict))
|
||||
self.assertEqual(1, len(list(hostdict.values())[0]))
|
||||
self.assertEqual(1, len(list(hostdict.values())[1]))
|
||||
fp = hexlify(hostdict['secure.example.com']['ssh-rsa'].get_fingerprint()).upper()
|
||||
self.assertEquals('E6684DB30E109B67B70FF1DC5C7F1363', fp)
|
||||
self.assertEqual(b'E6684DB30E109B67B70FF1DC5C7F1363', fp)
|
||||
|
||||
def test_2_add(self):
|
||||
hostdict = paramiko.HostKeys('hostfile.temp')
|
||||
hh = '|1|BMsIC6cUIP2zBuXR3t2LRcJYjzM=|hpkJMysjTk/+zzUUzxQEa2ieq6c='
|
||||
key = paramiko.RSAKey(data=base64.decodestring(keyblob))
|
||||
key = paramiko.RSAKey(data=decodebytes(keyblob))
|
||||
hostdict.add(hh, 'ssh-rsa', key)
|
||||
self.assertEquals(3, len(hostdict))
|
||||
self.assertEqual(3, len(list(hostdict)))
|
||||
x = hostdict['foo.example.com']
|
||||
fp = hexlify(x['ssh-rsa'].get_fingerprint()).upper()
|
||||
self.assertEquals('7EC91BB336CB6D810B124B1353C32396', fp)
|
||||
self.assert_(hostdict.check('foo.example.com', key))
|
||||
self.assertEqual(b'7EC91BB336CB6D810B124B1353C32396', fp)
|
||||
self.assertTrue(hostdict.check('foo.example.com', key))
|
||||
|
||||
def test_3_dict(self):
|
||||
hostdict = paramiko.HostKeys('hostfile.temp')
|
||||
self.assert_('secure.example.com' in hostdict)
|
||||
self.assert_('not.example.com' not in hostdict)
|
||||
self.assert_(hostdict.has_key('secure.example.com'))
|
||||
self.assert_(not hostdict.has_key('not.example.com'))
|
||||
self.assertTrue('secure.example.com' in hostdict)
|
||||
self.assertTrue('not.example.com' not in hostdict)
|
||||
self.assertTrue('secure.example.com' in hostdict)
|
||||
self.assertTrue('not.example.com' not in hostdict)
|
||||
x = hostdict.get('secure.example.com', None)
|
||||
self.assert_(x is not None)
|
||||
self.assertTrue(x is not None)
|
||||
fp = hexlify(x['ssh-rsa'].get_fingerprint()).upper()
|
||||
self.assertEquals('E6684DB30E109B67B70FF1DC5C7F1363', fp)
|
||||
self.assertEqual(b'E6684DB30E109B67B70FF1DC5C7F1363', fp)
|
||||
i = 0
|
||||
for key in hostdict:
|
||||
i += 1
|
||||
self.assertEquals(2, i)
|
||||
self.assertEqual(2, i)
|
||||
|
||||
def test_4_dict_set(self):
|
||||
hostdict = paramiko.HostKeys('hostfile.temp')
|
||||
key = paramiko.RSAKey(data=base64.decodestring(keyblob))
|
||||
key_dss = paramiko.DSSKey(data=base64.decodestring(keyblob_dss))
|
||||
key = paramiko.RSAKey(data=decodebytes(keyblob))
|
||||
key_dss = paramiko.DSSKey(data=decodebytes(keyblob_dss))
|
||||
hostdict['secure.example.com'] = {
|
||||
'ssh-rsa': key,
|
||||
'ssh-dss': key_dss
|
||||
|
@ -107,11 +106,11 @@ class HostKeysTest (unittest.TestCase):
|
|||
hostdict['fake.example.com'] = {}
|
||||
hostdict['fake.example.com']['ssh-rsa'] = key
|
||||
|
||||
self.assertEquals(3, len(hostdict))
|
||||
self.assertEquals(2, len(hostdict.values()[0]))
|
||||
self.assertEquals(1, len(hostdict.values()[1]))
|
||||
self.assertEquals(1, len(hostdict.values()[2]))
|
||||
self.assertEqual(3, len(hostdict))
|
||||
self.assertEqual(2, len(list(hostdict.values())[0]))
|
||||
self.assertEqual(1, len(list(hostdict.values())[1]))
|
||||
self.assertEqual(1, len(list(hostdict.values())[2]))
|
||||
fp = hexlify(hostdict['secure.example.com']['ssh-rsa'].get_fingerprint()).upper()
|
||||
self.assertEquals('7EC91BB336CB6D810B124B1353C32396', fp)
|
||||
self.assertEqual(b'7EC91BB336CB6D810B124B1353C32396', fp)
|
||||
fp = hexlify(hostdict['secure.example.com']['ssh-dss'].get_fingerprint()).upper()
|
||||
self.assertEquals('4478F0B9A23CC5182009FF755BC1D26C', fp)
|
||||
self.assertEqual(b'4478F0B9A23CC5182009FF755BC1D26C', fp)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -21,34 +21,40 @@ Some unit tests for the key exchange protocols.
|
|||
"""
|
||||
|
||||
from binascii import hexlify
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import paramiko.util
|
||||
from paramiko.kex_group1 import KexGroup1
|
||||
from paramiko.kex_gex import KexGex
|
||||
from paramiko import Message
|
||||
from paramiko.common import byte_chr
|
||||
|
||||
|
||||
class FakeRng (object):
|
||||
def read(self, n):
|
||||
return chr(0xcc) * n
|
||||
def dummy_urandom(n):
|
||||
return byte_chr(0xcc) * n
|
||||
|
||||
|
||||
class FakeKey (object):
|
||||
def __str__(self):
|
||||
return 'fake-key'
|
||||
def sign_ssh_data(self, rng, H):
|
||||
return 'fake-sig'
|
||||
|
||||
def asbytes(self):
|
||||
return b'fake-key'
|
||||
|
||||
def sign_ssh_data(self, H):
|
||||
return b'fake-sig'
|
||||
|
||||
|
||||
class FakeModulusPack (object):
|
||||
P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFFL
|
||||
P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF
|
||||
G = 2
|
||||
|
||||
def get_modulus(self, min, ask, max):
|
||||
return self.G, self.P
|
||||
|
||||
|
||||
class FakeTransport(object):
|
||||
rng = FakeRng()
|
||||
local_version = 'SSH-2.0-paramiko_1.0'
|
||||
remote_version = 'SSH-2.0-lame'
|
||||
local_kex_init = 'local-kex-init'
|
||||
|
@ -56,41 +62,49 @@ class FakeTransport (object):
|
|||
|
||||
def _send_message(self, m):
|
||||
self._message = m
|
||||
|
||||
def _expect_packet(self, *t):
|
||||
self._expect = t
|
||||
|
||||
def _set_K_H(self, K, H):
|
||||
self._K = K
|
||||
self._H = H
|
||||
|
||||
def _verify_key(self, host_key, sig):
|
||||
self._verify = (host_key, sig)
|
||||
|
||||
def _activate_outbound(self):
|
||||
self._activated = True
|
||||
|
||||
def _log(self, level, s):
|
||||
pass
|
||||
|
||||
def get_server_key(self):
|
||||
return FakeKey()
|
||||
|
||||
def _get_modulus_pack(self):
|
||||
return FakeModulusPack()
|
||||
|
||||
|
||||
class KexTest (unittest.TestCase):
|
||||
|
||||
K = 14730343317708716439807310032871972459448364195094179797249681733965528989482751523943515690110179031004049109375612685505881911274101441415545039654102474376472240501616988799699744135291070488314748284283496055223852115360852283821334858541043710301057312858051901453919067023103730011648890038847384890504L
|
||||
K = 14730343317708716439807310032871972459448364195094179797249681733965528989482751523943515690110179031004049109375612685505881911274101441415545039654102474376472240501616988799699744135291070488314748284283496055223852115360852283821334858541043710301057312858051901453919067023103730011648890038847384890504
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
self._original_urandom = os.urandom
|
||||
os.urandom = dummy_urandom
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
os.urandom = self._original_urandom
|
||||
|
||||
def test_1_group1_client(self):
|
||||
transport = FakeTransport()
|
||||
transport.server_mode = False
|
||||
kex = KexGroup1(transport)
|
||||
kex.start_kex()
|
||||
x = '1E000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D4'
|
||||
self.assertEquals(x, hexlify(str(transport._message)).upper())
|
||||
self.assertEquals((paramiko.kex_group1._MSG_KEXDH_REPLY,), transport._expect)
|
||||
x = b'1E000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D4'
|
||||
self.assertEqual(x, hexlify(transport._message.asbytes()).upper())
|
||||
self.assertEqual((paramiko.kex_group1._MSG_KEXDH_REPLY,), transport._expect)
|
||||
|
||||
# fake "reply"
|
||||
msg = Message()
|
||||
|
@ -99,47 +113,47 @@ class KexTest (unittest.TestCase):
|
|||
msg.add_string('fake-sig')
|
||||
msg.rewind()
|
||||
kex.parse_next(paramiko.kex_group1._MSG_KEXDH_REPLY, msg)
|
||||
H = '03079780F3D3AD0B3C6DB30C8D21685F367A86D2'
|
||||
self.assertEquals(self.K, transport._K)
|
||||
self.assertEquals(H, hexlify(transport._H).upper())
|
||||
self.assertEquals(('fake-host-key', 'fake-sig'), transport._verify)
|
||||
self.assert_(transport._activated)
|
||||
H = b'03079780F3D3AD0B3C6DB30C8D21685F367A86D2'
|
||||
self.assertEqual(self.K, transport._K)
|
||||
self.assertEqual(H, hexlify(transport._H).upper())
|
||||
self.assertEqual((b'fake-host-key', b'fake-sig'), transport._verify)
|
||||
self.assertTrue(transport._activated)
|
||||
|
||||
def test_2_group1_server(self):
|
||||
transport = FakeTransport()
|
||||
transport.server_mode = True
|
||||
kex = KexGroup1(transport)
|
||||
kex.start_kex()
|
||||
self.assertEquals((paramiko.kex_group1._MSG_KEXDH_INIT,), transport._expect)
|
||||
self.assertEqual((paramiko.kex_group1._MSG_KEXDH_INIT,), transport._expect)
|
||||
|
||||
msg = Message()
|
||||
msg.add_mpint(69)
|
||||
msg.rewind()
|
||||
kex.parse_next(paramiko.kex_group1._MSG_KEXDH_INIT, msg)
|
||||
H = 'B16BF34DD10945EDE84E9C1EF24A14BFDC843389'
|
||||
x = '1F0000000866616B652D6B6579000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D40000000866616B652D736967'
|
||||
self.assertEquals(self.K, transport._K)
|
||||
self.assertEquals(H, hexlify(transport._H).upper())
|
||||
self.assertEquals(x, hexlify(str(transport._message)).upper())
|
||||
self.assert_(transport._activated)
|
||||
H = b'B16BF34DD10945EDE84E9C1EF24A14BFDC843389'
|
||||
x = b'1F0000000866616B652D6B6579000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D40000000866616B652D736967'
|
||||
self.assertEqual(self.K, transport._K)
|
||||
self.assertEqual(H, hexlify(transport._H).upper())
|
||||
self.assertEqual(x, hexlify(transport._message.asbytes()).upper())
|
||||
self.assertTrue(transport._activated)
|
||||
|
||||
def test_3_gex_client(self):
|
||||
transport = FakeTransport()
|
||||
transport.server_mode = False
|
||||
kex = KexGex(transport)
|
||||
kex.start_kex()
|
||||
x = '22000004000000080000002000'
|
||||
self.assertEquals(x, hexlify(str(transport._message)).upper())
|
||||
self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_GROUP,), transport._expect)
|
||||
x = b'22000004000000080000002000'
|
||||
self.assertEqual(x, hexlify(transport._message.asbytes()).upper())
|
||||
self.assertEqual((paramiko.kex_gex._MSG_KEXDH_GEX_GROUP,), transport._expect)
|
||||
|
||||
msg = Message()
|
||||
msg.add_mpint(FakeModulusPack.P)
|
||||
msg.add_mpint(FakeModulusPack.G)
|
||||
msg.rewind()
|
||||
kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_GROUP, msg)
|
||||
x = '20000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D4'
|
||||
self.assertEquals(x, hexlify(str(transport._message)).upper())
|
||||
self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_REPLY,), transport._expect)
|
||||
x = b'20000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D4'
|
||||
self.assertEqual(x, hexlify(transport._message.asbytes()).upper())
|
||||
self.assertEqual((paramiko.kex_gex._MSG_KEXDH_GEX_REPLY,), transport._expect)
|
||||
|
||||
msg = Message()
|
||||
msg.add_string('fake-host-key')
|
||||
|
@ -147,29 +161,29 @@ class KexTest (unittest.TestCase):
|
|||
msg.add_string('fake-sig')
|
||||
msg.rewind()
|
||||
kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_REPLY, msg)
|
||||
H = 'A265563F2FA87F1A89BF007EE90D58BE2E4A4BD0'
|
||||
self.assertEquals(self.K, transport._K)
|
||||
self.assertEquals(H, hexlify(transport._H).upper())
|
||||
self.assertEquals(('fake-host-key', 'fake-sig'), transport._verify)
|
||||
self.assert_(transport._activated)
|
||||
H = b'A265563F2FA87F1A89BF007EE90D58BE2E4A4BD0'
|
||||
self.assertEqual(self.K, transport._K)
|
||||
self.assertEqual(H, hexlify(transport._H).upper())
|
||||
self.assertEqual((b'fake-host-key', b'fake-sig'), transport._verify)
|
||||
self.assertTrue(transport._activated)
|
||||
|
||||
def test_4_gex_old_client(self):
|
||||
transport = FakeTransport()
|
||||
transport.server_mode = False
|
||||
kex = KexGex(transport)
|
||||
kex.start_kex(_test_old_style=True)
|
||||
x = '1E00000800'
|
||||
self.assertEquals(x, hexlify(str(transport._message)).upper())
|
||||
self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_GROUP,), transport._expect)
|
||||
x = b'1E00000800'
|
||||
self.assertEqual(x, hexlify(transport._message.asbytes()).upper())
|
||||
self.assertEqual((paramiko.kex_gex._MSG_KEXDH_GEX_GROUP,), transport._expect)
|
||||
|
||||
msg = Message()
|
||||
msg.add_mpint(FakeModulusPack.P)
|
||||
msg.add_mpint(FakeModulusPack.G)
|
||||
msg.rewind()
|
||||
kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_GROUP, msg)
|
||||
x = '20000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D4'
|
||||
self.assertEquals(x, hexlify(str(transport._message)).upper())
|
||||
self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_REPLY,), transport._expect)
|
||||
x = b'20000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D4'
|
||||
self.assertEqual(x, hexlify(transport._message.asbytes()).upper())
|
||||
self.assertEqual((paramiko.kex_gex._MSG_KEXDH_GEX_REPLY,), transport._expect)
|
||||
|
||||
msg = Message()
|
||||
msg.add_string('fake-host-key')
|
||||
|
@ -177,18 +191,18 @@ class KexTest (unittest.TestCase):
|
|||
msg.add_string('fake-sig')
|
||||
msg.rewind()
|
||||
kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_REPLY, msg)
|
||||
H = '807F87B269EF7AC5EC7E75676808776A27D5864C'
|
||||
self.assertEquals(self.K, transport._K)
|
||||
self.assertEquals(H, hexlify(transport._H).upper())
|
||||
self.assertEquals(('fake-host-key', 'fake-sig'), transport._verify)
|
||||
self.assert_(transport._activated)
|
||||
H = b'807F87B269EF7AC5EC7E75676808776A27D5864C'
|
||||
self.assertEqual(self.K, transport._K)
|
||||
self.assertEqual(H, hexlify(transport._H).upper())
|
||||
self.assertEqual((b'fake-host-key', b'fake-sig'), transport._verify)
|
||||
self.assertTrue(transport._activated)
|
||||
|
||||
def test_5_gex_server(self):
|
||||
transport = FakeTransport()
|
||||
transport.server_mode = True
|
||||
kex = KexGex(transport)
|
||||
kex.start_kex()
|
||||
self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST, paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST_OLD), transport._expect)
|
||||
self.assertEqual((paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST, paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST_OLD), transport._expect)
|
||||
|
||||
msg = Message()
|
||||
msg.add_int(1024)
|
||||
|
@ -196,45 +210,45 @@ class KexTest (unittest.TestCase):
|
|||
msg.add_int(4096)
|
||||
msg.rewind()
|
||||
kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST, msg)
|
||||
x = '1F0000008100FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF0000000102'
|
||||
self.assertEquals(x, hexlify(str(transport._message)).upper())
|
||||
self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_INIT,), transport._expect)
|
||||
x = b'1F0000008100FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF0000000102'
|
||||
self.assertEqual(x, hexlify(transport._message.asbytes()).upper())
|
||||
self.assertEqual((paramiko.kex_gex._MSG_KEXDH_GEX_INIT,), transport._expect)
|
||||
|
||||
msg = Message()
|
||||
msg.add_mpint(12345)
|
||||
msg.rewind()
|
||||
kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_INIT, msg)
|
||||
K = 67592995013596137876033460028393339951879041140378510871612128162185209509220726296697886624612526735888348020498716482757677848959420073720160491114319163078862905400020959196386947926388406687288901564192071077389283980347784184487280885335302632305026248574716290537036069329724382811853044654824945750581L
|
||||
H = 'CE754197C21BF3452863B4F44D0B3951F12516EF'
|
||||
x = '210000000866616B652D6B6579000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D40000000866616B652D736967'
|
||||
self.assertEquals(K, transport._K)
|
||||
self.assertEquals(H, hexlify(transport._H).upper())
|
||||
self.assertEquals(x, hexlify(str(transport._message)).upper())
|
||||
self.assert_(transport._activated)
|
||||
K = 67592995013596137876033460028393339951879041140378510871612128162185209509220726296697886624612526735888348020498716482757677848959420073720160491114319163078862905400020959196386947926388406687288901564192071077389283980347784184487280885335302632305026248574716290537036069329724382811853044654824945750581
|
||||
H = b'CE754197C21BF3452863B4F44D0B3951F12516EF'
|
||||
x = b'210000000866616B652D6B6579000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D40000000866616B652D736967'
|
||||
self.assertEqual(K, transport._K)
|
||||
self.assertEqual(H, hexlify(transport._H).upper())
|
||||
self.assertEqual(x, hexlify(transport._message.asbytes()).upper())
|
||||
self.assertTrue(transport._activated)
|
||||
|
||||
def test_6_gex_server_with_old_client(self):
|
||||
transport = FakeTransport()
|
||||
transport.server_mode = True
|
||||
kex = KexGex(transport)
|
||||
kex.start_kex()
|
||||
self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST, paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST_OLD), transport._expect)
|
||||
self.assertEqual((paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST, paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST_OLD), transport._expect)
|
||||
|
||||
msg = Message()
|
||||
msg.add_int(2048)
|
||||
msg.rewind()
|
||||
kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST_OLD, msg)
|
||||
x = '1F0000008100FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF0000000102'
|
||||
self.assertEquals(x, hexlify(str(transport._message)).upper())
|
||||
self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_INIT,), transport._expect)
|
||||
x = b'1F0000008100FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF0000000102'
|
||||
self.assertEqual(x, hexlify(transport._message.asbytes()).upper())
|
||||
self.assertEqual((paramiko.kex_gex._MSG_KEXDH_GEX_INIT,), transport._expect)
|
||||
|
||||
msg = Message()
|
||||
msg.add_mpint(12345)
|
||||
msg.rewind()
|
||||
kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_INIT, msg)
|
||||
K = 67592995013596137876033460028393339951879041140378510871612128162185209509220726296697886624612526735888348020498716482757677848959420073720160491114319163078862905400020959196386947926388406687288901564192071077389283980347784184487280885335302632305026248574716290537036069329724382811853044654824945750581L
|
||||
H = 'B41A06B2E59043CEFC1AE16EC31F1E2D12EC455B'
|
||||
x = '210000000866616B652D6B6579000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D40000000866616B652D736967'
|
||||
self.assertEquals(K, transport._K)
|
||||
self.assertEquals(H, hexlify(transport._H).upper())
|
||||
self.assertEquals(x, hexlify(str(transport._message)).upper())
|
||||
self.assert_(transport._activated)
|
||||
K = 67592995013596137876033460028393339951879041140378510871612128162185209509220726296697886624612526735888348020498716482757677848959420073720160491114319163078862905400020959196386947926388406687288901564192071077389283980347784184487280885335302632305026248574716290537036069329724382811853044654824945750581
|
||||
H = b'B41A06B2E59043CEFC1AE16EC31F1E2D12EC455B'
|
||||
x = b'210000000866616B652D6B6579000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D40000000866616B652D736967'
|
||||
self.assertEqual(K, transport._K)
|
||||
self.assertEqual(H, hexlify(transport._H).upper())
|
||||
self.assertEqual(x, hexlify(transport._message.asbytes()).upper())
|
||||
self.assertTrue(transport._activated)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -22,14 +22,15 @@ Some unit tests for ssh protocol message blocks.
|
|||
|
||||
import unittest
|
||||
from paramiko.message import Message
|
||||
from paramiko.common import byte_chr, zero_byte
|
||||
|
||||
|
||||
class MessageTest (unittest.TestCase):
|
||||
|
||||
__a = '\x00\x00\x00\x17\x07\x60\xe0\x90\x00\x00\x00\x01q\x00\x00\x00\x05hello\x00\x00\x03\xe8' + ('x' * 1000)
|
||||
__b = '\x01\x00\xf3\x00\x3f\x00\x00\x00\x10huey,dewey,louie'
|
||||
__c = '\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\xf5\xe4\xd3\xc2\xb1\x09\x00\x00\x00\x01\x11\x00\x00\x00\x07\x00\xf5\xe4\xd3\xc2\xb1\x09\x00\x00\x00\x06\x9a\x1b\x2c\x3d\x4e\xf7'
|
||||
__d = '\x00\x00\x00\x05\x00\x00\x00\x05\x11\x22\x33\x44\x55\x01\x00\x00\x00\x03cat\x00\x00\x00\x03a,b'
|
||||
__a = b'\x00\x00\x00\x17\x07\x60\xe0\x90\x00\x00\x00\x01\x71\x00\x00\x00\x05\x68\x65\x6c\x6c\x6f\x00\x00\x03\xe8' + b'x' * 1000
|
||||
__b = b'\x01\x00\xf3\x00\x3f\x00\x00\x00\x10\x68\x75\x65\x79\x2c\x64\x65\x77\x65\x79\x2c\x6c\x6f\x75\x69\x65'
|
||||
__c = b'\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\xf5\xe4\xd3\xc2\xb1\x09\x00\x00\x00\x01\x11\x00\x00\x00\x07\x00\xf5\xe4\xd3\xc2\xb1\x09\x00\x00\x00\x06\x9a\x1b\x2c\x3d\x4e\xf7'
|
||||
__d = b'\x00\x00\x00\x05\xff\x00\x00\x00\x05\x11\x22\x33\x44\x55\xff\x00\x00\x00\x0a\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x03\x63\x61\x74\x00\x00\x00\x03\x61\x2c\x62'
|
||||
|
||||
def test_1_encode(self):
|
||||
msg = Message()
|
||||
|
@ -38,63 +39,65 @@ class MessageTest (unittest.TestCase):
|
|||
msg.add_string('q')
|
||||
msg.add_string('hello')
|
||||
msg.add_string('x' * 1000)
|
||||
self.assertEquals(str(msg), self.__a)
|
||||
self.assertEqual(msg.asbytes(), self.__a)
|
||||
|
||||
msg = Message()
|
||||
msg.add_boolean(True)
|
||||
msg.add_boolean(False)
|
||||
msg.add_byte('\xf3')
|
||||
msg.add_bytes('\x00\x3f')
|
||||
msg.add_byte(byte_chr(0xf3))
|
||||
|
||||
msg.add_bytes(zero_byte + byte_chr(0x3f))
|
||||
msg.add_list(['huey', 'dewey', 'louie'])
|
||||
self.assertEquals(str(msg), self.__b)
|
||||
self.assertEqual(msg.asbytes(), self.__b)
|
||||
|
||||
msg = Message()
|
||||
msg.add_int64(5)
|
||||
msg.add_int64(0xf5e4d3c2b109L)
|
||||
msg.add_int64(0xf5e4d3c2b109)
|
||||
msg.add_mpint(17)
|
||||
msg.add_mpint(0xf5e4d3c2b109L)
|
||||
msg.add_mpint(-0x65e4d3c2b109L)
|
||||
self.assertEquals(str(msg), self.__c)
|
||||
msg.add_mpint(0xf5e4d3c2b109)
|
||||
msg.add_mpint(-0x65e4d3c2b109)
|
||||
self.assertEqual(msg.asbytes(), self.__c)
|
||||
|
||||
def test_2_decode(self):
|
||||
msg = Message(self.__a)
|
||||
self.assertEquals(msg.get_int(), 23)
|
||||
self.assertEquals(msg.get_int(), 123789456)
|
||||
self.assertEquals(msg.get_string(), 'q')
|
||||
self.assertEquals(msg.get_string(), 'hello')
|
||||
self.assertEquals(msg.get_string(), 'x' * 1000)
|
||||
self.assertEqual(msg.get_int(), 23)
|
||||
self.assertEqual(msg.get_int(), 123789456)
|
||||
self.assertEqual(msg.get_text(), 'q')
|
||||
self.assertEqual(msg.get_text(), 'hello')
|
||||
self.assertEqual(msg.get_text(), 'x' * 1000)
|
||||
|
||||
msg = Message(self.__b)
|
||||
self.assertEquals(msg.get_boolean(), True)
|
||||
self.assertEquals(msg.get_boolean(), False)
|
||||
self.assertEquals(msg.get_byte(), '\xf3')
|
||||
self.assertEquals(msg.get_bytes(2), '\x00\x3f')
|
||||
self.assertEquals(msg.get_list(), ['huey', 'dewey', 'louie'])
|
||||
self.assertEqual(msg.get_boolean(), True)
|
||||
self.assertEqual(msg.get_boolean(), False)
|
||||
self.assertEqual(msg.get_byte(), byte_chr(0xf3))
|
||||
self.assertEqual(msg.get_bytes(2), zero_byte + byte_chr(0x3f))
|
||||
self.assertEqual(msg.get_list(), ['huey', 'dewey', 'louie'])
|
||||
|
||||
msg = Message(self.__c)
|
||||
self.assertEquals(msg.get_int64(), 5)
|
||||
self.assertEquals(msg.get_int64(), 0xf5e4d3c2b109L)
|
||||
self.assertEquals(msg.get_mpint(), 17)
|
||||
self.assertEquals(msg.get_mpint(), 0xf5e4d3c2b109L)
|
||||
self.assertEquals(msg.get_mpint(), -0x65e4d3c2b109L)
|
||||
self.assertEqual(msg.get_int64(), 5)
|
||||
self.assertEqual(msg.get_int64(), 0xf5e4d3c2b109)
|
||||
self.assertEqual(msg.get_mpint(), 17)
|
||||
self.assertEqual(msg.get_mpint(), 0xf5e4d3c2b109)
|
||||
self.assertEqual(msg.get_mpint(), -0x65e4d3c2b109)
|
||||
|
||||
def test_3_add(self):
|
||||
msg = Message()
|
||||
msg.add(5)
|
||||
msg.add(0x1122334455L)
|
||||
msg.add(0x1122334455)
|
||||
msg.add(0xf00000000000000000)
|
||||
msg.add(True)
|
||||
msg.add('cat')
|
||||
msg.add(['a', 'b'])
|
||||
self.assertEquals(str(msg), self.__d)
|
||||
self.assertEqual(msg.asbytes(), self.__d)
|
||||
|
||||
def test_4_misc(self):
|
||||
msg = Message(self.__d)
|
||||
self.assertEquals(msg.get_int(), 5)
|
||||
self.assertEquals(msg.get_mpint(), 0x1122334455L)
|
||||
self.assertEquals(msg.get_so_far(), self.__d[:13])
|
||||
self.assertEquals(msg.get_remainder(), self.__d[13:])
|
||||
self.assertEqual(msg.get_int(), 5)
|
||||
self.assertEqual(msg.get_int(), 0x1122334455)
|
||||
self.assertEqual(msg.get_int(), 0xf00000000000000000)
|
||||
self.assertEqual(msg.get_so_far(), self.__d[:29])
|
||||
self.assertEqual(msg.get_remainder(), self.__d[29:])
|
||||
msg.rewind()
|
||||
self.assertEquals(msg.get_int(), 5)
|
||||
self.assertEquals(msg.get_so_far(), self.__d[:4])
|
||||
self.assertEquals(msg.get_remainder(), self.__d[4:])
|
||||
|
||||
self.assertEqual(msg.get_int(), 5)
|
||||
self.assertEqual(msg.get_so_far(), self.__d[:4])
|
||||
self.assertEqual(msg.get_remainder(), self.__d[4:])
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -21,10 +21,18 @@ Some unit tests for the ssh2 protocol in Transport.
|
|||
"""
|
||||
|
||||
import unittest
|
||||
from loop import LoopSocket
|
||||
from hashlib import sha1
|
||||
|
||||
from tests.loop import LoopSocket
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Hash import SHA, HMAC
|
||||
|
||||
from paramiko import Message, Packetizer, util
|
||||
from paramiko.common import byte_chr, zero_byte
|
||||
|
||||
x55 = byte_chr(0x55)
|
||||
x1f = byte_chr(0x1f)
|
||||
|
||||
|
||||
class PacketizerTest (unittest.TestCase):
|
||||
|
||||
|
@ -35,21 +43,21 @@ class PacketizerTest (unittest.TestCase):
|
|||
p = Packetizer(wsock)
|
||||
p.set_log(util.get_logger('paramiko.transport'))
|
||||
p.set_hexdump(True)
|
||||
cipher = AES.new('\x00' * 16, AES.MODE_CBC, '\x55' * 16)
|
||||
p.set_outbound_cipher(cipher, 16, SHA, 12, '\x1f' * 20)
|
||||
cipher = AES.new(zero_byte * 16, AES.MODE_CBC, x55 * 16)
|
||||
p.set_outbound_cipher(cipher, 16, sha1, 12, x1f * 20)
|
||||
|
||||
# message has to be at least 16 bytes long, so we'll have at least one
|
||||
# block of data encrypted that contains zero random padding bytes
|
||||
m = Message()
|
||||
m.add_byte(chr(100))
|
||||
m.add_byte(byte_chr(100))
|
||||
m.add_int(100)
|
||||
m.add_int(1)
|
||||
m.add_int(900)
|
||||
p.send_message(m)
|
||||
data = rsock.recv(100)
|
||||
# 32 + 12 bytes of MAC = 44
|
||||
self.assertEquals(44, len(data))
|
||||
self.assertEquals('\x43\x91\x97\xbd\x5b\x50\xac\x25\x87\xc2\xc4\x6b\xc7\xe9\x38\xc0', data[:16])
|
||||
self.assertEqual(44, len(data))
|
||||
self.assertEqual(b'\x43\x91\x97\xbd\x5b\x50\xac\x25\x87\xc2\xc4\x6b\xc7\xe9\x38\xc0', data[:16])
|
||||
|
||||
def test_2_read(self):
|
||||
rsock = LoopSocket()
|
||||
|
@ -58,13 +66,11 @@ class PacketizerTest (unittest.TestCase):
|
|||
p = Packetizer(rsock)
|
||||
p.set_log(util.get_logger('paramiko.transport'))
|
||||
p.set_hexdump(True)
|
||||
cipher = AES.new('\x00' * 16, AES.MODE_CBC, '\x55' * 16)
|
||||
p.set_inbound_cipher(cipher, 16, SHA, 12, '\x1f' * 20)
|
||||
|
||||
wsock.send('C\x91\x97\xbd[P\xac%\x87\xc2\xc4k\xc7\xe98\xc0' + \
|
||||
'\x90\xd2\x16V\rqsa8|L=\xfb\x97}\xe2n\x03\xb1\xa0\xc2\x1c\xd6AAL\xb4Y')
|
||||
cipher = AES.new(zero_byte * 16, AES.MODE_CBC, x55 * 16)
|
||||
p.set_inbound_cipher(cipher, 16, sha1, 12, x1f * 20)
|
||||
wsock.send(b'\x43\x91\x97\xbd\x5b\x50\xac\x25\x87\xc2\xc4\x6b\xc7\xe9\x38\xc0\x90\xd2\x16\x56\x0d\x71\x73\x61\x38\x7c\x4c\x3d\xfb\x97\x7d\xe2\x6e\x03\xb1\xa0\xc2\x1c\xd6\x41\x41\x4c\xb4\x59')
|
||||
cmd, m = p.read_message()
|
||||
self.assertEquals(100, cmd)
|
||||
self.assertEquals(100, m.get_int())
|
||||
self.assertEquals(1, m.get_int())
|
||||
self.assertEquals(900, m.get_int())
|
||||
self.assertEqual(100, cmd)
|
||||
self.assertEqual(100, m.get_int())
|
||||
self.assertEqual(1, m.get_int())
|
||||
self.assertEqual(900, m.get_int())
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# Paramiko 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 Lesser General Public License for more
|
||||
# details.
|
||||
|
@ -20,17 +20,23 @@
|
|||
Some unit tests for public/private key objects.
|
||||
"""
|
||||
|
||||
from binascii import hexlify, unhexlify
|
||||
import StringIO
|
||||
import unittest
|
||||
from paramiko import RSAKey, DSSKey, Message, util
|
||||
from paramiko.common import rng
|
||||
from binascii import hexlify
|
||||
from hashlib import md5
|
||||
|
||||
from paramiko import RSAKey, DSSKey, ECDSAKey, Message, util
|
||||
from paramiko.py3compat import StringIO, byte_chr, b, bytes
|
||||
|
||||
from tests.util import test_path
|
||||
|
||||
# from openssh's ssh-keygen
|
||||
PUB_RSA = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA049W6geFpmsljTwfvI1UmKWWJPNFI74+vNKTk4dmzkQY2yAMs6FhlvhlI8ysU4oj71ZsRYMecHbBbxdN79+JRFVYTKaLqjwGENeTd+yv4q+V2PvZv3fLnzApI3l7EJCqhWwJUHJ1jAkZzqDx0tyOL4uoZpww3nmE0kb3y21tH4c='
|
||||
PUB_DSS = 'ssh-dss AAAAB3NzaC1kc3MAAACBAOeBpgNnfRzr/twmAQRu2XwWAp3CFtrVnug6s6fgwj/oLjYbVtjAy6pl/h0EKCWx2rf1IetyNsTxWrniA9I6HeDj65X1FyDkg6g8tvCnaNB8Xp/UUhuzHuGsMIipRxBxw9LF608EqZcj1E3ytktoW5B5OcjrkEoz3xG7C+rpIjYvAAAAFQDwz4UnmsGiSNu5iqjn3uTzwUpshwAAAIEAkxfFeY8P2wZpDjX0MimZl5wkoFQDL25cPzGBuB4OnB8NoUk/yjAHIIpEShw8V+LzouMK5CTJQo5+Ngw3qIch/WgRmMHy4kBq1SsXMjQCte1So6HBMvBPIW5SiMTmjCfZZiw4AYHK+B/JaOwaG9yRg2Ejg4Ok10+XFDxlqZo8Y+wAAACARmR7CCPjodxASvRbIyzaVpZoJ/Z6x7dAumV+ysrV1BVYd0lYukmnjO1kKBWApqpH1ve9XDQYN8zgxM4b16L21kpoWQnZtXrY3GZ4/it9kUgyB7+NwacIBlXa8cMDL7Q/69o0d54U0X/NeX5QxuYR6OMJlrkQB7oiW/P/1mwjQgE='
|
||||
PUB_ECDSA = 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJSPZm3ZWkvk/Zx8WP+fZRZ5/NBBHnGQwR6uIC6XHGPDIHuWUzIjAwA0bzqkOUffEsbLe+uQgKl5kbc/L8KA/eo='
|
||||
|
||||
FINGER_RSA = '1024 60:73:38:44:cb:51:86:65:7f:de:da:a2:2b:5a:57:d5'
|
||||
FINGER_DSS = '1024 44:78:f0:b9:a2:3c:c5:18:20:09:ff:75:5b:c1:d2:6c'
|
||||
FINGER_ECDSA = '256 25:19:eb:55:e6:a1:47:ff:4f:38:d2:75:6f:a5:d5:60'
|
||||
SIGNED_RSA = '20:d7:8a:31:21:cb:f7:92:12:f2:a4:89:37:f5:78:af:e6:16:b6:25:b9:97:3d:a2:cd:5f:ca:20:21:73:4c:ad:34:73:8f:20:77:28:e2:94:15:08:d8:91:40:7a:85:83:bf:18:37:95:dc:54:1a:9b:88:29:6c:73:ca:38:b4:04:f1:56:b9:f2:42:9d:52:1b:29:29:b4:4f:fd:c9:2d:af:47:d2:40:76:30:f3:63:45:0c:d9:1d:43:86:0f:1c:70:e2:93:12:34:f3:ac:c5:0a:2f:14:50:66:59:f1:88:ee:c1:4a:e9:d1:9c:4e:46:f0:0e:47:6f:38:74:f1:44:a8'
|
||||
|
||||
RSA_PRIVATE_OUT = """\
|
||||
|
@ -66,6 +72,16 @@ QPSch9pT9XHqn+1rZ4bK+QGA
|
|||
-----END DSA PRIVATE KEY-----
|
||||
"""
|
||||
|
||||
ECDSA_PRIVATE_OUT = """\
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIKB6ty3yVyKEnfF/zprx0qwC76MsMlHY4HXCnqho2eKioAoGCCqGSM49
|
||||
AwEHoUQDQgAElI9mbdlaS+T9nHxY/59lFnn80EEecZDBHq4gLpccY8Mge5ZTMiMD
|
||||
ADRvOqQ5R98Sxst765CAqXmRtz8vwoD96g==
|
||||
-----END EC PRIVATE KEY-----
|
||||
"""
|
||||
|
||||
x1234 = b'\x01\x02\x03\x04'
|
||||
|
||||
|
||||
class KeyTest (unittest.TestCase):
|
||||
|
||||
|
@ -76,115 +92,164 @@ class KeyTest (unittest.TestCase):
|
|||
pass
|
||||
|
||||
def test_1_generate_key_bytes(self):
|
||||
from Crypto.Hash import MD5
|
||||
key = util.generate_key_bytes(MD5, '\x01\x02\x03\x04', 'happy birthday', 30)
|
||||
exp = unhexlify('61E1F272F4C1C4561586BD322498C0E924672780F47BB37DDA7D54019E64')
|
||||
self.assertEquals(exp, key)
|
||||
key = util.generate_key_bytes(md5, x1234, 'happy birthday', 30)
|
||||
exp = b'\x61\xE1\xF2\x72\xF4\xC1\xC4\x56\x15\x86\xBD\x32\x24\x98\xC0\xE9\x24\x67\x27\x80\xF4\x7B\xB3\x7D\xDA\x7D\x54\x01\x9E\x64'
|
||||
self.assertEqual(exp, key)
|
||||
|
||||
def test_2_load_rsa(self):
|
||||
key = RSAKey.from_private_key_file('tests/test_rsa.key')
|
||||
self.assertEquals('ssh-rsa', key.get_name())
|
||||
exp_rsa = FINGER_RSA.split()[1].replace(':', '')
|
||||
key = RSAKey.from_private_key_file(test_path('test_rsa.key'))
|
||||
self.assertEqual('ssh-rsa', key.get_name())
|
||||
exp_rsa = b(FINGER_RSA.split()[1].replace(':', ''))
|
||||
my_rsa = hexlify(key.get_fingerprint())
|
||||
self.assertEquals(exp_rsa, my_rsa)
|
||||
self.assertEquals(PUB_RSA.split()[1], key.get_base64())
|
||||
self.assertEquals(1024, key.get_bits())
|
||||
self.assertEqual(exp_rsa, my_rsa)
|
||||
self.assertEqual(PUB_RSA.split()[1], key.get_base64())
|
||||
self.assertEqual(1024, key.get_bits())
|
||||
|
||||
s = StringIO.StringIO()
|
||||
s = StringIO()
|
||||
key.write_private_key(s)
|
||||
self.assertEquals(RSA_PRIVATE_OUT, s.getvalue())
|
||||
self.assertEqual(RSA_PRIVATE_OUT, s.getvalue())
|
||||
s.seek(0)
|
||||
key2 = RSAKey.from_private_key(s)
|
||||
self.assertEquals(key, key2)
|
||||
self.assertEqual(key, key2)
|
||||
|
||||
def test_3_load_rsa_password(self):
|
||||
key = RSAKey.from_private_key_file('tests/test_rsa_password.key', 'television')
|
||||
self.assertEquals('ssh-rsa', key.get_name())
|
||||
exp_rsa = FINGER_RSA.split()[1].replace(':', '')
|
||||
key = RSAKey.from_private_key_file(test_path('test_rsa_password.key'), 'television')
|
||||
self.assertEqual('ssh-rsa', key.get_name())
|
||||
exp_rsa = b(FINGER_RSA.split()[1].replace(':', ''))
|
||||
my_rsa = hexlify(key.get_fingerprint())
|
||||
self.assertEquals(exp_rsa, my_rsa)
|
||||
self.assertEquals(PUB_RSA.split()[1], key.get_base64())
|
||||
self.assertEquals(1024, key.get_bits())
|
||||
self.assertEqual(exp_rsa, my_rsa)
|
||||
self.assertEqual(PUB_RSA.split()[1], key.get_base64())
|
||||
self.assertEqual(1024, key.get_bits())
|
||||
|
||||
def test_4_load_dss(self):
|
||||
key = DSSKey.from_private_key_file('tests/test_dss.key')
|
||||
self.assertEquals('ssh-dss', key.get_name())
|
||||
exp_dss = FINGER_DSS.split()[1].replace(':', '')
|
||||
key = DSSKey.from_private_key_file(test_path('test_dss.key'))
|
||||
self.assertEqual('ssh-dss', key.get_name())
|
||||
exp_dss = b(FINGER_DSS.split()[1].replace(':', ''))
|
||||
my_dss = hexlify(key.get_fingerprint())
|
||||
self.assertEquals(exp_dss, my_dss)
|
||||
self.assertEquals(PUB_DSS.split()[1], key.get_base64())
|
||||
self.assertEquals(1024, key.get_bits())
|
||||
self.assertEqual(exp_dss, my_dss)
|
||||
self.assertEqual(PUB_DSS.split()[1], key.get_base64())
|
||||
self.assertEqual(1024, key.get_bits())
|
||||
|
||||
s = StringIO.StringIO()
|
||||
s = StringIO()
|
||||
key.write_private_key(s)
|
||||
self.assertEquals(DSS_PRIVATE_OUT, s.getvalue())
|
||||
self.assertEqual(DSS_PRIVATE_OUT, s.getvalue())
|
||||
s.seek(0)
|
||||
key2 = DSSKey.from_private_key(s)
|
||||
self.assertEquals(key, key2)
|
||||
self.assertEqual(key, key2)
|
||||
|
||||
def test_5_load_dss_password(self):
|
||||
key = DSSKey.from_private_key_file('tests/test_dss_password.key', 'television')
|
||||
self.assertEquals('ssh-dss', key.get_name())
|
||||
exp_dss = FINGER_DSS.split()[1].replace(':', '')
|
||||
key = DSSKey.from_private_key_file(test_path('test_dss_password.key'), 'television')
|
||||
self.assertEqual('ssh-dss', key.get_name())
|
||||
exp_dss = b(FINGER_DSS.split()[1].replace(':', ''))
|
||||
my_dss = hexlify(key.get_fingerprint())
|
||||
self.assertEquals(exp_dss, my_dss)
|
||||
self.assertEquals(PUB_DSS.split()[1], key.get_base64())
|
||||
self.assertEquals(1024, key.get_bits())
|
||||
self.assertEqual(exp_dss, my_dss)
|
||||
self.assertEqual(PUB_DSS.split()[1], key.get_base64())
|
||||
self.assertEqual(1024, key.get_bits())
|
||||
|
||||
def test_6_compare_rsa(self):
|
||||
# verify that the private & public keys compare equal
|
||||
key = RSAKey.from_private_key_file('tests/test_rsa.key')
|
||||
self.assertEquals(key, key)
|
||||
pub = RSAKey(data=str(key))
|
||||
self.assert_(key.can_sign())
|
||||
self.assert_(not pub.can_sign())
|
||||
self.assertEquals(key, pub)
|
||||
key = RSAKey.from_private_key_file(test_path('test_rsa.key'))
|
||||
self.assertEqual(key, key)
|
||||
pub = RSAKey(data=key.asbytes())
|
||||
self.assertTrue(key.can_sign())
|
||||
self.assertTrue(not pub.can_sign())
|
||||
self.assertEqual(key, pub)
|
||||
|
||||
def test_7_compare_dss(self):
|
||||
# verify that the private & public keys compare equal
|
||||
key = DSSKey.from_private_key_file('tests/test_dss.key')
|
||||
self.assertEquals(key, key)
|
||||
pub = DSSKey(data=str(key))
|
||||
self.assert_(key.can_sign())
|
||||
self.assert_(not pub.can_sign())
|
||||
self.assertEquals(key, pub)
|
||||
key = DSSKey.from_private_key_file(test_path('test_dss.key'))
|
||||
self.assertEqual(key, key)
|
||||
pub = DSSKey(data=key.asbytes())
|
||||
self.assertTrue(key.can_sign())
|
||||
self.assertTrue(not pub.can_sign())
|
||||
self.assertEqual(key, pub)
|
||||
|
||||
def test_8_sign_rsa(self):
|
||||
# verify that the rsa private key can sign and verify
|
||||
key = RSAKey.from_private_key_file('tests/test_rsa.key')
|
||||
msg = key.sign_ssh_data(rng, 'ice weasels')
|
||||
self.assert_(type(msg) is Message)
|
||||
key = RSAKey.from_private_key_file(test_path('test_rsa.key'))
|
||||
msg = key.sign_ssh_data(b'ice weasels')
|
||||
self.assertTrue(type(msg) is Message)
|
||||
msg.rewind()
|
||||
self.assertEquals('ssh-rsa', msg.get_string())
|
||||
sig = ''.join([chr(int(x, 16)) for x in SIGNED_RSA.split(':')])
|
||||
self.assertEquals(sig, msg.get_string())
|
||||
self.assertEqual('ssh-rsa', msg.get_text())
|
||||
sig = bytes().join([byte_chr(int(x, 16)) for x in SIGNED_RSA.split(':')])
|
||||
self.assertEqual(sig, msg.get_binary())
|
||||
msg.rewind()
|
||||
pub = RSAKey(data=str(key))
|
||||
self.assert_(pub.verify_ssh_sig('ice weasels', msg))
|
||||
pub = RSAKey(data=key.asbytes())
|
||||
self.assertTrue(pub.verify_ssh_sig(b'ice weasels', msg))
|
||||
|
||||
def test_9_sign_dss(self):
|
||||
# verify that the dss private key can sign and verify
|
||||
key = DSSKey.from_private_key_file('tests/test_dss.key')
|
||||
msg = key.sign_ssh_data(rng, 'ice weasels')
|
||||
self.assert_(type(msg) is Message)
|
||||
key = DSSKey.from_private_key_file(test_path('test_dss.key'))
|
||||
msg = key.sign_ssh_data(b'ice weasels')
|
||||
self.assertTrue(type(msg) is Message)
|
||||
msg.rewind()
|
||||
self.assertEquals('ssh-dss', msg.get_string())
|
||||
self.assertEqual('ssh-dss', msg.get_text())
|
||||
# can't do the same test as we do for RSA, because DSS signatures
|
||||
# are usually different each time. but we can test verification
|
||||
# anyway so it's ok.
|
||||
self.assertEquals(40, len(msg.get_string()))
|
||||
self.assertEqual(40, len(msg.get_binary()))
|
||||
msg.rewind()
|
||||
pub = DSSKey(data=str(key))
|
||||
self.assert_(pub.verify_ssh_sig('ice weasels', msg))
|
||||
pub = DSSKey(data=key.asbytes())
|
||||
self.assertTrue(pub.verify_ssh_sig(b'ice weasels', msg))
|
||||
|
||||
def test_A_generate_rsa(self):
|
||||
key = RSAKey.generate(1024)
|
||||
msg = key.sign_ssh_data(rng, 'jerri blank')
|
||||
msg = key.sign_ssh_data(b'jerri blank')
|
||||
msg.rewind()
|
||||
self.assert_(key.verify_ssh_sig('jerri blank', msg))
|
||||
self.assertTrue(key.verify_ssh_sig(b'jerri blank', msg))
|
||||
|
||||
def test_B_generate_dss(self):
|
||||
key = DSSKey.generate(1024)
|
||||
msg = key.sign_ssh_data(rng, 'jerri blank')
|
||||
msg = key.sign_ssh_data(b'jerri blank')
|
||||
msg.rewind()
|
||||
self.assert_(key.verify_ssh_sig('jerri blank', msg))
|
||||
self.assertTrue(key.verify_ssh_sig(b'jerri blank', msg))
|
||||
|
||||
def test_10_load_ecdsa(self):
|
||||
key = ECDSAKey.from_private_key_file(test_path('test_ecdsa.key'))
|
||||
self.assertEqual('ecdsa-sha2-nistp256', key.get_name())
|
||||
exp_ecdsa = b(FINGER_ECDSA.split()[1].replace(':', ''))
|
||||
my_ecdsa = hexlify(key.get_fingerprint())
|
||||
self.assertEqual(exp_ecdsa, my_ecdsa)
|
||||
self.assertEqual(PUB_ECDSA.split()[1], key.get_base64())
|
||||
self.assertEqual(256, key.get_bits())
|
||||
|
||||
s = StringIO()
|
||||
key.write_private_key(s)
|
||||
self.assertEqual(ECDSA_PRIVATE_OUT, s.getvalue())
|
||||
s.seek(0)
|
||||
key2 = ECDSAKey.from_private_key(s)
|
||||
self.assertEqual(key, key2)
|
||||
|
||||
def test_11_load_ecdsa_password(self):
|
||||
key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_password.key'), b'television')
|
||||
self.assertEqual('ecdsa-sha2-nistp256', key.get_name())
|
||||
exp_ecdsa = b(FINGER_ECDSA.split()[1].replace(':', ''))
|
||||
my_ecdsa = hexlify(key.get_fingerprint())
|
||||
self.assertEqual(exp_ecdsa, my_ecdsa)
|
||||
self.assertEqual(PUB_ECDSA.split()[1], key.get_base64())
|
||||
self.assertEqual(256, key.get_bits())
|
||||
|
||||
def test_12_compare_ecdsa(self):
|
||||
# verify that the private & public keys compare equal
|
||||
key = ECDSAKey.from_private_key_file(test_path('test_ecdsa.key'))
|
||||
self.assertEqual(key, key)
|
||||
pub = ECDSAKey(data=key.asbytes())
|
||||
self.assertTrue(key.can_sign())
|
||||
self.assertTrue(not pub.can_sign())
|
||||
self.assertEqual(key, pub)
|
||||
|
||||
def test_13_sign_ecdsa(self):
|
||||
# verify that the rsa private key can sign and verify
|
||||
key = ECDSAKey.from_private_key_file(test_path('test_ecdsa.key'))
|
||||
msg = key.sign_ssh_data(b'ice weasels')
|
||||
self.assertTrue(type(msg) is Message)
|
||||
msg.rewind()
|
||||
self.assertEqual('ecdsa-sha2-nistp256', msg.get_text())
|
||||
# ECDSA signatures, like DSS signatures, tend to be different
|
||||
# each time, so we can't compare against a "known correct"
|
||||
# signature.
|
||||
# Even the length of the signature can change.
|
||||
|
||||
msg.rewind()
|
||||
pub = ECDSAKey(data=key.asbytes())
|
||||
self.assertTrue(pub.verify_ssh_sig(b'ice weasels', msg))
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue