From bd21af3405a3591dcc0533993d587aa2e4db0c4f Mon Sep 17 00:00:00 2001 From: Matthias Witte Date: Tue, 15 Jul 2014 12:22:11 +0200 Subject: Add support for sha256 based hmac and kexgex --- paramiko/kex_gex.py | 13 +++++++++---- paramiko/transport.py | 26 +++++++++++++++++--------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/paramiko/kex_gex.py b/paramiko/kex_gex.py index 5ff8a287..1eadba03 100644 --- a/paramiko/kex_gex.py +++ b/paramiko/kex_gex.py @@ -23,7 +23,7 @@ client side, and a B{lot} more on the server side. """ import os -from hashlib import sha1 +from hashlib import sha1, sha256 from paramiko import util from paramiko.common import DEBUG @@ -44,6 +44,7 @@ class KexGex (object): min_bits = 1024 max_bits = 8192 preferred_bits = 2048 + hash_algo = sha1 def __init__(self, transport): self.transport = transport @@ -87,7 +88,7 @@ class KexGex (object): return self._parse_kexdh_gex_reply(m) elif ptype == _MSG_KEXDH_GEX_REQUEST_OLD: return self._parse_kexdh_gex_request_old(m) - raise SSHException('KexGex asked to handle packet type %d' % ptype) + raise SSHException('KexGex %s asked to handle packet type %d' % self.name, ptype) ### internals... @@ -204,7 +205,7 @@ class KexGex (object): hm.add_mpint(self.e) hm.add_mpint(self.f) hm.add_mpint(K) - H = sha1(hm.asbytes()).digest() + H = self.hash_algo(hm.asbytes()).digest() self.transport._set_K_H(K, H) # sign it sig = self.transport.get_server_key().sign_ssh_data(H) @@ -239,6 +240,10 @@ class KexGex (object): hm.add_mpint(self.e) hm.add_mpint(self.f) hm.add_mpint(K) - self.transport._set_K_H(K, sha1(hm.asbytes()).digest()) + self.transport._set_K_H(K, self.hash_algo(hm.asbytes()).digest()) self.transport._verify_key(host_key, sig) self.transport._activate_outbound() + +class KexGexSHA256(KexGex): + name = 'diffie-hellman-group-exchange-sha256' + hash_algo = sha256 diff --git a/paramiko/transport.py b/paramiko/transport.py index 406626a7..48ab1729 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -26,7 +26,7 @@ import sys import threading import time import weakref -from hashlib import md5, sha1 +from hashlib import md5, sha1, sha256 import paramiko from paramiko import util @@ -45,7 +45,7 @@ from paramiko.common import xffffffff, cMSG_CHANNEL_OPEN, cMSG_IGNORE, \ MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE from paramiko.compress import ZlibCompressor, ZlibDecompressor from paramiko.dsskey import DSSKey -from paramiko.kex_gex import KexGex +from paramiko.kex_gex import KexGex, KexGexSHA256 from paramiko.kex_group1 import KexGroup1 from paramiko.message import Message from paramiko.packet import Packetizer, NeedRekeyException @@ -90,9 +90,12 @@ class Transport (threading.Thread): _preferred_ciphers = ('aes128-ctr', 'aes256-ctr', 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc', 'arcfour128', 'arcfour256') - _preferred_macs = ('hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96') + _preferred_macs = ('hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96', + 'hmac-sha2-256') _preferred_keys = ('ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256') - _preferred_kex = ('diffie-hellman-group1-sha1', 'diffie-hellman-group-exchange-sha1') + _preferred_kex = ('diffie-hellman-group1-sha1', + 'diffie-hellman-group-exchange-sha1', + 'diffie-hellman-group-exchange-sha256') _preferred_compression = ('none',) _cipher_info = { @@ -109,6 +112,7 @@ class Transport (threading.Thread): _mac_info = { 'hmac-sha1': {'class': sha1, 'size': 20}, 'hmac-sha1-96': {'class': sha1, 'size': 12}, + 'hmac-sha2-256': {'class': sha256, 'size': 32}, 'hmac-md5': {'class': md5, 'size': 16}, 'hmac-md5-96': {'class': md5, 'size': 12}, } @@ -122,6 +126,7 @@ class Transport (threading.Thread): _kex_info = { 'diffie-hellman-group1-sha1': KexGroup1, 'diffie-hellman-group-exchange-sha1': KexGex, + 'diffie-hellman-group-exchange-sha256': KexGexSHA256, } _compression_info = { @@ -1336,13 +1341,14 @@ class Transport (threading.Thread): m.add_bytes(self.H) m.add_byte(b(id)) m.add_bytes(self.session_id) - out = sofar = sha1(m.asbytes()).digest() + hash_algo = self._mac_info[self.local_mac]['class'] + out = sofar = hash_algo(m.asbytes()).digest() while len(out) < nbytes: m = Message() m.add_mpint(self.K) m.add_bytes(self.H) m.add_bytes(sofar) - digest = sha1(m.asbytes()).digest() + digest = hash_algo(m.asbytes()).digest() out += digest sofar += digest return out[:nbytes] @@ -1572,10 +1578,12 @@ class Transport (threading.Thread): self.clear_to_send_lock.release() self.in_kex = True if self.server_mode: - if (self._modulus_pack is None) and ('diffie-hellman-group-exchange-sha1' in self._preferred_kex): + mp_required_prefix = 'diffie-hellman-group-exchange-sha' + kex_mp = [k for k in self._preferred_kex if k.startswith(mp_required_prefix)] + if (self._modulus_pack is None) and (len(kex_mp) > 0): # can't do group-exchange if we don't have a pack of potential primes - pkex = list(self.get_security_options().kex) - pkex.remove('diffie-hellman-group-exchange-sha1') + pkex = [k for k in self.get_security_options().kex + if not k.startswith(mp_required_prefix)] self.get_security_options().kex = pkex available_server_keys = list(filter(list(self.server_key_dict.keys()).__contains__, self._preferred_keys)) -- cgit v1.2.3 From f4cfd1967a1b9d15262ab859d392b3edef512ad7 Mon Sep 17 00:00:00 2001 From: Matthias Witte Date: Tue, 15 Jul 2014 12:22:11 +0200 Subject: Add support for sha256 based hmac and kexgex - fix tab damage --- paramiko/kex_gex.py | 13 +++++++++---- paramiko/transport.py | 26 +++++++++++++++++--------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/paramiko/kex_gex.py b/paramiko/kex_gex.py index 5ff8a287..1eadba03 100644 --- a/paramiko/kex_gex.py +++ b/paramiko/kex_gex.py @@ -23,7 +23,7 @@ client side, and a B{lot} more on the server side. """ import os -from hashlib import sha1 +from hashlib import sha1, sha256 from paramiko import util from paramiko.common import DEBUG @@ -44,6 +44,7 @@ class KexGex (object): min_bits = 1024 max_bits = 8192 preferred_bits = 2048 + hash_algo = sha1 def __init__(self, transport): self.transport = transport @@ -87,7 +88,7 @@ class KexGex (object): return self._parse_kexdh_gex_reply(m) elif ptype == _MSG_KEXDH_GEX_REQUEST_OLD: return self._parse_kexdh_gex_request_old(m) - raise SSHException('KexGex asked to handle packet type %d' % ptype) + raise SSHException('KexGex %s asked to handle packet type %d' % self.name, ptype) ### internals... @@ -204,7 +205,7 @@ class KexGex (object): hm.add_mpint(self.e) hm.add_mpint(self.f) hm.add_mpint(K) - H = sha1(hm.asbytes()).digest() + H = self.hash_algo(hm.asbytes()).digest() self.transport._set_K_H(K, H) # sign it sig = self.transport.get_server_key().sign_ssh_data(H) @@ -239,6 +240,10 @@ class KexGex (object): hm.add_mpint(self.e) hm.add_mpint(self.f) hm.add_mpint(K) - self.transport._set_K_H(K, sha1(hm.asbytes()).digest()) + self.transport._set_K_H(K, self.hash_algo(hm.asbytes()).digest()) self.transport._verify_key(host_key, sig) self.transport._activate_outbound() + +class KexGexSHA256(KexGex): + name = 'diffie-hellman-group-exchange-sha256' + hash_algo = sha256 diff --git a/paramiko/transport.py b/paramiko/transport.py index 406626a7..9d0889bb 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -26,7 +26,7 @@ import sys import threading import time import weakref -from hashlib import md5, sha1 +from hashlib import md5, sha1, sha256 import paramiko from paramiko import util @@ -45,7 +45,7 @@ from paramiko.common import xffffffff, cMSG_CHANNEL_OPEN, cMSG_IGNORE, \ MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE from paramiko.compress import ZlibCompressor, ZlibDecompressor from paramiko.dsskey import DSSKey -from paramiko.kex_gex import KexGex +from paramiko.kex_gex import KexGex, KexGexSHA256 from paramiko.kex_group1 import KexGroup1 from paramiko.message import Message from paramiko.packet import Packetizer, NeedRekeyException @@ -90,9 +90,12 @@ class Transport (threading.Thread): _preferred_ciphers = ('aes128-ctr', 'aes256-ctr', 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc', 'arcfour128', 'arcfour256') - _preferred_macs = ('hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96') + _preferred_macs = ('hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96', + 'hmac-sha2-256') _preferred_keys = ('ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256') - _preferred_kex = ('diffie-hellman-group1-sha1', 'diffie-hellman-group-exchange-sha1') + _preferred_kex = ('diffie-hellman-group1-sha1', + 'diffie-hellman-group-exchange-sha1', + 'diffie-hellman-group-exchange-sha256') _preferred_compression = ('none',) _cipher_info = { @@ -109,6 +112,7 @@ class Transport (threading.Thread): _mac_info = { 'hmac-sha1': {'class': sha1, 'size': 20}, 'hmac-sha1-96': {'class': sha1, 'size': 12}, + 'hmac-sha2-256': {'class': sha256, 'size': 32}, 'hmac-md5': {'class': md5, 'size': 16}, 'hmac-md5-96': {'class': md5, 'size': 12}, } @@ -122,6 +126,7 @@ class Transport (threading.Thread): _kex_info = { 'diffie-hellman-group1-sha1': KexGroup1, 'diffie-hellman-group-exchange-sha1': KexGex, + 'diffie-hellman-group-exchange-sha256': KexGexSHA256, } _compression_info = { @@ -1336,13 +1341,14 @@ class Transport (threading.Thread): m.add_bytes(self.H) m.add_byte(b(id)) m.add_bytes(self.session_id) - out = sofar = sha1(m.asbytes()).digest() + hash_algo = self._mac_info[self.local_mac]['class'] + out = sofar = hash_algo(m.asbytes()).digest() while len(out) < nbytes: m = Message() m.add_mpint(self.K) m.add_bytes(self.H) m.add_bytes(sofar) - digest = sha1(m.asbytes()).digest() + digest = hash_algo(m.asbytes()).digest() out += digest sofar += digest return out[:nbytes] @@ -1572,10 +1578,12 @@ class Transport (threading.Thread): self.clear_to_send_lock.release() self.in_kex = True if self.server_mode: - if (self._modulus_pack is None) and ('diffie-hellman-group-exchange-sha1' in self._preferred_kex): + mp_required_prefix = 'diffie-hellman-group-exchange-sha' + kex_mp = [k for k in self._preferred_kex if k.startswith(mp_required_prefix)] + if (self._modulus_pack is None) and (len(kex_mp) > 0): # can't do group-exchange if we don't have a pack of potential primes - pkex = list(self.get_security_options().kex) - pkex.remove('diffie-hellman-group-exchange-sha1') + pkex = [k for k in self.get_security_options().kex + if not k.startswith(mp_required_prefix)] self.get_security_options().kex = pkex available_server_keys = list(filter(list(self.server_key_dict.keys()).__contains__, self._preferred_keys)) -- cgit v1.2.3 From 26b11d06ff2790cfa55c9557f10a2b48a3fc55b7 Mon Sep 17 00:00:00 2001 From: Matthias Witte Date: Tue, 15 Jul 2014 14:36:41 +0200 Subject: Fix transport test --- tests/test_transport.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_transport.py b/tests/test_transport.py index 485a18e8..3e794c12 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -26,6 +26,7 @@ import socket import time import threading import random +from hashlib import sha1 from paramiko import Transport, SecurityOptions, ServerInterface, RSAKey, DSSKey, \ SSHException, ChannelException @@ -160,6 +161,7 @@ class TransportTest(ParamikoTest): self.tc.K = 123281095979686581523377256114209720774539068973101330872763622971399429481072519713536292772709507296759612401802191955568143056534122385270077606457721553469730659233569339356140085284052436697480759510519672848743794433460113118986816826624865291116513647975790797391795651716378444844877749505443714557929 self.tc.H = b'\x0C\x83\x07\xCD\xE6\x85\x6F\xF3\x0B\xA9\x36\x84\xEB\x0F\x04\xC2\x52\x0E\x9E\xD3' self.tc.session_id = self.tc.H + self.tc.local_mac = 'hmac-sha1' key = self.tc._compute_key('C', 32) self.assertEqual(b'207E66594CA87C44ECCBA3B3CD39FDDB378E6FDB0F97C54B2AA0CFBF900CD995', hexlify(key).upper()) @@ -426,9 +428,11 @@ class TransportTest(ParamikoTest): bytes = self.tc.packetizer._Packetizer__sent_bytes chan.send('x' * 1024) bytes2 = self.tc.packetizer._Packetizer__sent_bytes + block_size = self.tc._cipher_info[self.tc.local_cipher]['block-size'] + mac_size = self.tc._mac_info[self.tc.local_mac]['size'] # tests show this is actually compressed to *52 bytes*! including packet overhead! nice!! :) self.assertTrue(bytes2 - bytes < 1024) - self.assertEqual(52, bytes2 - bytes) + self.assertEqual(16 + block_size + mac_size, bytes2 - bytes) chan.close() schan.close() -- cgit v1.2.3 From 47da1935dc84784bfee2e493474232bf2e0d8d37 Mon Sep 17 00:00:00 2001 From: Matthias Witte Date: Wed, 16 Jul 2014 11:17:40 +0200 Subject: Include sha2 changes in tests - let _compute_key default default to sha1 if local_mac is not set instead of setting local_mac explicitly in the unit test - add tests for KexGexSHA256 --- paramiko/transport.py | 2 +- tests/test_kex.py | 120 +++++++++++++++++++++++++++++++++++++++++++++++- tests/test_transport.py | 1 - 3 files changed, 120 insertions(+), 3 deletions(-) diff --git a/paramiko/transport.py b/paramiko/transport.py index 9d0889bb..35c20341 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -1341,7 +1341,7 @@ class Transport (threading.Thread): m.add_bytes(self.H) m.add_byte(b(id)) m.add_bytes(self.session_id) - hash_algo = self._mac_info[self.local_mac]['class'] + hash_algo = self._mac_info[self.local_mac]['class'] if self.local_mac else sha1 out = sofar = hash_algo(m.asbytes()).digest() while len(out) < nbytes: m = Message() diff --git a/tests/test_kex.py b/tests/test_kex.py index 56f1b7c7..19804fbf 100644 --- a/tests/test_kex.py +++ b/tests/test_kex.py @@ -26,7 +26,7 @@ import unittest import paramiko.util from paramiko.kex_group1 import KexGroup1 -from paramiko.kex_gex import KexGex +from paramiko.kex_gex import KexGex, KexGexSHA256 from paramiko import Message from paramiko.common import byte_chr @@ -252,3 +252,121 @@ class KexTest (unittest.TestCase): self.assertEqual(H, hexlify(transport._H).upper()) self.assertEqual(x, hexlify(transport._message.asbytes()).upper()) self.assertTrue(transport._activated) + + def test_7_gex_sha256_client(self): + transport = FakeTransport() + transport.server_mode = False + kex = KexGexSHA256(transport) + kex.start_kex() + 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 = 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') + msg.add_mpint(69) + msg.add_string('fake-sig') + msg.rewind() + kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_REPLY, msg) + H = b'AD1A9365A67B4496F05594AD1BF656E3CDA0851289A4C1AFF549FEAE50896DF4' + 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_8_gex_sha256_old_client(self): + transport = FakeTransport() + transport.server_mode = False + kex = KexGexSHA256(transport) + kex.start_kex(_test_old_style=True) + 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 = 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') + msg.add_mpint(69) + msg.add_string('fake-sig') + msg.rewind() + kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_REPLY, msg) + H = b'518386608B15891AE5237DEE08DCADDE76A0BCEFCE7F6DB3AD66BC41D256DFE5' + 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_9_gex_sha256_server(self): + transport = FakeTransport() + transport.server_mode = True + kex = KexGexSHA256(transport) + kex.start_kex() + 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) + msg.add_int(2048) + msg.add_int(4096) + msg.rewind() + kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST, msg) + 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 = 67592995013596137876033460028393339951879041140378510871612128162185209509220726296697886624612526735888348020498716482757677848959420073720160491114319163078862905400020959196386947926388406687288901564192071077389283980347784184487280885335302632305026248574716290537036069329724382811853044654824945750581 + H = b'CCAC0497CF0ABA1DBF55E1A3995D17F4CC31824B0E8D95CDF8A06F169D050D80' + 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_10_gex_sha256_server_with_old_client(self): + transport = FakeTransport() + transport.server_mode = True + kex = KexGexSHA256(transport) + kex.start_kex() + 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 = 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 = 67592995013596137876033460028393339951879041140378510871612128162185209509220726296697886624612526735888348020498716482757677848959420073720160491114319163078862905400020959196386947926388406687288901564192071077389283980347784184487280885335302632305026248574716290537036069329724382811853044654824945750581 + H = b'3DDD2AD840AD095E397BA4D0573972DC60F6461FD38A187CACA6615A5BC8ADBB' + 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) + + diff --git a/tests/test_transport.py b/tests/test_transport.py index 3e794c12..0d66c0dc 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -161,7 +161,6 @@ class TransportTest(ParamikoTest): self.tc.K = 123281095979686581523377256114209720774539068973101330872763622971399429481072519713536292772709507296759612401802191955568143056534122385270077606457721553469730659233569339356140085284052436697480759510519672848743794433460113118986816826624865291116513647975790797391795651716378444844877749505443714557929 self.tc.H = b'\x0C\x83\x07\xCD\xE6\x85\x6F\xF3\x0B\xA9\x36\x84\xEB\x0F\x04\xC2\x52\x0E\x9E\xD3' self.tc.session_id = self.tc.H - self.tc.local_mac = 'hmac-sha1' key = self.tc._compute_key('C', 32) self.assertEqual(b'207E66594CA87C44ECCBA3B3CD39FDDB378E6FDB0F97C54B2AA0CFBF900CD995', hexlify(key).upper()) -- cgit v1.2.3 From dae916f7bd6723cee95891778baff51ef45532ee Mon Sep 17 00:00:00 2001 From: Perry Randall Date: Wed, 24 Dec 2014 07:54:06 -0800 Subject: Add more flexible support for two factor authentication. Allow paramiko to partially authenticate and continue by echo'ing the prompt on the remote end and responding. --- paramiko/client.py | 25 +++++++++++++++---------- paramiko/transport.py | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/paramiko/client.py b/paramiko/client.py index 393e3e09..1ccf0456 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -404,7 +404,8 @@ class SSHClient (ClosingContextManager): """ saved_exception = None two_factor = False - allowed_types = [] + allowed_types = set() + two_factor_types = set(['keyboard-interactive','password']) # If GSS-API support and GSS-PI Key Exchange was performed, we attempt # authentication with gssapi-keyex. @@ -430,8 +431,8 @@ class SSHClient (ClosingContextManager): if pkey is not None: try: self._log(DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint())) - allowed_types = self._transport.auth_publickey(username, pkey) - two_factor = (allowed_types == ['password']) + allowed_types = set(self._transport.auth_publickey(username, pkey)) + two_factor = (allowed_types & two_factor_types) if not two_factor: return except SSHException as e: @@ -444,7 +445,7 @@ class SSHClient (ClosingContextManager): key = pkey_class.from_private_key_file(key_filename, password) self._log(DEBUG, 'Trying key %s from %s' % (hexlify(key.get_fingerprint()), key_filename)) self._transport.auth_publickey(username, key) - two_factor = (allowed_types == ['password']) + two_factor = (allowed_types & two_factor_types) if not two_factor: return break @@ -458,9 +459,9 @@ class SSHClient (ClosingContextManager): for key in self._agent.get_keys(): try: self._log(DEBUG, 'Trying SSH agent key %s' % hexlify(key.get_fingerprint())) - # for 2-factor auth a successfully auth'd key will result in ['password'] - allowed_types = self._transport.auth_publickey(username, key) - two_factor = (allowed_types == ['password']) + # for 2-factor auth a successfully auth'd key password will return an allowed 2fac auth method + allowed_types = set(self._transport.auth_publickey(username, key)) + two_factor = (allowed_types & two_factor_types) if not two_factor: return break @@ -497,8 +498,8 @@ class SSHClient (ClosingContextManager): key = pkey_class.from_private_key_file(filename, password) self._log(DEBUG, 'Trying discovered key %s in %s' % (hexlify(key.get_fingerprint()), filename)) # for 2-factor auth a successfully auth'd key will result in ['password'] - allowed_types = self._transport.auth_publickey(username, key) - two_factor = (allowed_types == ['password']) + allowed_types = set(self._transport.auth_publickey(username, key)) + two_factor = (allowed_types & two_factor_types) if not two_factor: return break @@ -512,7 +513,11 @@ class SSHClient (ClosingContextManager): except SSHException as e: saved_exception = e elif two_factor: - raise SSHException('Two-factor authentication requires a password') + try: + self._transport.auth_interactive_dumb(username) + return + except SSHException as e: + saved_exception = e # if we got an auth-failed exception earlier, re-raise it if saved_exception is not None: diff --git a/paramiko/transport.py b/paramiko/transport.py index 36da3043..4fa36191 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -20,6 +20,7 @@ Core protocol implementation """ +from __future__ import print_function import os import socket import sys @@ -1284,6 +1285,27 @@ class Transport (threading.Thread, ClosingContextManager): self.auth_handler.auth_interactive(username, handler, my_event, submethods) return self.auth_handler.wait_for_response(my_event) + def auth_interactive_dumb(self, username, handler=None, submethods=''): + """ + Autenticate to the server interactively but dumber. + Just print the prompt and / or instructions to stdout and send back + the response. This is good for situations where partial auth is + achieved by key and then the user has to enter a 2fac token. + """ + + if not handler: + def handler(title, instructions, prompt_list): + answers = [] + if title: + print(title.strip()) + if instructions: + print(instructions.strip()) + for prompt,show_input in prompt_list: + print(prompt.strip(),end=' ') + answers.append(raw_input()) + return answers + return self.auth_interactive(username, handler, submethods) + def auth_gssapi_with_mic(self, username, gss_host, gss_deleg_creds): """ Authenticate to the Server using GSS-API / SSPI. -- cgit v1.2.3 From 70924234bb70d15005e4ce18305fc610482acf1b Mon Sep 17 00:00:00 2001 From: Scott Maxwell Date: Mon, 26 Jan 2015 22:36:15 -0800 Subject: Revert add_int and get_int to strictly 32-bits and add adaptive versions --- paramiko/message.py | 45 +++++++++------------------------------------ tests/test_message.py | 8 ++++---- 2 files changed, 13 insertions(+), 40 deletions(-) diff --git a/paramiko/message.py b/paramiko/message.py index b893e76d..bf4c6b95 100644 --- a/paramiko/message.py +++ b/paramiko/message.py @@ -129,7 +129,7 @@ class Message (object): b = self.get_bytes(1) return b != zero_byte - def get_int(self): + def get_adaptive_int(self): """ Fetch an int from the stream. @@ -141,20 +141,7 @@ class Message (object): 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 - """ - 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): + def get_int(self): """ Fetch an int from the stream. @@ -185,7 +172,7 @@ class Message (object): contain unprintable characters. (It's not unheard of for a string to contain another byte-stream message.) """ - return self.get_bytes(self.get_size()) + return self.get_bytes(self.get_int()) def get_text(self): """ @@ -196,7 +183,7 @@ class Message (object): @return: a string. @rtype: string """ - return u(self.get_bytes(self.get_size())) + return u(self.get_bytes(self.get_int())) #return self.get_bytes(self.get_size()) def get_binary(self): @@ -208,7 +195,7 @@ class Message (object): @return: a string. @rtype: string """ - return self.get_bytes(self.get_size()) + return self.get_bytes(self.get_int()) def get_list(self): """ @@ -248,7 +235,7 @@ class Message (object): self.packet.write(zero_byte) return self - def add_size(self, n): + def add_int(self, n): """ Add an integer to the stream. @@ -257,7 +244,7 @@ class Message (object): self.packet.write(struct.pack('>I', n)) return self - def add_int(self, n): + def add_adaptive_int(self, n): """ Add an integer to the stream. @@ -270,20 +257,6 @@ class Message (object): 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 - """ - 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 - def add_int64(self, n): """ Add a 64-bit int to the stream. @@ -310,7 +283,7 @@ class Message (object): :param str s: string to add """ s = asbytes(s) - self.add_size(len(s)) + self.add_int(len(s)) self.packet.write(s) return self @@ -329,7 +302,7 @@ class Message (object): if type(i) is bool: return self.add_boolean(i) elif isinstance(i, integer_types): - return self.add_int(i) + return self.add_adaptive_int(i) elif type(i) is list: return self.add_list(i) else: diff --git a/tests/test_message.py b/tests/test_message.py index f308c037..f18cae90 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -92,12 +92,12 @@ class MessageTest (unittest.TestCase): def test_4_misc(self): msg = Message(self.__d) - self.assertEqual(msg.get_int(), 5) - self.assertEqual(msg.get_int(), 0x1122334455) - self.assertEqual(msg.get_int(), 0xf00000000000000000) + self.assertEqual(msg.get_adaptive_int(), 5) + self.assertEqual(msg.get_adaptive_int(), 0x1122334455) + self.assertEqual(msg.get_adaptive_int(), 0xf00000000000000000) self.assertEqual(msg.get_so_far(), self.__d[:29]) self.assertEqual(msg.get_remainder(), self.__d[29:]) msg.rewind() - self.assertEqual(msg.get_int(), 5) + self.assertEqual(msg.get_adaptive_int(), 5) self.assertEqual(msg.get_so_far(), self.__d[:4]) self.assertEqual(msg.get_remainder(), self.__d[4:]) -- cgit v1.2.3 From b98ba1c5bb6ff1d968e9105ff093d360ac9091e9 Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Tue, 24 Feb 2015 14:47:49 +0100 Subject: Add support for signaling a handshake process in packetizer. This makes it possible to raise an EOFError if the handshake process has started but takes too long time to finish. --- paramiko/packet.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/paramiko/packet.py b/paramiko/packet.py index f516ff9b..b922000c 100644 --- a/paramiko/packet.py +++ b/paramiko/packet.py @@ -99,6 +99,10 @@ class Packetizer (object): self.__keepalive_last = time.time() self.__keepalive_callback = None + self.__timer = None + self.__handshake_complete = False + self.__timer_expired = False + def set_log(self, log): """ Set the Python log object to use for logging. @@ -182,6 +186,45 @@ class Packetizer (object): self.__keepalive_callback = callback self.__keepalive_last = time.time() + def read_timer(self): + self.__timer_expired = True + + def start_handshake(self, timeout): + """ + Tells `Packetizer` that the handshake process started. + Starts a book keeping timer that can signal a timeout in the + handshake process. + + :param float timeout: amount of seconds to wait before timing out + """ + if not self.__timer: + self.__timer = threading.Timer(float(timeout), self.read_timer) + self.__timer.start() + + def handshake_timed_out(self): + """ + Checks if the handshake has timed out. + If `start_handshake` wasn't called before the call to this function + the return value will always be `False`. + If the handshake completed before a time out was reached the return value will be `False` + + :return: handshake time out status, as a `bool` + """ + if not self.__timer: + return False + if self.__handshake_complete: + return False + return self.__timer_expired + + def complete_handshake(self): + """ + Tells `Packetizer` that the handshake has completed. + """ + if self.__timer: + self.__timer.cancel() + self.__timer_expired = False + self.__handshake_complete = True + def read_all(self, n, check_rekey=False): """ Read as close to N bytes as possible, blocking as long as necessary. @@ -200,6 +243,8 @@ class Packetizer (object): n -= len(out) while n > 0: got_timeout = False + if self.handshake_timed_out(): + raise EOFError() try: x = self.__socket.recv(n) if len(x) == 0: -- cgit v1.2.3 From d1f72859c76beda46a072cdc75b2e19e4418275a Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Tue, 24 Feb 2015 14:49:36 +0100 Subject: Expose handshake timeout in the transport API. This is a reimplementation of #62. --- paramiko/transport.py | 9 +++++++++ sites/www/changelog.rst | 5 +++++ tests/test_transport.py | 17 +++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/paramiko/transport.py b/paramiko/transport.py index 36da3043..6047fb99 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -295,6 +295,8 @@ class Transport (threading.Thread, ClosingContextManager): self.global_response = None # response Message from an arbitrary global request self.completion_event = None # user-defined event callbacks self.banner_timeout = 15 # how long (seconds) to wait for the SSH banner + self.handshake_timeout = 15 # how long (seconds) to wait for the handshake to finish after SSH banner sent. + # server mode: self.server_mode = False @@ -1582,6 +1584,12 @@ class Transport (threading.Thread, ClosingContextManager): try: self.packetizer.write_all(b(self.local_version + '\r\n')) self._check_banner() + # The above is actually very much part of the handshake, but sometimes the banner can be read + # but the machine is not responding, for example when the remote ssh daemon is loaded in to memory + # but we can not read from the disk/spawn a new shell. + # Make sure we can specify a timeout for the initial handshake. + # Re-use the banner timeout for now. + self.packetizer.start_handshake(self.handshake_timeout) self._send_kex_init() self._expect_packet(MSG_KEXINIT) @@ -1631,6 +1639,7 @@ class Transport (threading.Thread, ClosingContextManager): msg.add_byte(cMSG_UNIMPLEMENTED) msg.add_int(m.seqno) self._send_message(msg) + self.packetizer.complete_handshake() except SSHException as e: self._log(ERROR, 'Exception: ' + str(e)) self._log(ERROR, util.tb_strings()) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 6520dde4..f9900327 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,11 @@ Changelog ========= +* :bug:`62` Add timeout for handshake completion. + This adds a mechanism for timing out a connection if the ssh handshake + never completes. + Credit to ``@dacut`` for initial report and patch and to Olle Lundberg for + re-implementation. * :bug:`402` Check to see if an SSH agent is actually present before trying to forward it to the remote end. This replaces what was usually a useless ``TypeError`` with a human-readable ``AuthenticationError``. Credit to Ken diff --git a/tests/test_transport.py b/tests/test_transport.py index 5cf9a867..3c8ad81e 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -792,3 +792,20 @@ class TransportTest(unittest.TestCase): (None, DEFAULT_WINDOW_SIZE), (2**32, MAX_WINDOW_SIZE)]: self.assertEqual(self.tc._sanitize_window_size(val), correct) + + def test_L_handshake_timeout(self): + """ + verify that we can get a hanshake timeout. + """ + host_key = RSAKey.from_private_key_file(test_path('test_rsa.key')) + public_host_key = RSAKey(data=host_key.asbytes()) + self.ts.add_server_key(host_key) + event = threading.Event() + server = NullServer() + self.assertTrue(not event.is_set()) + self.tc.handshake_timeout = 0.000000000001 + self.ts.start_server(event, server) + self.assertRaises(EOFError, self.tc.connect, + hostkey=public_host_key, + username='slowdive', + password='pygmalion') -- cgit v1.2.3 From 6ba6ccda7bb34f16e92aa1acfb430055f264bd41 Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Tue, 24 Feb 2015 15:14:51 +0100 Subject: Patch resolving the timeout issue on lost conection. (This rolls in patch in #439) --- paramiko/client.py | 2 +- paramiko/transport.py | 18 +++++++++++++----- sites/www/changelog.rst | 3 +++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/paramiko/client.py b/paramiko/client.py index 393e3e09..9ee30287 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -338,7 +338,7 @@ class SSHClient (ClosingContextManager): :raises SSHException: if the server fails to execute the command """ - chan = self._transport.open_session() + chan = self._transport.open_session(timeout=timeout) if get_pty: chan.get_pty() chan.settimeout(timeout) diff --git a/paramiko/transport.py b/paramiko/transport.py index 6047fb99..31c27a2f 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -589,7 +589,7 @@ class Transport (threading.Thread, ClosingContextManager): """ return self.active - def open_session(self, window_size=None, max_packet_size=None): + def open_session(self, window_size=None, max_packet_size=None, timeout=None): """ Request a new channel to the server, of type ``"session"``. This is just an alias for calling `open_channel` with an argument of @@ -614,7 +614,8 @@ class Transport (threading.Thread, ClosingContextManager): """ return self.open_channel('session', window_size=window_size, - max_packet_size=max_packet_size) + max_packet_size=max_packet_size, + timeout=timeout) def open_x11_channel(self, src_addr=None): """ @@ -661,7 +662,8 @@ class Transport (threading.Thread, ClosingContextManager): dest_addr=None, src_addr=None, window_size=None, - max_packet_size=None): + max_packet_size=None, + timeout=None): """ Request a new channel to the server. `Channels <.Channel>` are socket-like objects used for the actual transfer of data across the @@ -685,17 +687,20 @@ class Transport (threading.Thread, ClosingContextManager): optional window size for this session. :param int max_packet_size: optional max packet size for this session. + :param float timeout: + optional timeout opening a channel, default 3600s (1h) :return: a new `.Channel` on success - :raises SSHException: if the request is rejected or the session ends - prematurely + :raises SSHException: if the request is rejected, the session ends + prematurely or there is a timeout openning a channel .. versionchanged:: 1.15 Added the ``window_size`` and ``max_packet_size`` arguments. """ if not self.active: raise SSHException('SSH session not active') + timeout = 3600 if timeout is None else timeout self.lock.acquire() try: window_size = self._sanitize_window_size(window_size) @@ -724,6 +729,7 @@ class Transport (threading.Thread, ClosingContextManager): finally: self.lock.release() self._send_user_message(m) + start_ts = time.time() while True: event.wait(0.1) if not self.active: @@ -733,6 +739,8 @@ class Transport (threading.Thread, ClosingContextManager): raise e if event.is_set(): break + elif start_ts + timeout < time.time(): + raise SSHException('Timeout openning channel.') chan = self._channels.get(chanid) if chan is not None: return chan diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index f9900327..16a60a68 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +* :bug:`439` Resolve the timeout issue on lost conection. + When the destination disappears on an established session paramiko will hang on trying to open a channel. + Credit to ``@vazir`` for patch. * :bug:`62` Add timeout for handshake completion. This adds a mechanism for timing out a connection if the ssh handshake never completes. -- cgit v1.2.3 From 23dc9d1fdee82b94c40cc86970246e027c0f360e Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Tue, 3 Mar 2015 12:30:37 +0100 Subject: Add a missing import. --- paramiko/auth_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paramiko/auth_handler.py b/paramiko/auth_handler.py index c001aeee..9cf4e271 100644 --- a/paramiko/auth_handler.py +++ b/paramiko/auth_handler.py @@ -34,7 +34,7 @@ from paramiko.common import cMSG_SERVICE_REQUEST, cMSG_DISCONNECT, \ cMSG_USERAUTH_GSSAPI_ERRTOK, cMSG_USERAUTH_GSSAPI_MIC,\ MSG_USERAUTH_GSSAPI_RESPONSE, MSG_USERAUTH_GSSAPI_TOKEN, \ MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, MSG_USERAUTH_GSSAPI_ERROR, \ - MSG_USERAUTH_GSSAPI_ERRTOK, MSG_USERAUTH_GSSAPI_MIC + MSG_USERAUTH_GSSAPI_ERRTOK, MSG_USERAUTH_GSSAPI_MIC, MSG_NAMES from paramiko.message import Message from paramiko.py3compat import bytestring -- cgit v1.2.3 From ee47257684d2b9999743317c1b1bc1a0b135875d Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Tue, 3 Mar 2015 12:59:01 +0100 Subject: Fix the documentation of both implementations of ssh_check_mic. The method raises an exception, if the check fails and has no return value. --- paramiko/ssh_gss.py | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/paramiko/ssh_gss.py b/paramiko/ssh_gss.py index aa28e2ec..e9b13a66 100644 --- a/paramiko/ssh_gss.py +++ b/paramiko/ssh_gss.py @@ -360,8 +360,8 @@ class _SSH_GSSAPI(_SSH_GSSAuth): :param str mic_token: The MIC token received from the client :param str session_id: The SSH session ID :param str username: The name of the user who attempts to login - :return: 0 if the MIC check was successful and 1 if it fails - :rtype: int + :return: None if the MIC check was successful + :raises gssapi.GSSException: if the MIC check failed """ self._session_id = session_id self._username = username @@ -371,11 +371,7 @@ class _SSH_GSSAPI(_SSH_GSSAuth): self._username, self._service, self._auth_method) - try: - self._gss_srv_ctxt.verify_mic(mic_field, - mic_token) - except gssapi.BadSignature: - raise Exception("GSS-API MIC check failed.") + self._gss_srv_ctxt.verify_mic(mic_field, mic_token) else: # for key exchange with gssapi-keyex # client mode @@ -534,31 +530,26 @@ class _SSH_SSPI(_SSH_GSSAuth): :param str mic_token: The MIC token received from the client :param str session_id: The SSH session ID :param str username: The name of the user who attempts to login - :return: 0 if the MIC check was successful - :rtype: int + :return: None if the MIC check was successful + :raises sspi.error: if the MIC check failed """ self._session_id = session_id self._username = username - mic_status = 1 if username is not None: # server mode mic_field = self._ssh_build_mic(self._session_id, self._username, self._service, self._auth_method) - mic_status = self._gss_srv_ctxt.verify(mic_field, - mic_token) + # Verifies data and its signature. If verification fails, an + # sspi.error will be raised. + self._gss_srv_ctxt.verify(mic_field, mic_token) else: # for key exchange with gssapi-keyex # client mode - mic_status = self._gss_ctxt.verify(self._session_id, - mic_token) - """ - The SSPI method C{verify} has no return value, so if no SSPI error - is returned, set C{mic_status} to 0. - """ - mic_status = 0 - return mic_status + # Verifies data and its signature. If verification fails, an + # sspi.error will be raised. + self._gss_ctxt.verify(self._session_id, mic_token) @property def credentials_delegated(self): -- cgit v1.2.3 From ceddde7498a1b88e9662668b037552b71010cf46 Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Tue, 3 Mar 2015 13:00:47 +0100 Subject: Fix an uninitialised variable usage and simplify the code. --- paramiko/auth_handler.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/paramiko/auth_handler.py b/paramiko/auth_handler.py index 9cf4e271..ef4a8c7e 100644 --- a/paramiko/auth_handler.py +++ b/paramiko/auth_handler.py @@ -510,15 +510,11 @@ class AuthHandler (object): result = AUTH_FAILED self._send_auth_result(username, method, result) raise - if retval == 0: - # TODO: Implement client credential saving. - # The OpenSSH server is able to create a TGT with the delegated - # client credentials, but this is not supported by GSS-API. - result = AUTH_SUCCESSFUL - self.transport.server_object.check_auth_gssapi_with_mic( - username, result) - else: - result = AUTH_FAILED + # TODO: Implement client credential saving. + # The OpenSSH server is able to create a TGT with the delegated + # client credentials, but this is not supported by GSS-API. + result = AUTH_SUCCESSFUL + self.transport.server_object.check_auth_gssapi_with_mic(username, result) elif method == "gssapi-keyex" and gss_auth: mic_token = m.get_string() sshgss = self.transport.kexgss_ctxt @@ -534,12 +530,8 @@ class AuthHandler (object): result = AUTH_FAILED self._send_auth_result(username, method, result) raise - if retval == 0: - result = AUTH_SUCCESSFUL - self.transport.server_object.check_auth_gssapi_keyex(username, - result) - else: - result = AUTH_FAILED + result = AUTH_SUCCESSFUL + self.transport.server_object.check_auth_gssapi_keyex(username, result) else: result = self.transport.server_object.check_auth_none(username) # okay, send result -- cgit v1.2.3 From c158db9833c70b2f57db0a58ab6133595c740d6f Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Tue, 3 Mar 2015 13:02:02 +0100 Subject: Switch kex_gss from using PyCrypto's Random to using os.urandom. --- paramiko/kex_gss.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/paramiko/kex_gss.py b/paramiko/kex_gss.py index d026807c..69969f8a 100644 --- a/paramiko/kex_gss.py +++ b/paramiko/kex_gss.py @@ -37,6 +37,7 @@ This module provides GSS-API / SSPI Key Exchange as defined in :rfc:`4462`. .. versionadded:: 1.15 """ +import os from hashlib import sha1 from paramiko.common import * @@ -130,7 +131,7 @@ class KexGSSGroup1(object): larger than q (but this is a tiny tiny subset of potential x). """ while 1: - x_bytes = self.transport.rng.read(128) + x_bytes = os.urandom(128) x_bytes = byte_mask(x_bytes[0], 0x7f) + x_bytes[1:] if (x_bytes[:8] != self.b7fffffffffffffff) and \ (x_bytes[:8] != self.b0000000000000000): @@ -366,7 +367,7 @@ class KexGSSGex(object): qhbyte <<= 1 qmask >>= 1 while True: - x_bytes = self.transport.rng.read(byte_count) + 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): -- cgit v1.2.3 From 2e4d604cdd3d65dd5a826794231ad03839c28d4a Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Wed, 18 Mar 2015 14:49:09 +0100 Subject: Add a missing import. --- paramiko/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paramiko/server.py b/paramiko/server.py index bf5039a2..f79a1748 100644 --- a/paramiko/server.py +++ b/paramiko/server.py @@ -22,7 +22,7 @@ import threading from paramiko import util -from paramiko.common import DEBUG, ERROR, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, AUTH_FAILED +from paramiko.common import DEBUG, ERROR, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, AUTH_FAILED, AUTH_SUCCESSFUL from paramiko.py3compat import string_types -- cgit v1.2.3 From 838e02ab4287b40c6ea043abcd5a0065ef9554c8 Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Fri, 20 Mar 2015 11:52:23 +0100 Subject: According to RFC 4254 sec 6.5 the "command" string of an "exec" channel request is a byte-string. Previously paramiko assumed "command" to be UTF-8 encoded. Invalid UTF-8 sequences caused an UnicodeDecodeError. This commit changes a test case to uses a non UTF-8 string and fixes the bug. --- paramiko/channel.py | 2 +- tests/test_transport.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/paramiko/channel.py b/paramiko/channel.py index 23323650..0acd85ef 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -989,7 +989,7 @@ class Channel (object): else: ok = server.check_channel_env_request(self, name, value) elif key == 'exec': - cmd = m.get_text() + cmd = m.get_string() if server is None: ok = False else: diff --git a/tests/test_transport.py b/tests/test_transport.py index 485a18e8..fb83fd2f 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -246,7 +246,7 @@ class TransportTest(ParamikoTest): chan = self.tc.open_session() schan = self.ts.accept(1.0) try: - chan.exec_command('no') + chan.exec_command(b'command contains \xfc and is not a valid UTF-8 string') self.assertTrue(False) except SSHException: pass -- cgit v1.2.3 From 063c394633567e8afd8980113690311337108c3c Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Fri, 20 Mar 2015 12:59:48 +0100 Subject: Changelog for pull request #502. --- sites/www/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 9ce2eded..50447c04 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,8 @@ Changelog ========= +* :bug:`502` Fix an issue in server mode, when processing an exec request. + A command that is not a valid UTF-8 string, caused an UnicodeDecodeError. * :release:`1.13.3 <2014-12-19>` * :bug:`413` (also :issue:`414`, :issue:`420`, :issue:`454`) Be significantly smarter about polling & timing behavior when running proxy commands, to avoid -- cgit v1.2.3 From 94c20181dd8073e0cdbc83973c87e89c5f472d80 Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Fri, 20 Mar 2015 16:01:51 +0100 Subject: Commit 838e02ab42 changed the type of the exec command string on python3 from unicode to bytes. This commit adapts the test suite accordingly. --- tests/test_client.py | 2 +- tests/test_transport.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 7e5c80b4..1791bed6 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -53,7 +53,7 @@ class NullServer (paramiko.ServerInterface): return paramiko.OPEN_SUCCEEDED def check_channel_exec_request(self, channel, command): - if command != 'yes': + if command != b'yes': return False return True diff --git a/tests/test_transport.py b/tests/test_transport.py index fb83fd2f..93d33099 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -72,7 +72,7 @@ class NullServer (ServerInterface): return OPEN_SUCCEEDED def check_channel_exec_request(self, channel, command): - if command != 'yes': + if command != b'yes': return False return True -- cgit v1.2.3 From 2cea4c78317d0295d5d1a9d309e66c2b35bf0ca9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 May 2015 10:28:07 -0400 Subject: Refresh vendored _winapi module. --- paramiko/_winapi.py | 545 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 321 insertions(+), 224 deletions(-) diff --git a/paramiko/_winapi.py b/paramiko/_winapi.py index f141b005..84ee07fd 100644 --- a/paramiko/_winapi.py +++ b/paramiko/_winapi.py @@ -1,269 +1,366 @@ """ Windows API functions implemented as ctypes functions and classes as found -in jaraco.windows (2.10). +in jaraco.windows (3.3). 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 sys import ctypes.wintypes -import __builtin__ + +import six +from six.moves import builtins + ###################### # 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 - 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(bytes) - message = result_buffer.value - ctypes.windll.kernel32.LocalFree(result_buffer) - return message - - -class WindowsError(__builtin__.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()) + """ + Call FormatMessage with a system error number to retrieve + the descriptive error message. + """ + # first some flags used by FormatMessageW + ALLOCATE_BUFFER = 0x100 + FROM_SYSTEM = 0x1000 + + # 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 + 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(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) + if sys.version_info > (3,3): + args = 0, strerror, None, value + else: + args = value, strerror + super(WindowsError, self).__init__(*args) + + @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() + if result == 0: + raise WindowsError() -##################### -# jaraco.windows.mmap +########################### +# jaraco.windows.api.memory + +GMEM_MOVEABLE = 0x2 + +GlobalAlloc = ctypes.windll.kernel32.GlobalAlloc +GlobalAlloc.argtypes = ctypes.wintypes.UINT, ctypes.c_ssize_t +GlobalAlloc.restype = ctypes.wintypes.HANDLE + +GlobalLock = ctypes.windll.kernel32.GlobalLock +GlobalLock.argtypes = ctypes.wintypes.HGLOBAL, +GlobalLock.restype = ctypes.wintypes.LPVOID + +GlobalUnlock = ctypes.windll.kernel32.GlobalUnlock +GlobalUnlock.argtypes = ctypes.wintypes.HGLOBAL, +GlobalUnlock.restype = ctypes.wintypes.BOOL + +GlobalSize = ctypes.windll.kernel32.GlobalSize +GlobalSize.argtypes = ctypes.wintypes.HGLOBAL, +GlobalSize.restype = ctypes.c_size_t 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, + 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 +##################### +# jaraco.windows.mmap + 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, - unicode(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): - ctypes.windll.msvcrt.memcpy(self.view + self.pos, msg, len(msg)) - self.pos += len(msg) - - def read(self, n): - """ - Read n bytes from mapped view. - """ - out = ctypes.create_string_buffer(n) - ctypes.windll.msvcrt.memcpy(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) + """ + 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, + six.text_type(self.name)) + handle_nonzero_success(filemap) + if filemap == INVALID_HANDLE_VALUE: + raise Exception("Failed to create file mapping") + self.filemap = filemap + self.view = memory.MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0) + return self + + def seek(self, pos): + self.pos = pos + + def write(self, msg): + assert isinstance(msg, bytes) + n = len(msg) + if self.pos + n >= self.length: # A little safety. + raise ValueError("Refusing to write %d bytes" % n) + dest = self.view + self.pos + length = ctypes.wintypes.SIZE(n) + ctypes.windll.kernel32.RtlMoveMemory(dest, msg, length) + self.pos += n + + def read(self, n): + """ + Read n bytes from mapped view. + """ + out = ctypes.create_string_buffer(n) + source = self.view + self.pos + length = ctypes.wintypes.SIZE(n) + ctypes.windll.kernel32.RtlMoveMemory(out, source, length) + 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.api.security + +# from WinNT.h +READ_CONTROL = 0x00020000 +STANDARD_RIGHTS_REQUIRED = 0x000F0000 +STANDARD_RIGHTS_READ = READ_CONTROL +STANDARD_RIGHTS_WRITE = READ_CONTROL +STANDARD_RIGHTS_EXECUTE = READ_CONTROL +STANDARD_RIGHTS_ALL = 0x001F0000 + +# from NTSecAPI.h +POLICY_VIEW_LOCAL_INFORMATION = 0x00000001 +POLICY_VIEW_AUDIT_INFORMATION = 0x00000002 +POLICY_GET_PRIVATE_INFORMATION = 0x00000004 +POLICY_TRUST_ADMIN = 0x00000008 +POLICY_CREATE_ACCOUNT = 0x00000010 +POLICY_CREATE_SECRET = 0x00000020 +POLICY_CREATE_PRIVILEGE = 0x00000040 +POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080 +POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100 +POLICY_AUDIT_LOG_ADMIN = 0x00000200 +POLICY_SERVER_ADMIN = 0x00000400 +POLICY_LOOKUP_NAMES = 0x00000800 +POLICY_NOTIFICATION = 0x00001000 + +POLICY_ALL_ACCESS = ( + STANDARD_RIGHTS_REQUIRED | + POLICY_VIEW_LOCAL_INFORMATION | + POLICY_VIEW_AUDIT_INFORMATION | + POLICY_GET_PRIVATE_INFORMATION | + POLICY_TRUST_ADMIN | + POLICY_CREATE_ACCOUNT | + POLICY_CREATE_SECRET | + POLICY_CREATE_PRIVILEGE | + POLICY_SET_DEFAULT_QUOTA_LIMITS | + POLICY_SET_AUDIT_REQUIREMENTS | + POLICY_AUDIT_LOG_ADMIN | + POLICY_SERVER_ADMIN | + POLICY_LOOKUP_NAMES) + + +POLICY_READ = ( + STANDARD_RIGHTS_READ | + POLICY_VIEW_AUDIT_INFORMATION | + POLICY_GET_PRIVATE_INFORMATION) + +POLICY_WRITE = ( + STANDARD_RIGHTS_WRITE | + POLICY_TRUST_ADMIN | + POLICY_CREATE_ACCOUNT | + POLICY_CREATE_SECRET | + POLICY_CREATE_PRIVILEGE | + POLICY_SET_DEFAULT_QUOTA_LIMITS | + POLICY_SET_AUDIT_REQUIREMENTS | + POLICY_AUDIT_LOG_ADMIN | + POLICY_SERVER_ADMIN) + +POLICY_EXECUTE = ( + STANDARD_RIGHTS_EXECUTE | + POLICY_VIEW_LOCAL_INFORMATION | + POLICY_LOOKUP_NAMES) -######################### -# jaraco.windows.security +class TokenAccess: + TOKEN_QUERY = 0x8 class TokenInformationClass: - TokenUser = 1 + TokenUser = 1 class TOKEN_USER(ctypes.Structure): - num = 1 - _fields_ = [ - ('SID', ctypes.c_void_p), - ('ATTRIBUTES', ctypes.wintypes.DWORD), - ] + 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 = ctypes.wintypes.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), - ] + """ + 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 = ctypes.wintypes.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) + """ + 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) + + @property + def descriptor(self): + return self._descriptor + + @descriptor.setter + def descriptor(self, value): + self._descriptor = value + self.lpSecurityDescriptor = ctypes.addressof(value) -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 +######################### +# jaraco.windows.security -class TokenAccess: - TOKEN_QUERY = 0x8 +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(security.TOKEN_USER)).contents 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 + 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) + """ + Return a TOKEN_USER for the owner of this process. + """ + process = OpenProcessToken( + ctypes.windll.kernel32.GetCurrentProcess(), + security.TokenAccess.TOKEN_QUERY, + ) + return GetTokenInformation(process, security.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 + """ + 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, security.TOKEN_USER), "user must be TOKEN_USER instance" + + SD = security.SECURITY_DESCRIPTOR() + SA = security.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.SECURITY_DESCRIPTOR.REVISION) + ctypes.windll.advapi32.SetSecurityDescriptorOwner(ctypes.byref(SD), + user.SID, 0) + return SA -- cgit v1.2.3 From 9375e451f48b99e0cfeeb28da3c6357732f2bd07 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 May 2015 10:31:34 -0400 Subject: Replace use of six with imports from py3compat --- paramiko/_winapi.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/paramiko/_winapi.py b/paramiko/_winapi.py index 84ee07fd..9ec4d44f 100644 --- a/paramiko/_winapi.py +++ b/paramiko/_winapi.py @@ -9,8 +9,7 @@ in jaraco.windows and asking the author to port the fixes back here. import sys import ctypes.wintypes -import six -from six.moves import builtins +from paramiko.py3compat import u, builtins ###################### @@ -142,7 +141,7 @@ class MemoryMap(object): FILE_MAP_WRITE = 0x2 filemap = ctypes.windll.kernel32.CreateFileMappingW( INVALID_HANDLE_VALUE, p_SA, PAGE_READWRITE, 0, self.length, - six.text_type(self.name)) + u(self.name)) handle_nonzero_success(filemap) if filemap == INVALID_HANDLE_VALUE: raise Exception("Failed to create file mapping") -- cgit v1.2.3 From f2213b8a40c652780c209071574e4c85a8480eb3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 May 2015 10:32:32 -0400 Subject: Remove references to other modules --- paramiko/_winapi.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/paramiko/_winapi.py b/paramiko/_winapi.py index 9ec4d44f..c67c2421 100644 --- a/paramiko/_winapi.py +++ b/paramiko/_winapi.py @@ -146,7 +146,7 @@ class MemoryMap(object): if filemap == INVALID_HANDLE_VALUE: raise Exception("Failed to create file mapping") self.filemap = filemap - self.view = memory.MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0) + self.view = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0) return self def seek(self, pos): @@ -322,7 +322,7 @@ def GetTokenInformation(token, information_class): information_class.num, ctypes.byref(data), ctypes.sizeof(data), ctypes.byref(data_size))) - return ctypes.cast(data, ctypes.POINTER(security.TOKEN_USER)).contents + return ctypes.cast(data, ctypes.POINTER(TOKEN_USER)).contents def OpenProcessToken(proc_handle, access): result = ctypes.wintypes.HANDLE() @@ -337,9 +337,9 @@ def get_current_user(): """ process = OpenProcessToken( ctypes.windll.kernel32.GetCurrentProcess(), - security.TokenAccess.TOKEN_QUERY, + TokenAccess.TOKEN_QUERY, ) - return GetTokenInformation(process, security.TOKEN_USER) + return GetTokenInformation(process, TOKEN_USER) def get_security_attributes_for_user(user=None): """ @@ -349,17 +349,17 @@ def get_security_attributes_for_user(user=None): if user is None: user = get_current_user() - assert isinstance(user, security.TOKEN_USER), "user must be TOKEN_USER instance" + assert isinstance(user, TOKEN_USER), "user must be TOKEN_USER instance" - SD = security.SECURITY_DESCRIPTOR() - SA = security.SECURITY_ATTRIBUTES() + 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.SECURITY_DESCRIPTOR.REVISION) + SECURITY_DESCRIPTOR.REVISION) ctypes.windll.advapi32.SetSecurityDescriptorOwner(ctypes.byref(SD), user.SID, 0) return SA -- cgit v1.2.3 From e7808e11de87127451f25e5cd45f8f764cbcb590 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 May 2015 10:36:57 -0400 Subject: Add builtins to compatibility imports --- paramiko/py3compat.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/paramiko/py3compat.py b/paramiko/py3compat.py index 57c096b2..6fafc31d 100644 --- a/paramiko/py3compat.py +++ b/paramiko/py3compat.py @@ -3,7 +3,7 @@ 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'] + 'b', 'u', 'b2s', 'StringIO', 'BytesIO', 'is_callable', 'MAXSIZE', 'next', 'builtins'] PY2 = sys.version_info[0] < 3 @@ -18,6 +18,8 @@ if PY2: decodebytes = base64.decodestring encodebytes = base64.encodestring + import __builtin__ as builtins + def bytestring(s): # NOQA if isinstance(s, unicode): @@ -102,6 +104,7 @@ if PY2: else: import collections import struct + import builtins string_types = str text_type = str bytes = bytes -- cgit v1.2.3 From a671dafb8775e585086600a1fddd364def5aeb20 Mon Sep 17 00:00:00 2001 From: Torkil Gustavsen Date: Thu, 23 Jul 2015 13:22:39 +0200 Subject: prefetch now requires file_size to be passed in as a parameter Calling stat from inside the prefetch-body has led users to receive IOError: The message [] is not extractable. --- paramiko/sftp_client.py | 5 +++-- paramiko/sftp_file.py | 7 +++---- tests/test_sftp.py | 3 ++- tests/test_sftp_big.py | 18 ++++++++++++------ 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index 89840eaa..e4c3a37d 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -685,9 +685,10 @@ class SFTPClient(BaseSFTP, ClosingContextManager): .. versionadded:: 1.10 """ + file_size = self.stat(remotepath).st_size with self.open(remotepath, 'rb') as fr: - file_size = self.stat(remotepath).st_size - fr.prefetch() + fr.prefetch(file_size) + size = 0 while True: data = fr.read(32768) diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py index d0a37da3..8f73dcbf 100644 --- a/paramiko/sftp_file.py +++ b/paramiko/sftp_file.py @@ -379,7 +379,7 @@ class SFTPFile (BufferedFile): """ self.pipelined = pipelined - def prefetch(self): + def prefetch(self, file_size=None): """ Pre-fetch the remaining contents of this file in anticipation of future `.read` calls. If reading the entire file, pre-fetching can @@ -393,12 +393,11 @@ class SFTPFile (BufferedFile): .. versionadded:: 1.5.1 """ - size = self.stat().st_size # queue up async reads for the rest of the file chunks = [] n = self._realpos - while n < size: - chunk = min(self.MAX_REQUEST_SIZE, size - n) + while n < file_size: + chunk = min(self.MAX_REQUEST_SIZE, file_size - n) chunks.append((n, chunk)) n += chunk if len(chunks) > 0: diff --git a/tests/test_sftp.py b/tests/test_sftp.py index cb8f7f84..55762c21 100755 --- a/tests/test_sftp.py +++ b/tests/test_sftp.py @@ -696,7 +696,8 @@ class SFTPTest (unittest.TestCase): f.readv([(0, 12)]) with sftp.open(FOLDER + '/zero', 'r') as f: - f.prefetch() + file_size = f.stat().st_size + f.prefetch(file_size) f.read(100) finally: sftp.unlink(FOLDER + '/zero') diff --git a/tests/test_sftp_big.py b/tests/test_sftp_big.py index abed27b8..cfad5682 100644 --- a/tests/test_sftp_big.py +++ b/tests/test_sftp_big.py @@ -132,7 +132,8 @@ class BigSFTPTest (unittest.TestCase): start = time.time() with sftp.open('%s/hongry.txt' % FOLDER, 'rb') as f: - f.prefetch() + file_size = f.stat().st_size + f.prefetch(file_size) # read on odd boundaries to make sure the bytes aren't getting scrambled n = 0 @@ -171,7 +172,8 @@ class BigSFTPTest (unittest.TestCase): chunk = 793 for i in range(10): with sftp.open('%s/hongry.txt' % FOLDER, 'rb') as f: - f.prefetch() + file_size = f.stat().st_size + f.prefetch(file_size) base_offset = (512 * 1024) + 17 * random.randint(1000, 2000) offsets = [base_offset + j * chunk for j in range(100)] # randomly seek around and read them out @@ -245,9 +247,11 @@ class BigSFTPTest (unittest.TestCase): for i in range(10): with sftp.open('%s/hongry.txt' % FOLDER, 'r') as f: - f.prefetch() + file_size = f.stat().st_size + f.prefetch(file_size) with sftp.open('%s/hongry.txt' % FOLDER, 'r') as f: - f.prefetch() + file_size = f.stat().st_size + f.prefetch(file_size) for n in range(1024): data = f.read(1024) self.assertEqual(data, kblob) @@ -275,7 +279,8 @@ class BigSFTPTest (unittest.TestCase): self.assertEqual(sftp.stat('%s/hongry.txt' % FOLDER).st_size, 1024 * 1024) with sftp.open('%s/hongry.txt' % FOLDER, 'rb') as f: - f.prefetch() + file_size = f.stat().st_size + f.prefetch(file_size) data = f.read(1024) self.assertEqual(data, kblob) @@ -353,7 +358,8 @@ class BigSFTPTest (unittest.TestCase): # try to read it too. with sftp.open('%s/hongry.txt' % FOLDER, 'r', 128 * 1024) as f: - f.prefetch() + file_size = f.stat().st_size + f.prefetch(file_size) total = 0 while total < 1024 * 1024: total += len(f.read(32 * 1024)) -- cgit v1.2.3 From 49072f3537a8981e9d448c22481a1d2b92c03643 Mon Sep 17 00:00:00 2001 From: Torkil Gustavsen Date: Thu, 23 Jul 2015 14:29:03 +0200 Subject: prefetch's file_size param is not optional --- paramiko/sftp_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py index 8f73dcbf..c5b65488 100644 --- a/paramiko/sftp_file.py +++ b/paramiko/sftp_file.py @@ -379,7 +379,7 @@ class SFTPFile (BufferedFile): """ self.pipelined = pipelined - def prefetch(self, file_size=None): + def prefetch(self, file_size): """ Pre-fetch the remaining contents of this file in anticipation of future `.read` calls. If reading the entire file, pre-fetching can -- cgit v1.2.3 From 7b33770b4786af2508fab11cebe934584fe19ca6 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 21 Sep 2015 17:19:40 -0700 Subject: gratipay no more :( --- sites/shared_conf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sites/shared_conf.py b/sites/shared_conf.py index 4a6a5c4e..99fab315 100644 --- a/sites/shared_conf.py +++ b/sites/shared_conf.py @@ -12,7 +12,6 @@ html_theme_options = { 'description': "A Python implementation of SSHv2.", 'github_user': 'paramiko', 'github_repo': 'paramiko', - 'gratipay_user': 'bitprophet', 'analytics_id': 'UA-18486793-2', 'travis_button': True, } -- cgit v1.2.3 From aef405c9adc3ca087b21836d4a2ee56e05a2b3c4 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 30 Sep 2015 14:02:27 -0700 Subject: Changelog closes #353 --- sites/www/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 6520dde4..be3f5da7 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,10 @@ Changelog ========= +* :bug:`353` (via :issue:`482`) Fix a bug introduced in the Python 3 port + which caused ``OverFlowError`` (and other symptoms) in SFTP functionality. + Thanks to ``@dboreham`` for leading the troubleshooting charge, and to + Scott Maxwell for the final patch. * :bug:`402` Check to see if an SSH agent is actually present before trying to forward it to the remote end. This replaces what was usually a useless ``TypeError`` with a human-readable ``AuthenticationError``. Credit to Ken -- cgit v1.2.3 From a436b9f1087752b60bc31d73306bdac1229d1118 Mon Sep 17 00:00:00 2001 From: Steve Cohen Date: Tue, 17 Feb 2015 13:47:03 -0500 Subject: Typo causes failure if Putty Pageant is running. --- paramiko/_winapi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paramiko/_winapi.py b/paramiko/_winapi.py index d6aabf76..cf4d68e5 100644 --- a/paramiko/_winapi.py +++ b/paramiko/_winapi.py @@ -219,8 +219,8 @@ class SECURITY_ATTRIBUTES(ctypes.Structure): @descriptor.setter def descriptor(self, value): - self._descriptor = descriptor - self.lpSecurityDescriptor = ctypes.addressof(descriptor) + self._descriptor = value + self.lpSecurityDescriptor = ctypes.addressof(value) def GetTokenInformation(token, information_class): """ -- cgit v1.2.3 From e9d65f4199bb6a8589c9a89f8a8d68edd66ac6d0 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 30 Sep 2015 14:09:15 -0700 Subject: Changelog closes #488 --- sites/www/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index be3f5da7..7e8c02fe 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,10 @@ Changelog ========= +* :bug:`469` (also :issue:`488`, :issue:`461` and like a dozen others) Fix a + typo introduced in the 1.15 release which broke WinPageant support. Thanks to + everyone who submitted patches, and to Steve Cohen who was the lucky winner + of the cherry-pick lottery. * :bug:`353` (via :issue:`482`) Fix a bug introduced in the Python 3 port which caused ``OverFlowError`` (and other symptoms) in SFTP functionality. Thanks to ``@dboreham`` for leading the troubleshooting charge, and to -- cgit v1.2.3 From 3bc8fd1a02358a8647bcee11e652ee5aec0bdf97 Mon Sep 17 00:00:00 2001 From: Loic Dachary Date: Thu, 25 Sep 2014 21:32:19 +0200 Subject: Add informative BadHostKeyException Displaying the keys being compared makes it easy to diagnose the problem. Otherwise there is more guessing involved. Signed-off-by: Loic Dachary --- paramiko/ssh_exception.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py index b99e42b3..e120a45e 100644 --- a/paramiko/ssh_exception.py +++ b/paramiko/ssh_exception.py @@ -105,7 +105,11 @@ class BadHostKeyException (SSHException): .. versionadded:: 1.6 """ def __init__(self, hostname, got_key, expected_key): - SSHException.__init__(self, 'Host key for server %s does not match!' % hostname) + SSHException.__init__(self, + 'Host key for server %s does not match : got %s expected %s' % ( + hostname, + got_key.get_base64(), + expected_key.get_base64())) self.hostname = hostname self.key = got_key self.expected_key = expected_key -- cgit v1.2.3 From 48dc72b87567152ac8d45b4bad2bdd0d4ad3ac8b Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 30 Sep 2015 14:14:27 -0700 Subject: Changelog closes #404 --- sites/www/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 7e8c02fe..3c11ff87 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +* :bug:`404` Print details when displaying `BadHostKeyException` objects + (expected vs received data) instead of just "hey shit broke". Patch credit: + Loic Dachary. * :bug:`469` (also :issue:`488`, :issue:`461` and like a dozen others) Fix a typo introduced in the 1.15 release which broke WinPageant support. Thanks to everyone who submitted patches, and to Steve Cohen who was the lucky winner -- cgit v1.2.3 From 669ecbd66f1218e5c93f6a53a25ea062c7b0b947 Mon Sep 17 00:00:00 2001 From: Martin Topholm Date: Mon, 2 Mar 2015 07:17:50 +0100 Subject: Silently ignore invalid keys in HostKeys.load() When broken entries exists in known_hosts, paramiko raises SSHException with "Invalid key". This patch catches the exception during HostKeys.load() and continues to next line. This should fix #490. --- paramiko/hostkeys.py | 6 +++++- tests/test_hostkeys.py | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py index 84868875..7e2d22cf 100644 --- a/paramiko/hostkeys.py +++ b/paramiko/hostkeys.py @@ -19,6 +19,7 @@ import binascii import os +import ssh_exception from hashlib import sha1 from hmac import HMAC @@ -96,7 +97,10 @@ class HostKeys (MutableMapping): line = line.strip() if (len(line) == 0) or (line[0] == '#'): continue - e = HostKeyEntry.from_line(line, lineno) + try: + e = HostKeyEntry.from_line(line, lineno) + except ssh_exception.SSHException: + continue if e is not None: _hostnames = e.hostnames for h in _hostnames: diff --git a/tests/test_hostkeys.py b/tests/test_hostkeys.py index 0ee1bbf0..2bdcad9c 100644 --- a/tests/test_hostkeys.py +++ b/tests/test_hostkeys.py @@ -31,6 +31,7 @@ test_hosts_file = """\ secure.example.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA1PD6U2/TVxET6lkpKhOk5r\ 9q/kAYG6sP9f5zuUYP8i7FOFp/6ncCEbbtg/lB+A3iidyxoSWl+9jtoyyDOOVX4UIDV9G11Ml8om3\ D+jrpI9cycZHqilK0HmxDeCuxbwyMuaCygU9gS2qoRvNLWZk70OpIKSSpBo0Wl3/XUmz9uhc= +broken.example.com ssh-rsa AAAA happy.example.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA8bP1ZA7DCZDB9J0s50l31M\ BGQ3GQ/Fc7SX6gkpXkwcZryoi4kNFhHu5LvHcZPdxXV1D+uTMfGS1eyd2Yz/DoNWXNAl8TI0cAsW\ 5ymME3bQ4J/k1IKxCtz/bAlAqFgKoc+EolMziDYqWIATtW0rYTJvzGAzTmMj80/QpsFH+Pc2M= -- cgit v1.2.3 From fb258f88b4b61627a51f30f9a21fcbc7ec35c1e6 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 30 Sep 2015 14:18:24 -0700 Subject: Changelog closes #490, closes #500 (cherry-pick) --- sites/www/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 3c11ff87..5f6a16f9 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,10 @@ Changelog ========= +* :bug:`490` Skip invalid/unparseable lines in ``known_hosts`` files, instead + of raising `SSHException`. This brings Paramiko's behavior more in line with + OpenSSH, which silently ignores such input. Catch & patch courtesy of Martin + Topholm. * :bug:`404` Print details when displaying `BadHostKeyException` objects (expected vs received data) instead of just "hey shit broke". Patch credit: Loic Dachary. -- cgit v1.2.3 From 0e9b78539da392170dd09d629bcfaef9cf724bc1 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 30 Sep 2015 14:32:05 -0700 Subject: No idea why this only tanked tests on Python 3, ugh @ our old crusty test suite --- paramiko/hostkeys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py index 7e2d22cf..c7e1f72e 100644 --- a/paramiko/hostkeys.py +++ b/paramiko/hostkeys.py @@ -19,7 +19,6 @@ import binascii import os -import ssh_exception from hashlib import sha1 from hmac import HMAC @@ -36,6 +35,7 @@ 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 +from paramiko.ssh_exception import SSHException class HostKeys (MutableMapping): @@ -99,7 +99,7 @@ class HostKeys (MutableMapping): continue try: e = HostKeyEntry.from_line(line, lineno) - except ssh_exception.SSHException: + except SSHException: continue if e is not None: _hostnames = e.hostnames -- cgit v1.2.3 From e287d6b70c5de5470393210623a46741f73a526d Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 30 Sep 2015 14:41:26 -0700 Subject: Don't use --coverage for tests under 3.2 anymore --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9a55dbb6..45fb1722 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,8 +13,8 @@ install: - pip install coveralls # For coveralls.io specifically - pip install -r dev-requirements.txt script: - # Main tests, with coverage! - - inv test --coverage + # Main tests, w/ coverage! (but skip coverage on 3.2, coverage.py dropped it) + - "[[ $TRAVIS_PYTHON_VERSION != 3.2 ]] && inv test --coverage || inv test" # 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 -- cgit v1.2.3 From 57106d04def84ca1d9dd23c4d85b2ba9242556ff Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 30 Sep 2015 14:53:02 -0700 Subject: Rework changelog entries re #491 a bit Closes #491, closes #62, closes #439 --- sites/www/changelog.rst | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 97b6fe9c..764c8801 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,14 +2,10 @@ Changelog ========= -* :bug:`439` Resolve the timeout issue on lost conection. - When the destination disappears on an established session paramiko will hang on trying to open a channel. - Credit to ``@vazir`` for patch. -* :bug:`62` Add timeout for handshake completion. - This adds a mechanism for timing out a connection if the ssh handshake - never completes. - Credit to ``@dacut`` for initial report and patch and to Olle Lundberg for - re-implementation. +* :bug:`491` (combines :issue:`62` and :issue:`439`) Implement timeout + functionality to address hangs from dropped network connections and/or failed + handshakes. Credit to ``@vazir`` and ``@dacut`` for the original patches and + to Olle Lundberg for reimplementation. * :bug:`490` Skip invalid/unparseable lines in ``known_hosts`` files, instead of raising `SSHException`. This brings Paramiko's behavior more in line with OpenSSH, which silently ignores such input. Catch & patch courtesy of Martin -- cgit v1.2.3 From 8bf03014128b074bf6988100f18e48a94671cca2 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 30 Sep 2015 15:43:59 -0700 Subject: Changelog re #496 --- sites/www/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 764c8801..5b900c61 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +* :bug:`496` Fix a handful of small but critical bugs in Paramiko's GSSAPI + support (note: this includes switching from PyCrypo's Random to + `os.urandom`). Thanks to Anselm Kruis for catch & patch. * :bug:`491` (combines :issue:`62` and :issue:`439`) Implement timeout functionality to address hangs from dropped network connections and/or failed handshakes. Credit to ``@vazir`` and ``@dacut`` for the original patches and -- cgit v1.2.3 From 93694bb9ed4e28673586fd18ffce0fc0e925df1f Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 30 Sep 2015 15:57:59 -0700 Subject: Add docstring to AgentRequestHandler so it shows up in the docs. --- paramiko/agent.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/paramiko/agent.py b/paramiko/agent.py index f928881e..6a8e7fb4 100644 --- a/paramiko/agent.py +++ b/paramiko/agent.py @@ -290,6 +290,26 @@ class AgentServerProxy(AgentSSH): class AgentRequestHandler(object): + """ + Primary/default implementation of SSH agent forwarding functionality. + + Simply instantiate this class, handing it a live command-executing session + object, and it will handle forwarding any local SSH agent processes it + finds. + + For example:: + + # Connect + client = SSHClient() + client.connect(host, port, username) + # Obtain session + session = client.get_transport().open_session() + # Forward local agent + AgentRequestHandler(session) + # Commands executed after this point will see the forwarded agent on + # the remote end. + session.exec_command("git clone https://my.git.repository/") + """ def __init__(self, chanClient): self._conn = None self.__chanC = chanClient -- cgit v1.2.3 From b67ee80ba6cbb985a537123a0ae099b81ddfc999 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 30 Sep 2015 15:59:04 -0700 Subject: Changelog closes #516 --- sites/www/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 5b900c61..b7f19d63 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,8 @@ Changelog ========= +* :support:`516 backported` Document `~paramiko.agent.AgentRequestHandler`. + Thanks to ``@toejough`` for report & suggestions. * :bug:`496` Fix a handful of small but critical bugs in Paramiko's GSSAPI support (note: this includes switching from PyCrypo's Random to `os.urandom`). Thanks to Anselm Kruis for catch & patch. -- cgit v1.2.3 From 150960800dd5cf260a9932df53847d2d029c3656 Mon Sep 17 00:00:00 2001 From: Jared Hance Date: Sun, 5 Jul 2015 10:18:34 -0700 Subject: Fix ECDSA generate documentation. Was a blatant copy of a comment from RSAKey. --- paramiko/ecdsakey.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/paramiko/ecdsakey.py b/paramiko/ecdsakey.py index 6b047959..8827a1db 100644 --- a/paramiko/ecdsakey.py +++ b/paramiko/ecdsakey.py @@ -129,13 +129,11 @@ class ECDSAKey (PKey): @staticmethod def generate(curve=curves.NIST256p, progress_func=None): """ - Generate a new private RSA key. This factory function can be used to + Generate a new private ECDSA key. This factory function can be used to generate a new host key or authentication key. - :param function progress_func: - an optional function to call at key points in key generation (used - by ``pyCrypto.PublicKey``). - :returns: A new private key (`.RSAKey`) object + :param function progress_func: Not used for this type of key. + :returns: A new private key (`.ECDSAKey`) object """ signing_key = SigningKey.generate(curve) key = ECDSAKey(vals=(signing_key, signing_key.get_verifying_key())) -- cgit v1.2.3 From a8ac9e6441030f2cc49de579c3d598e5f05ca331 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 2 Oct 2015 15:23:16 -0700 Subject: Changelog closes #554 --- sites/www/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index b7f19d63..1d3debb7 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,8 @@ Changelog ========= +* :support:`554 backported` Fix inaccuracies in the docstring for the ECDSA key + class. Thanks to Jared Hance for the patch. * :support:`516 backported` Document `~paramiko.agent.AgentRequestHandler`. Thanks to ``@toejough`` for report & suggestions. * :bug:`496` Fix a handful of small but critical bugs in Paramiko's GSSAPI -- cgit v1.2.3 From 9a5fbad601d7567cde59071f36ba6a34d6bcf696 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 2 Oct 2015 15:56:28 -0700 Subject: Fix some typos/bad doc references in changelog --- sites/www/changelog.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 1d3debb7..9a4e6c76 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -14,12 +14,12 @@ Changelog handshakes. Credit to ``@vazir`` and ``@dacut`` for the original patches and to Olle Lundberg for reimplementation. * :bug:`490` Skip invalid/unparseable lines in ``known_hosts`` files, instead - of raising `SSHException`. This brings Paramiko's behavior more in line with - OpenSSH, which silently ignores such input. Catch & patch courtesy of Martin - Topholm. -* :bug:`404` Print details when displaying `BadHostKeyException` objects - (expected vs received data) instead of just "hey shit broke". Patch credit: - Loic Dachary. + of raising `~paramiko.ssh_exception.SSHException`. This brings Paramiko's + behavior more in line with OpenSSH, which silently ignores such input. Catch + & patch courtesy of Martin Topholm. +* :bug:`404` Print details when displaying + `~paramiko.ssh_exception.BadHostKeyException` objects (expected vs received + data) instead of just "hey shit broke". Patch credit: Loic Dachary. * :bug:`469` (also :issue:`488`, :issue:`461` and like a dozen others) Fix a typo introduced in the 1.15 release which broke WinPageant support. Thanks to everyone who submitted patches, and to Steve Cohen who was the lucky winner @@ -30,8 +30,9 @@ Changelog Scott Maxwell for the final patch. * :bug:`402` Check to see if an SSH agent is actually present before trying to forward it to the remote end. This replaces what was usually a useless - ``TypeError`` with a human-readable ``AuthenticationError``. Credit to Ken - Jordan for the fix and Yvan Marques for original report. + ``TypeError`` with a human-readable + `~paramiko.ssh_exception.AuthenticationException`. Credit to Ken Jordan for + the fix and Yvan Marques for original report. * :release:`1.15.2 <2014-12-19>` * :release:`1.14.2 <2014-12-19>` * :release:`1.13.3 <2014-12-19>` -- cgit v1.2.3 From 5b1b13c2fb48ac55d64022212bf132b8c01ce0c7 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 2 Oct 2015 15:59:15 -0700 Subject: Cut 1.15.3 --- paramiko/_version.py | 2 +- sites/www/changelog.rst | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/paramiko/_version.py b/paramiko/_version.py index 3bf9dac7..25aac14f 100644 --- a/paramiko/_version.py +++ b/paramiko/_version.py @@ -1,2 +1,2 @@ -__version_info__ = (1, 15, 2) +__version_info__ = (1, 15, 3) __version__ = '.'.join(map(str, __version_info__)) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 9a4e6c76..d94d5bc2 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,7 @@ Changelog ========= +* :release:`1.15.3 <2015-10-02>` * :support:`554 backported` Fix inaccuracies in the docstring for the ECDSA key class. Thanks to Jared Hance for the patch. * :support:`516 backported` Document `~paramiko.agent.AgentRequestHandler`. -- cgit v1.2.3 From 985df357ec039bcb28f6f260e64e9838204506f6 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 2 Oct 2015 16:20:27 -0700 Subject: Fix dumb bug in release task --- tasks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 3d575670..7c920daf 100644 --- a/tasks.py +++ b/tasks.py @@ -30,7 +30,8 @@ def release(ctx): # Move the built docs into where Epydocs used to live target = 'docs' rmtree(target, ignore_errors=True) - copytree(docs_build, target) + # TODO: make it easier to yank out this config val from the docs coll + copytree('sites/docs/_build', target) # Publish publish(ctx) # Remind -- cgit v1.2.3 From 53e91cc449ff3070cd57af2ed317a17a47d378e1 Mon Sep 17 00:00:00 2001 From: gshaanan Date: Thu, 15 Oct 2015 13:51:12 +0300 Subject: This adds SHA-256 support based on a fork of 'zamiam69:add_sha2_support'. This commit fixes the problem with the fork where you can't connect using SHA1 anymore. The fix changes the '_preferred_macs' and '_preferred_kex' order. --- paramiko/transport.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/paramiko/transport.py b/paramiko/transport.py index 9009f9f5..5ab5ac95 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -96,13 +96,13 @@ class Transport (threading.Thread, ClosingContextManager): _preferred_ciphers = ('aes128-ctr', 'aes256-ctr', 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc', 'arcfour128', 'arcfour256') - _preferred_macs = ('hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96', - 'hmac-sha2-256') + _preferred_macs = ('hmac-sha2-256', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96', + 'hmac-sha1') _preferred_keys = ('ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256') - _preferred_kex = ('diffie-hellman-group-exchange-sha256', + _preferred_kex = ('diffie-hellman-group1-sha1', 'diffie-hellman-group14-sha1', - 'diffie-hellman-group-exchange-sha1' , - 'diffie-hellman-group1-sha1') + 'diffie-hellman-group-exchange-sha1' , + 'diffie-hellman-group-exchange-sha256') _preferred_compression = ('none',) _cipher_info = { -- cgit v1.2.3 From ae1cb8fe2cf39fea0ee441dac037931c5e4bc23e Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 30 Oct 2015 11:43:43 -0700 Subject: Whitespace trims --- paramiko/transport.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paramiko/transport.py b/paramiko/transport.py index ef57108a..e0b7c296 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -100,8 +100,8 @@ class Transport (threading.Thread, ClosingContextManager): 'hmac-sha1') _preferred_keys = ('ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256') _preferred_kex = ('diffie-hellman-group1-sha1', - 'diffie-hellman-group14-sha1', - 'diffie-hellman-group-exchange-sha1' , + 'diffie-hellman-group14-sha1', + 'diffie-hellman-group-exchange-sha1', 'diffie-hellman-group-exchange-sha256') _preferred_compression = ('none',) @@ -1772,7 +1772,7 @@ class Transport (threading.Thread, ClosingContextManager): kex_mp = [k for k in self._preferred_kex if k.startswith(mp_required_prefix)] if (self._modulus_pack is None) and (len(kex_mp) > 0): # can't do group-exchange if we don't have a pack of potential primes - pkex = [k for k in self.get_security_options().kex + pkex = [k for k in self.get_security_options().kex if not k.startswith(mp_required_prefix)] self.get_security_options().kex = pkex available_server_keys = list(filter(list(self.server_key_dict.keys()).__contains__, -- cgit v1.2.3 From 439876b38cb8caf38ed957bdf0caeeeedfac740d Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 30 Oct 2015 11:45:57 -0700 Subject: Explicitly log agreed-upon MAC --- paramiko/transport.py | 1 + 1 file changed, 1 insertion(+) diff --git a/paramiko/transport.py b/paramiko/transport.py index e0b7c296..5d6ffa67 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -1873,6 +1873,7 @@ class Transport (threading.Thread, ClosingContextManager): raise SSHException('Incompatible ssh server (no acceptable macs)') self.local_mac = agreed_local_macs[0] self.remote_mac = agreed_remote_macs[0] + self._log(DEBUG, 'MACs agreed: local=%s, remote=%s' % (self.local_mac, self.remote_mac)) if self.server_mode: agreed_remote_compression = list(filter(self._preferred_compression.__contains__, client_compress_algo_list)) -- cgit v1.2.3 From c06927016f0b3e51ba725b57e6f20f0f5c74dbc7 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 30 Oct 2015 12:21:59 -0700 Subject: Formatting --- paramiko/packet.py | 3 ++- paramiko/transport.py | 16 +++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/paramiko/packet.py b/paramiko/packet.py index b922000c..2be2bb2b 100644 --- a/paramiko/packet.py +++ b/paramiko/packet.py @@ -389,7 +389,8 @@ class Packetizer (object): if self.__dump_packets: 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 contains decrypted bytes from the first block (after the + # length field) leftover = header[4:] if (packet_size - len(leftover)) % self.__block_size_in != 0: raise SSHException('Invalid packet blocking') diff --git a/paramiko/transport.py b/paramiko/transport.py index 5d6ffa67..7d0857c8 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -1599,9 +1599,11 @@ class Transport (threading.Thread, ClosingContextManager): try: self.packetizer.write_all(b(self.local_version + '\r\n')) self._check_banner() - # The above is actually very much part of the handshake, but sometimes the banner can be read - # but the machine is not responding, for example when the remote ssh daemon is loaded in to memory - # but we can not read from the disk/spawn a new shell. + # The above is actually very much part of the handshake, but + # sometimes the banner can be read but the machine is not + # responding, for example when the remote ssh daemon is loaded + # in to memory but we can not read from the disk/spawn a new + # shell. # Make sure we can specify a timeout for the initial handshake. # Re-use the banner timeout for now. self.packetizer.start_handshake(self.handshake_timeout) @@ -1909,8 +1911,8 @@ class Transport (threading.Thread, ClosingContextManager): engine = self._get_cipher(self.remote_cipher, key_in, IV_in) mac_size = self._mac_info[self.remote_mac]['size'] mac_engine = self._mac_info[self.remote_mac]['class'] - # initial mac keys are done in the hash's natural size (not the potentially truncated - # transmission size) + # initial mac keys are done in the hash's natural size (not the + # potentially truncated transmission size) if self.server_mode: mac_key = self._compute_key('E', mac_engine().digest_size) else: @@ -1936,8 +1938,8 @@ class Transport (threading.Thread, ClosingContextManager): engine = self._get_cipher(self.local_cipher, key_out, IV_out) mac_size = self._mac_info[self.local_mac]['size'] mac_engine = self._mac_info[self.local_mac]['class'] - # initial mac keys are done in the hash's natural size (not the potentially truncated - # transmission size) + # initial mac keys are done in the hash's natural size (not the + # potentially truncated transmission size) if self.server_mode: mac_key = self._compute_key('F', mac_engine().digest_size) else: -- cgit v1.2.3 From 013ec610643f56f737fe7d1d4ac0f33f09bb3f4a Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 1 Nov 2015 11:51:41 -0800 Subject: Log identificatin-string exchange at DEBUG --- paramiko/transport.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/paramiko/transport.py b/paramiko/transport.py index 7d0857c8..2f90cdfa 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -1598,6 +1598,7 @@ class Transport (threading.Thread, ClosingContextManager): try: try: self.packetizer.write_all(b(self.local_version + '\r\n')) + self._log(DEBUG, 'Local version/idstring: %s' % self.local_version) self._check_banner() # The above is actually very much part of the handshake, but # sometimes the banner can be read but the machine is not @@ -1742,6 +1743,7 @@ class Transport (threading.Thread, ClosingContextManager): raise SSHException('Indecipherable protocol version "' + buf + '"') # save this server version string for later self.remote_version = buf + self._log(DEBUG, 'Remote version/idstring: %s' % buf) # pull off any attached comment comment = '' i = buf.find(' ') -- cgit v1.2.3 From 380aaa3631a0b2a197987d0c5c6163be418e128d Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 1 Nov 2015 13:25:58 -0800 Subject: Reformat algorithm tuples for readability, & add ordering comment --- paramiko/transport.py | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/paramiko/transport.py b/paramiko/transport.py index 2f90cdfa..e2a0c0ff 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -94,15 +94,36 @@ class Transport (threading.Thread, ClosingContextManager): _PROTO_ID = '2.0' _CLIENT_ID = 'paramiko_%s' % paramiko.__version__ - _preferred_ciphers = ('aes128-ctr', 'aes256-ctr', 'aes128-cbc', 'blowfish-cbc', - 'aes256-cbc', '3des-cbc', 'arcfour128', 'arcfour256') - _preferred_macs = ('hmac-sha2-256', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96', - 'hmac-sha1') - _preferred_keys = ('ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256') - _preferred_kex = ('diffie-hellman-group1-sha1', - 'diffie-hellman-group14-sha1', - 'diffie-hellman-group-exchange-sha1', - 'diffie-hellman-group-exchange-sha256') + # These tuples of algorithm identifiers are in preference order; do not + # reorder without reason! + _preferred_ciphers = ( + 'aes128-ctr', + 'aes256-ctr', + 'aes128-cbc', + 'blowfish-cbc', + 'aes256-cbc', + '3des-cbc', + 'arcfour128', + 'arcfour256', + ) + _preferred_macs = ( + 'hmac-sha2-256', + 'hmac-md5', + 'hmac-sha1-96', + 'hmac-md5-96', + 'hmac-sha1', + ) + _preferred_keys = ( + 'ssh-rsa', + 'ssh-dss', + 'ecdsa-sha2-nistp256', + ) + _preferred_kex = ( + 'diffie-hellman-group1-sha1', + 'diffie-hellman-group14-sha1', + 'diffie-hellman-group-exchange-sha1', + 'diffie-hellman-group-exchange-sha256', + ) _preferred_compression = ('none',) _cipher_info = { -- cgit v1.2.3 From 347f948bcfc5db180a49c7d06c1b67f24b75ad21 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 1 Nov 2015 14:37:29 -0800 Subject: Fix incorrect hash algorithm selection during kex key generation. Re #356 --- paramiko/kex_group1.py | 1 + paramiko/kex_group14.py | 2 ++ paramiko/transport.py | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/paramiko/kex_group1.py b/paramiko/kex_group1.py index a88f00d2..9eee066c 100644 --- a/paramiko/kex_group1.py +++ b/paramiko/kex_group1.py @@ -45,6 +45,7 @@ class KexGroup1(object): G = 2 name = 'diffie-hellman-group1-sha1' + hash_algo = sha1 def __init__(self, transport): self.transport = transport diff --git a/paramiko/kex_group14.py b/paramiko/kex_group14.py index a914aeaf..9f7dd216 100644 --- a/paramiko/kex_group14.py +++ b/paramiko/kex_group14.py @@ -22,6 +22,7 @@ Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of """ from paramiko.kex_group1 import KexGroup1 +from hashlib import sha1 class KexGroup14(KexGroup1): @@ -31,3 +32,4 @@ class KexGroup14(KexGroup1): G = 2 name = 'diffie-hellman-group14-sha1' + hash_algo = sha1 diff --git a/paramiko/transport.py b/paramiko/transport.py index e2a0c0ff..d2fccf07 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -1534,7 +1534,7 @@ class Transport (threading.Thread, ClosingContextManager): m.add_bytes(self.H) m.add_byte(b(id)) m.add_bytes(self.session_id) - hash_algo = self._mac_info[self.local_mac]['class'] if self.local_mac else sha1 + hash_algo = self.kex_engine.hash_algo out = sofar = hash_algo(m.asbytes()).digest() while len(out) < nbytes: m = Message() -- cgit v1.2.3 From 9b745412a9b36dffe3ba5ad7304e202a500e77fb Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 1 Nov 2015 15:09:12 -0800 Subject: Allow specifying test.py flags in 'inv test' --- tasks.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tasks.py b/tasks.py index 7c920daf..05654d3b 100644 --- a/tasks.py +++ b/tasks.py @@ -9,11 +9,12 @@ from invocations.packaging import publish # Until we move to spec-based testing @task -def test(ctx, coverage=False): +def test(ctx, coverage=False, flags=""): + if "--verbose" not in flags.split(): + flags += " --verbose" runner = "python" if coverage: runner = "coverage run --source=paramiko" - flags = "--verbose" ctx.run("{0} test.py {1}".format(runner, flags), pty=True) -- cgit v1.2.3 From aeb28073e95557122d7325f7addac9eca7b07e7f Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 1 Nov 2015 15:48:44 -0800 Subject: Fallback to sha1 in _compute_key --- paramiko/transport.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/paramiko/transport.py b/paramiko/transport.py index d2fccf07..98b810ee 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -1534,7 +1534,9 @@ class Transport (threading.Thread, ClosingContextManager): m.add_bytes(self.H) m.add_byte(b(id)) m.add_bytes(self.session_id) - hash_algo = self.kex_engine.hash_algo + # Fallback to SHA1 for kex engines that fail to specify a hex + # algorithm, or for e.g. transport tests that don't run kexinit. + hash_algo = getattr(self.kex_engine, 'hex_algo', sha1) out = sofar = hash_algo(m.asbytes()).digest() while len(out) < nbytes: m = Message() -- cgit v1.2.3 From 66ff4deabbd1c14df3fd2d8729107d904c30c7d5 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 1 Nov 2015 16:04:58 -0800 Subject: Changelog closes #356, closes #596. Will expand to include SHA512 stuff if I merge that prior to release. --- sites/www/changelog.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index ff05365c..833560af 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,15 @@ Changelog ========= +* :feature:`356` (also :issue:`596`, :issue:`365`, :issue:`341`, :issue:`164`, + and a bunch of other duplicates besides) Add support for 256-bit SHA-2 based + key exchange (kex) algorithm ``diffie-hellman-group-exchange-sha256`` and + (H)MAC algorithm ``hmac-sha2-256``. + + Thanks to the many people who submitted patches for this functionality and/or + assisted in testing those patches. That list includes but is not limited to, + and in no particular order: Matthias Witte, Dag Wieers, Ash Berlin, Etienne + Perot, Gert van Dijk, ``@GuyShaanan``, Aaron Bieber, and ``@cyphase``. * :release:`1.15.3 <2015-10-02>` * :support:`554 backported` Fix inaccuracies in the docstring for the ECDSA key class. Thanks to Jared Hance for the patch. -- cgit v1.2.3 From e937e37d2559064d3f746e6b969546f44b858e52 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 1 Nov 2015 16:35:41 -0800 Subject: What a dumb-as-rocks typo --- paramiko/transport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paramiko/transport.py b/paramiko/transport.py index 98b810ee..773a8769 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -1536,7 +1536,7 @@ class Transport (threading.Thread, ClosingContextManager): m.add_bytes(self.session_id) # Fallback to SHA1 for kex engines that fail to specify a hex # algorithm, or for e.g. transport tests that don't run kexinit. - hash_algo = getattr(self.kex_engine, 'hex_algo', sha1) + hash_algo = getattr(self.kex_engine, 'hash_algo', None) out = sofar = hash_algo(m.asbytes()).digest() while len(out) < nbytes: m = Message() -- cgit v1.2.3 From aeff78b28c50844cd15d4774e74e912bcae6f2ca Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 1 Nov 2015 16:35:55 -0800 Subject: Log one DEBUG message per key-compute hash algo selection --- paramiko/transport.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/paramiko/transport.py b/paramiko/transport.py index 773a8769..bca4d37e 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -1537,6 +1537,13 @@ class Transport (threading.Thread, ClosingContextManager): # Fallback to SHA1 for kex engines that fail to specify a hex # algorithm, or for e.g. transport tests that don't run kexinit. hash_algo = getattr(self.kex_engine, 'hash_algo', None) + hash_select_msg = "kex engine %s specified hash_algo %r" % (self.kex_engine.__class__.__name__, hash_algo) + if hash_algo is None: + hash_algo = sha1 + hash_select_msg += ", falling back to sha1" + if not hasattr(self, '_logged_hash_selection'): + self._log(DEBUG, hash_select_msg) + setattr(self, '_logged_hash_selection', True) out = sofar = hash_algo(m.asbytes()).digest() while len(out) < nbytes: m = Message() -- cgit v1.2.3 From 445d739757c927e0bde8cb4156122e90ac1d486a Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 1 Nov 2015 18:16:50 -0800 Subject: Formatting --- paramiko/transport.py | 72 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/paramiko/transport.py b/paramiko/transport.py index bca4d37e..5f170ffd 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -127,14 +127,54 @@ class Transport (threading.Thread, ClosingContextManager): _preferred_compression = ('none',) _cipher_info = { - 'aes128-ctr': {'class': AES, 'mode': AES.MODE_CTR, 'block-size': 16, 'key-size': 16}, - 'aes256-ctr': {'class': AES, 'mode': AES.MODE_CTR, 'block-size': 16, 'key-size': 32}, - 'blowfish-cbc': {'class': Blowfish, 'mode': Blowfish.MODE_CBC, 'block-size': 8, 'key-size': 16}, - 'aes128-cbc': {'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 16}, - 'aes256-cbc': {'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 32}, - '3des-cbc': {'class': DES3, 'mode': DES3.MODE_CBC, 'block-size': 8, 'key-size': 24}, - 'arcfour128': {'class': ARC4, 'mode': None, 'block-size': 8, 'key-size': 16}, - 'arcfour256': {'class': ARC4, 'mode': None, 'block-size': 8, 'key-size': 32}, + 'aes128-ctr': { + 'class': AES, + 'mode': AES.MODE_CTR, + 'block-size': 16, + 'key-size': 16 + }, + 'aes256-ctr': { + 'class': AES, + 'mode': AES.MODE_CTR, + 'block-size': 16, + 'key-size': 32 + }, + 'blowfish-cbc': { + 'class': Blowfish, + 'mode': Blowfish.MODE_CBC, + 'block-size': 8, + 'key-size': 16 + }, + 'aes128-cbc': { + 'class': AES, + 'mode': AES.MODE_CBC, + 'block-size': 16, + 'key-size': 16 + }, + 'aes256-cbc': { + 'class': AES, + 'mode': AES.MODE_CBC, + 'block-size': 16, + 'key-size': 32 + }, + '3des-cbc': { + 'class': DES3, + 'mode': DES3.MODE_CBC, + 'block-size': 8, + 'key-size': 24 + }, + 'arcfour128': { + 'class': ARC4, + 'mode': None, + 'block-size': 8, + 'key-size': 16 + }, + 'arcfour256': { + 'class': ARC4, + 'mode': None, + 'block-size': 8, + 'key-size': 32 + }, } _mac_info = { @@ -1859,12 +1899,20 @@ class Transport (threading.Thread, ClosingContextManager): ' server lang:' + str(server_lang_list) + ' kex follows?' + str(kex_follows)) - # as a server, we pick the first item in the client's list that we support. - # as a client, we pick the first item in our list that the server supports. + # as a server, we pick the first item in the client's list that we + # support. + # as a client, we pick the first item in our list that the server + # supports. if self.server_mode: - agreed_kex = list(filter(self._preferred_kex.__contains__, kex_algo_list)) + agreed_kex = list(filter( + self._preferred_kex.__contains__, + kex_algo_list + )) else: - agreed_kex = list(filter(kex_algo_list.__contains__, self._preferred_kex)) + agreed_kex = list(filter( + kex_algo_list.__contains__, + self._preferred_kex + )) if len(agreed_kex) == 0: raise SSHException('Incompatible ssh peer (no acceptable kex algorithm)') self.kex_engine = self._kex_info[agreed_kex[0]](self) -- cgit v1.2.3 From 375ab8b8475081dfdd8c92ae3a6953d8dbbe6297 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 1 Nov 2015 18:17:20 -0800 Subject: Implement SHA-2 512bit HMAC support from #581 --- paramiko/transport.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/paramiko/transport.py b/paramiko/transport.py index 5f170ffd..b8ad8424 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -26,7 +26,7 @@ import sys import threading import time import weakref -from hashlib import md5, sha1, sha256 +from hashlib import md5, sha1, sha256, sha512 import paramiko from paramiko import util @@ -108,6 +108,7 @@ class Transport (threading.Thread, ClosingContextManager): ) _preferred_macs = ( 'hmac-sha2-256', + 'hmac-sha2-512', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96', @@ -181,6 +182,7 @@ class Transport (threading.Thread, ClosingContextManager): 'hmac-sha1': {'class': sha1, 'size': 20}, 'hmac-sha1-96': {'class': sha1, 'size': 12}, 'hmac-sha2-256': {'class': sha256, 'size': 32}, + 'hmac-sha2-512': {'class': sha512, 'size': 64}, 'hmac-md5': {'class': md5, 'size': 16}, 'hmac-md5-96': {'class': md5, 'size': 12}, } -- cgit v1.2.3 From 5a89ea28105ea7e6caad861e64b8aa4f2ffc7394 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 1 Nov 2015 18:19:16 -0800 Subject: Update changelog closing #581 --- sites/www/changelog.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 833560af..f1e33bcf 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -3,14 +3,15 @@ Changelog ========= * :feature:`356` (also :issue:`596`, :issue:`365`, :issue:`341`, :issue:`164`, - and a bunch of other duplicates besides) Add support for 256-bit SHA-2 based - key exchange (kex) algorithm ``diffie-hellman-group-exchange-sha256`` and - (H)MAC algorithm ``hmac-sha2-256``. + :issue:`581`, and a bunch of other duplicates besides) Add support for SHA-2 + based key exchange (kex) algorithm ``diffie-hellman-group-exchange-sha256`` + and (H)MAC algorithms ``hmac-sha2-256`` and ``hmac-sha2-512``. Thanks to the many people who submitted patches for this functionality and/or assisted in testing those patches. That list includes but is not limited to, and in no particular order: Matthias Witte, Dag Wieers, Ash Berlin, Etienne - Perot, Gert van Dijk, ``@GuyShaanan``, Aaron Bieber, and ``@cyphase``. + Perot, Gert van Dijk, ``@GuyShaanan``, Aaron Bieber, ``@cyphase``, and Eric + Brown. * :release:`1.15.3 <2015-10-02>` * :support:`554 backported` Fix inaccuracies in the docstring for the ECDSA key class. Thanks to Jared Hance for the patch. -- cgit v1.2.3 From bd58eb6ab221a52e2a8c76aa1f24eac5a618f4d7 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 1 Nov 2015 18:20:23 -0800 Subject: Explicitly log kex and compression algorithms --- paramiko/transport.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/paramiko/transport.py b/paramiko/transport.py index b8ad8424..1d3b7c78 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -1918,6 +1918,7 @@ class Transport (threading.Thread, ClosingContextManager): if len(agreed_kex) == 0: raise SSHException('Incompatible ssh peer (no acceptable kex algorithm)') self.kex_engine = self._kex_info[agreed_kex[0]](self) + self._log(DEBUG, "Kex agreed: %s" % agreed_kex[0]) if self.server_mode: available_server_keys = list(filter(list(self.server_key_dict.keys()).__contains__, @@ -1969,6 +1970,7 @@ class Transport (threading.Thread, ClosingContextManager): raise SSHException('Incompatible ssh server (no acceptable compression) %r %r %r' % (agreed_local_compression, agreed_remote_compression, self._preferred_compression)) self.local_compression = agreed_local_compression[0] self.remote_compression = agreed_remote_compression[0] + self._log(DEBUG, 'Compressions agreed: local=%s' % self.local_compression) self._log(DEBUG, 'using kex %s; server key type %s; cipher: local %s, remote %s; mac: local %s, remote %s; compression: local %s, remote %s' % (agreed_kex[0], self.host_key_type, self.local_cipher, self.remote_cipher, self.local_mac, -- cgit v1.2.3 From e2bce29615c7418b51995aa008bc8c79849971b0 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 1 Nov 2015 18:21:34 -0800 Subject: Remove aggregated algorithms log line. All constituent parts now have their own log lines which are printed closer to when the handshakes occur. More accurate and more readable. --- paramiko/transport.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/paramiko/transport.py b/paramiko/transport.py index 1d3b7c78..324ed277 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -1972,10 +1972,6 @@ class Transport (threading.Thread, ClosingContextManager): self.remote_compression = agreed_remote_compression[0] self._log(DEBUG, 'Compressions agreed: local=%s' % self.local_compression) - self._log(DEBUG, 'using kex %s; server key type %s; cipher: local %s, remote %s; mac: local %s, remote %s; compression: local %s, remote %s' % - (agreed_kex[0], self.host_key_type, self.local_cipher, self.remote_cipher, self.local_mac, - self.remote_mac, self.local_compression, self.remote_compression)) - # save for computing hash later... # now wait! openssh has a bug (and others might too) where there are # actually some extra bytes (one NUL byte in openssh's case) added to -- cgit v1.2.3 From 9c12f12a08f25a2135e0f17832d2acdc8bafbf1b Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 1 Nov 2015 18:24:37 -0800 Subject: Add note re: logging tweaks to changelog. Better safe than sorry. --- sites/www/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index f1e33bcf..3aa2b84b 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -6,6 +6,10 @@ Changelog :issue:`581`, and a bunch of other duplicates besides) Add support for SHA-2 based key exchange (kex) algorithm ``diffie-hellman-group-exchange-sha256`` and (H)MAC algorithms ``hmac-sha2-256`` and ``hmac-sha2-512``. + + This change includes tweaks to debug-level logging regarding + algorithm-selection handshakes; the old all-in-one log line is now multiple + easier-to-read, printed-at-handshake-time log lines. Thanks to the many people who submitted patches for this functionality and/or assisted in testing those patches. That list includes but is not limited to, -- cgit v1.2.3 From 6f3ea41cbc559cbf1db92eeebcb4ec3432182299 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 10:16:35 -0800 Subject: Finish cleaning up algo-agreement log lines. Make them all consistent re: display of local-vs-remote, and less verbose when possible. --- paramiko/transport.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/paramiko/transport.py b/paramiko/transport.py index 324ed277..b60fe85f 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -1778,6 +1778,18 @@ class Transport (threading.Thread, ClosingContextManager): if self.sys.modules is not None: raise + + def _log_agreement(self, which, local, remote): + # Log useful, non-duplicative line re: an agreed-upon algorithm. + # Old code implied algorithms could be asymmetrical (different for + # inbound vs outbound) so we preserve that possibility. + msg = "{0} agreed: ".format(which) + if local == remote: + msg += local + else: + msg += "local={0}, remote={1}".format(local, remote) + self._log(DEBUG, msg) + ### protocol stages def _negotiate_keys(self, m): @@ -1946,7 +1958,9 @@ class Transport (threading.Thread, ClosingContextManager): raise SSHException('Incompatible ssh server (no acceptable ciphers)') self.local_cipher = agreed_local_ciphers[0] self.remote_cipher = agreed_remote_ciphers[0] - self._log(DEBUG, 'Ciphers agreed: local=%s, remote=%s' % (self.local_cipher, self.remote_cipher)) + self._log_agreement( + 'Cipher', local=self.local_cipher, remote=self.remote_cipher + ) if self.server_mode: agreed_remote_macs = list(filter(self._preferred_macs.__contains__, client_mac_algo_list)) @@ -1958,7 +1972,9 @@ class Transport (threading.Thread, ClosingContextManager): raise SSHException('Incompatible ssh server (no acceptable macs)') self.local_mac = agreed_local_macs[0] self.remote_mac = agreed_remote_macs[0] - self._log(DEBUG, 'MACs agreed: local=%s, remote=%s' % (self.local_mac, self.remote_mac)) + self._log_agreement( + 'MAC', local=self.local_mac, remote=self.remote_mac + ) if self.server_mode: agreed_remote_compression = list(filter(self._preferred_compression.__contains__, client_compress_algo_list)) @@ -1970,7 +1986,11 @@ class Transport (threading.Thread, ClosingContextManager): raise SSHException('Incompatible ssh server (no acceptable compression) %r %r %r' % (agreed_local_compression, agreed_remote_compression, self._preferred_compression)) self.local_compression = agreed_local_compression[0] self.remote_compression = agreed_remote_compression[0] - self._log(DEBUG, 'Compressions agreed: local=%s' % self.local_compression) + self._log_agreement( + 'Compression', + local=self.local_compression, + remote=self.remote_compression + ) # save for computing hash later... # now wait! openssh has a bug (and others might too) where there are -- cgit v1.2.3 From 7030ad9f8d2497761bcbf7d1bc97d41a5041cb2a Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 10:23:02 -0800 Subject: Port #604 to latest master --- paramiko/transport.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/paramiko/transport.py b/paramiko/transport.py index b60fe85f..c5054dea 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -98,9 +98,11 @@ class Transport (threading.Thread, ClosingContextManager): # reorder without reason! _preferred_ciphers = ( 'aes128-ctr', + 'aes192-ctr', 'aes256-ctr', 'aes128-cbc', 'blowfish-cbc', + 'aes192-cbc', 'aes256-cbc', '3des-cbc', 'arcfour128', @@ -134,6 +136,12 @@ class Transport (threading.Thread, ClosingContextManager): 'block-size': 16, 'key-size': 16 }, + 'aes192-ctr': { + 'class': AES, + 'mode': AES.MODE_CTR, + 'block-size': 16, + 'key-size': 24 + }, 'aes256-ctr': { 'class': AES, 'mode': AES.MODE_CTR, @@ -152,6 +160,12 @@ class Transport (threading.Thread, ClosingContextManager): 'block-size': 16, 'key-size': 16 }, + 'aes192-cbc': { + 'class': AES, + 'mode': AES.MODE_CBC, + 'block-size': 16, + 'key-size': 24 + }, 'aes256-cbc': { 'class': AES, 'mode': AES.MODE_CBC, -- cgit v1.2.3 From 9a091e0494269ae5e6074877fb0b335181ad28ae Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 10:24:48 -0800 Subject: Changelog closes #604 --- sites/www/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 3aa2b84b..9c94002d 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +* :feature:`604` Add support for the ``aes192-ctr`` and ``aes192-cbc`` ciphers. + Thanks to Michiel Tiller for noticing it was as easy as tweaking some key + sizes :D * :feature:`356` (also :issue:`596`, :issue:`365`, :issue:`341`, :issue:`164`, :issue:`581`, and a bunch of other duplicates besides) Add support for SHA-2 based key exchange (kex) algorithm ``diffie-hellman-group-exchange-sha256`` -- cgit v1.2.3 From aed3d8b75a49ca85a322cf95d6b6dcc1131619fd Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Tue, 21 Jul 2015 14:46:08 +0530 Subject: compare end of key file before reading it --- paramiko/pkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paramiko/pkey.py b/paramiko/pkey.py index c8f84e0a..db5b77ac 100644 --- a/paramiko/pkey.py +++ b/paramiko/pkey.py @@ -274,7 +274,7 @@ class PKey (object): start += 1 # find end end = start - while (lines[end].strip() != '-----END ' + tag + ' PRIVATE KEY-----') and (end < len(lines)): + while end < len(lines) and lines[end].strip() != '-----END ' + tag + ' PRIVATE KEY-----': end += 1 # if we trudged to the end of the file, just try to cope. try: -- cgit v1.2.3 From 663e4ca4a0363670d6dd72a512e936d0c47457c0 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 2 Oct 2015 16:20:27 -0700 Subject: Fix dumb bug in release task --- tasks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 20ded03d..2aeea0da 100644 --- a/tasks.py +++ b/tasks.py @@ -25,7 +25,8 @@ def release(ctx): # Move the built docs into where Epydocs used to live target = 'docs' rmtree(target, ignore_errors=True) - copytree(docs_build, target) + # TODO: make it easier to yank out this config val from the docs coll + copytree('sites/docs/_build', target) # Publish publish(ctx) # Remind -- cgit v1.2.3 From ce2df91a790aedcd0ec08f3526141cd01c63560d Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 1 Nov 2015 15:09:12 -0800 Subject: Allow specifying test.py flags in 'inv test' --- tasks.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tasks.py b/tasks.py index 2aeea0da..05654d3b 100644 --- a/tasks.py +++ b/tasks.py @@ -9,8 +9,14 @@ from invocations.packaging import publish # Until we move to spec-based testing @task -def test(ctx): - ctx.run("python test.py --verbose", pty=True) +def test(ctx, coverage=False, flags=""): + if "--verbose" not in flags.split(): + flags += " --verbose" + runner = "python" + if coverage: + runner = "coverage run --source=paramiko" + ctx.run("{0} test.py {1}".format(runner, flags), pty=True) + @task def coverage(ctx): -- cgit v1.2.3 From fcacbe4620a867acedf33da7a069b09e4a8d370d Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 12:52:24 -0800 Subject: Changelog closes #565 --- sites/www/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 9ce2eded..7c6b74e4 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +* :bug:`565` Don't explode with ``IndexError`` when reading private key files + lacking an ``-----END PRIVATE KEY-----`` footer. Patch courtesy of + Prasanna Santhanam. * :release:`1.13.3 <2014-12-19>` * :bug:`413` (also :issue:`414`, :issue:`420`, :issue:`454`) Be significantly smarter about polling & timing behavior when running proxy commands, to avoid -- cgit v1.2.3 From 0683452c622e95c2b3a775621d3ace7a5afcc152 Mon Sep 17 00:00:00 2001 From: Sergey Skripnick Date: Mon, 12 Oct 2015 12:32:59 +0200 Subject: Fix data types in some docstrings Some methods return `bytes` type, but documentation said `str`. Also method BufferedPipe.feed accept both types. --- paramiko/buffered_pipe.py | 4 ++-- paramiko/channel.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/paramiko/buffered_pipe.py b/paramiko/buffered_pipe.py index ac35b3e1..d5fe164e 100644 --- a/paramiko/buffered_pipe.py +++ b/paramiko/buffered_pipe.py @@ -81,7 +81,7 @@ 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, as a `str` + :param data: the data to add, as a `str` or `bytes` """ self._lock.acquire() try: @@ -125,7 +125,7 @@ class BufferedPipe (object): :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` + :return: the read data, as a `bytes` :raises PipeTimeout: if a timeout was specified and no data was ready before that diff --git a/paramiko/channel.py b/paramiko/channel.py index 23323650..a36c70f4 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -574,8 +574,8 @@ class Channel (object): is returned, the channel stream has closed. :param int nbytes: maximum number of bytes to read. - :return: received data, as a `str` - + :return: received data, as a `bytes` + :raises socket.timeout: if no data is ready before the timeout set by `settimeout`. """ -- cgit v1.2.3 From 3e08a40e9aee4aa289e9704c115773e1596d7f5d Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 12:55:36 -0800 Subject: Changelog closes #594 --- sites/www/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 7c6b74e4..e81327fc 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,8 @@ Changelog ========= +* :support:`594 backported` Correct some post-Python3-port docstrings to + specify ``bytes`` type instead of ``str``. Credit to ``@redixin``. * :bug:`565` Don't explode with ``IndexError`` when reading private key files lacking an ``-----END PRIVATE KEY-----`` footer. Patch courtesy of Prasanna Santhanam. -- cgit v1.2.3 From 81024992e8fe5ccde8303e08feb4b9bdc4bd0d24 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 18 Jul 2014 11:20:34 -0700 Subject: Fixed a typo in method name The method on Python3 is `bit_length`, not `bitlength`. --- paramiko/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paramiko/util.py b/paramiko/util.py index 46278a69..0d8ce32a 100644 --- a/paramiko/util.py +++ b/paramiko/util.py @@ -118,7 +118,7 @@ def safe_string(s): def bit_length(n): try: - return n.bitlength() + return n.bit_length() except AttributeError: norm = deflate_long(n, False) hbyte = byte_ord(norm[0]) -- cgit v1.2.3 From 7611c57910f49aadf8caafbc7970bc3d991382d8 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 13:07:02 -0800 Subject: Changelog closes #359 --- sites/www/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index e81327fc..5dc877c4 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +* :bug:`359` Use correct attribute name when trying to use Python 3's + ``int.bit_length`` method; prior to fix, the Python 2 custom fallback + implementation was always used, even on Python 3. Thanks to Alex Gaynor. * :support:`594 backported` Correct some post-Python3-port docstrings to specify ``bytes`` type instead of ``str``. Credit to ``@redixin``. * :bug:`565` Don't explode with ``IndexError`` when reading private key files -- cgit v1.2.3 From 815f9c7b7dd169060a69fb818bec04ed4db1f9da Mon Sep 17 00:00:00 2001 From: Ulrich Petri Date: Sat, 26 Jul 2014 17:40:58 +0200 Subject: Fixed str() of empty SFTPAttributes() --- paramiko/sftp_attr.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/paramiko/sftp_attr.py b/paramiko/sftp_attr.py index d12eff8d..708afc5a 100644 --- a/paramiko/sftp_attr.py +++ b/paramiko/sftp_attr.py @@ -210,12 +210,15 @@ class SFTPAttributes (object): # not all servers support uid/gid uid = self.st_uid gid = self.st_gid + size = self.st_size if uid is None: uid = 0 if gid is None: gid = 0 + if size is None: + size = 0 - return '%s 1 %-8d %-8d %8d %-12s %s' % (ks, uid, gid, self.st_size, datestr, filename) + return '%s 1 %-8d %-8d %8d %-12s %s' % (ks, uid, gid, size, datestr, filename) def asbytes(self): return b(str(self)) -- cgit v1.2.3 From f6135f43d49c3903f9538f57191c2c381e86f689 Mon Sep 17 00:00:00 2001 From: Ulrich Petri Date: Sat, 26 Jul 2014 17:36:35 +0200 Subject: Added test for str() of empty SFTPAttributes() --- tests/test_sftp.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_sftp.py b/tests/test_sftp.py index 2b6aa3b6..980e8367 100755 --- a/tests/test_sftp.py +++ b/tests/test_sftp.py @@ -776,6 +776,11 @@ class SFTPTest (unittest.TestCase): sftp.remove('%s/nonutf8data' % FOLDER) + def test_sftp_attributes_empty_str(self): + sftp_attributes = SFTPAttributes() + self.assertEqual(str(sftp_attributes), "?--------- 1 0 0 0 (unknown date) ?") + + if __name__ == '__main__': SFTPTest.init_loopback() # logging is required by test_N_file_with_percent -- cgit v1.2.3 From f3649c0d7d9d6d46269c5ad05ef88383cf50180f Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 13:12:39 -0800 Subject: Changelog closes #366 --- sites/www/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 5dc877c4..831d425b 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +* :bug:`366` Fix `~paramiko.sftp_attributes.SFTPAttributes` so its string + representation doesn't raise exceptions on empty/initialized instances. Patch + by Ulrich Petri. * :bug:`359` Use correct attribute name when trying to use Python 3's ``int.bit_length`` method; prior to fix, the Python 2 custom fallback implementation was always used, even on Python 3. Thanks to Alex Gaynor. -- cgit v1.2.3 From a1d0cd6c6c375e6b1c17710805f0a2e7503ee0a9 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 13:37:12 -0800 Subject: 80-col tweaks --- paramiko/pkey.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/paramiko/pkey.py b/paramiko/pkey.py index db5b77ac..d8018e70 100644 --- a/paramiko/pkey.py +++ b/paramiko/pkey.py @@ -171,8 +171,9 @@ class PKey (object): is useless on the abstract PKey class. :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 + :param str password: + an optional password to use to decrypt the key file, if it's + encrypted :return: a new `.PKey` based on the given private key :raises IOError: if there was an error reading the file @@ -187,8 +188,8 @@ 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 ``password`` is not ``None``, - the given password will be used to decrypt the key (otherwise + object. If the private key is encrypted and ``password`` is not + ``None``, the given password will be used to decrypt the key (otherwise `.PasswordRequiredException` is thrown). :param file file_obj: the file to read from @@ -197,8 +198,8 @@ class PKey (object): :return: a new `.PKey` based on the given private key :raises IOError: if there was an error reading the key - :raises PasswordRequiredException: if the private key file is encrypted, - and ``password`` is ``None`` + :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) -- cgit v1.2.3 From 545dedba3c3074e8ceacf0a4b639e515c85df671 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 13:51:26 -0800 Subject: Add 'sites' task for rigorous doc error discovery --- tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasks.py b/tasks.py index 05654d3b..3d55a778 100644 --- a/tasks.py +++ b/tasks.py @@ -3,7 +3,7 @@ from os.path import join from shutil import rmtree, copytree from invoke import Collection, ctask as task -from invocations.docs import docs, www +from invocations.docs import docs, www, sites from invocations.packaging import publish @@ -39,4 +39,4 @@ def release(ctx): print("\n\nDon't forget to update RTD's versions page for new minor releases!") -ns = Collection(test, coverage, release, docs, www) +ns = Collection(test, coverage, release, docs, www, sites) -- cgit v1.2.3 From e6593de3b5ebb88a9a32d8e83245726d394a2646 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 13:51:46 -0800 Subject: Fix somebody's smart quotes --- paramiko/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paramiko/config.py b/paramiko/config.py index c21de936..b58e66a2 100644 --- a/paramiko/config.py +++ b/paramiko/config.py @@ -99,7 +99,7 @@ class SSHConfig (object): The host-matching rules of OpenSSH's ``ssh_config`` man page are used: For each parameter, the first obtained value will be used. The - configuration files contain sections separated by ``Host'' + configuration files contain sections separated by ``Host`` specifications, and that section is only applied for hosts that match one of the patterns given in the specification. -- cgit v1.2.3 From e73dc7751bb2ad360308f43918f6bd62aefbb085 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 17:06:11 -0800 Subject: Fix a handful of non-strict warnings in the Sphinx docs. Turns out there's a reason we haven't run the strict checker (sphinx-build -nW) before - it tries to resolve literally every info-list field like ':raises IOError:' and sphinx is apparently not cool enough to mesh that with intersphinx. --- paramiko/config.py | 2 +- paramiko/pkey.py | 9 +++++---- paramiko/sftp_client.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/paramiko/config.py b/paramiko/config.py index b58e66a2..5c2efdcc 100644 --- a/paramiko/config.py +++ b/paramiko/config.py @@ -51,7 +51,7 @@ class SSHConfig (object): """ Read an OpenSSH config from the given file object. - :param file file_obj: a file-like object to read the config file from + :param file_obj: a file-like object to read the config file from """ host = {"host": ['*'], "config": {}} for line in file_obj: diff --git a/paramiko/pkey.py b/paramiko/pkey.py index d8018e70..472c1287 100644 --- a/paramiko/pkey.py +++ b/paramiko/pkey.py @@ -192,7 +192,7 @@ class PKey (object): ``None``, the given password will be used to decrypt the key (otherwise `.PasswordRequiredException` is thrown). - :param file file_obj: the file to read from + :param file_obj: the file-like object 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 @@ -225,7 +225,7 @@ class PKey (object): Write private key contents into a file (or file-like) object. If the password is not ``None``, the key is encrypted before writing. - :param file file_obj: the file object to write into + :param file_obj: the file-like object to write into :param str password: an optional password to use to encrypt the key :raises IOError: if there was an error writing to the file @@ -311,8 +311,9 @@ class PKey (object): a trivially-encoded format (base64) which is completely insecure. If a password is given, DES-EDE3-CBC is used. - :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 tag: + ``"RSA"`` or ``"DSA"``, the tag used to mark the data block. + :param 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. diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index 99a29e36..9a3e04b7 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -517,7 +517,7 @@ class SFTPClient(BaseSFTP): The SFTP operations use pipelining for speed. - :param file fl: opened file or file-like object to copy + :param 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, -- cgit v1.2.3 From 98b504ce1d840cc543aa85bdfb23b217931eabbf Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 17:14:30 -0800 Subject: Bump dev-req for invoke/invocations updates --- dev-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 059572cf..90cfd477 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,8 +1,8 @@ # Older junk tox>=1.4,<1.5 # For newer tasks like building Sphinx docs. -invoke>=0.10 -invocations>=0.9.2 +invoke>=0.11.1 +invocations>=0.11.0 sphinx>=1.1.3 alabaster>=0.6.1 releases>=0.5.2 -- cgit v1.2.3 From d22597b558ca8a1465352497e5adfd5d79cf4f3a Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 17:24:41 -0800 Subject: Really, really sick of Python 3.2 being shite to support. Step 1: make it stop tanking Travis builds randomly. --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 64f64e60..dc5bd561 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ sudo: false python: - "2.6" - "2.7" - - "3.2" - "3.3" install: # Self-install for setup.py-driven deps @@ -18,9 +17,7 @@ script: # 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. - # Finally, skip them under Python 3.2 due to sphinx shenanigans - - "[[ $TRAVIS_PYTHON_VERSION != 3.2 ]] && invoke docs -o -W || true" - - "[[ $TRAVIS_PYTHON_VERSION != 3.2 ]] && invoke www -o -W || true" + - invoke docs -o -W www -o -W notifications: irc: channels: "irc.freenode.org#paramiko" -- cgit v1.2.3 From 583d6eaedfe2b004777b100ab8a8185bb9bec073 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 17:32:00 -0800 Subject: We ought to work on python 3.4/3.5... --- .travis.yml | 2 ++ setup.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index dc5bd561..f841a71e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ python: - "2.6" - "2.7" - "3.3" + - "3.4" + - "3.5" install: # Self-install for setup.py-driven deps - pip install -e . diff --git a/setup.py b/setup.py index 235fd0e3..9e08323d 100644 --- a/setup.py +++ b/setup.py @@ -86,6 +86,8 @@ setup( 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', ], **kw ) -- cgit v1.2.3 From 24e767792dab6e4cac6e5374032e772951d0392e Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 17:43:03 -0800 Subject: Backport a Python 3.4+ threading change to test.py --- test.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test.py b/test.py index bd966d1e..6a2c9a8b 100755 --- a/test.py +++ b/test.py @@ -149,10 +149,7 @@ 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() + thread.join(timeout=1) # Exit correctly if not result.wasSuccessful(): sys.exit(1) -- cgit v1.2.3 From 0a57d0337778d99066688e310c81d449c64c9bb6 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 17:57:45 -0800 Subject: Cut 1.13.4 --- paramiko/_version.py | 2 +- sites/www/changelog.rst | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/paramiko/_version.py b/paramiko/_version.py index 0402fcf2..63bba727 100644 --- a/paramiko/_version.py +++ b/paramiko/_version.py @@ -1,2 +1,2 @@ -__version_info__ = (1, 13, 3) +__version_info__ = (1, 13, 4) __version__ = '.'.join(map(str, __version_info__)) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 831d425b..e435c65e 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,7 @@ Changelog ========= +* :release:`1.13.4 <2015-11-02>` * :bug:`366` Fix `~paramiko.sftp_attributes.SFTPAttributes` so its string representation doesn't raise exceptions on empty/initialized instances. Patch by Ulrich Petri. -- cgit v1.2.3 From 79bdefe35610b651566bb7422518fb60b3f72bdd Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 17:59:40 -0800 Subject: Cut 1.14.3 --- paramiko/_version.py | 2 +- sites/www/changelog.rst | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/paramiko/_version.py b/paramiko/_version.py index f941ac22..871565d3 100644 --- a/paramiko/_version.py +++ b/paramiko/_version.py @@ -1,2 +1,2 @@ -__version_info__ = (1, 14, 2) +__version_info__ = (1, 14, 3) __version__ = '.'.join(map(str, __version_info__)) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 484e7be9..7a140e38 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,7 @@ Changelog ========= +* :release:`1.14.3 <2015-11-02>` * :release:`1.13.4 <2015-11-02>` * :bug:`366` Fix `~paramiko.sftp_attributes.SFTPAttributes` so its string representation doesn't raise exceptions on empty/initialized instances. Patch -- cgit v1.2.3 From d37c68673396b247c08d0d5122bb012e9c3c46c3 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 2 Nov 2015 18:01:15 -0800 Subject: Cut 1.15.4 --- paramiko/_version.py | 2 +- sites/www/changelog.rst | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/paramiko/_version.py b/paramiko/_version.py index 25aac14f..3b9c059e 100644 --- a/paramiko/_version.py +++ b/paramiko/_version.py @@ -1,2 +1,2 @@ -__version_info__ = (1, 15, 3) +__version_info__ = (1, 15, 4) __version__ = '.'.join(map(str, __version_info__)) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index a7824175..fe4b2b2d 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,7 @@ Changelog ========= +* :release:`1.15.4 <2015-11-02>` * :release:`1.14.3 <2015-11-02>` * :release:`1.13.4 <2015-11-02>` * :bug:`366` Fix `~paramiko.sftp_attributes.SFTPAttributes` so its string -- cgit v1.2.3 From 6f773cef69f2a70e51d44affd0e592edc099cc11 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Tue, 3 Nov 2015 12:57:14 -0800 Subject: Changelog re #525 --- sites/www/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index e435c65e..cbecabea 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +* :support:`525 backported` Update the vendored Windows API addon to a more + recent edition. Also fixes :issue:`193`, :issue:`488`, :issue:`498`. Thanks + to Jason Coombs. * :release:`1.13.4 <2015-11-02>` * :bug:`366` Fix `~paramiko.sftp_attributes.SFTPAttributes` so its string representation doesn't raise exceptions on empty/initialized instances. Patch -- cgit v1.2.3 From 52ba29d62796215d417716802808f8702b673baf Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Mon, 22 Sep 2014 00:18:07 -0400 Subject: Fix line number for known_hosts error messages. --- paramiko/hostkeys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py index 7c6a5aa5..1a5031d1 100644 --- a/paramiko/hostkeys.py +++ b/paramiko/hostkeys.py @@ -89,7 +89,7 @@ class HostKeys (MutableMapping): :raises IOError: if there was an error reading the file """ with open(filename, 'r') as f: - for lineno, line in enumerate(f): + for lineno, line in enumerate(f, 1): line = line.strip() if (len(line) == 0) or (line[0] == '#'): continue -- cgit v1.2.3 From 3a5227c477295c8e14e395d3ac66e9a58db0ebc8 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Tue, 3 Nov 2015 13:31:13 -0800 Subject: Changelog closes #401 --- sites/www/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index cbecabea..278f7450 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +* :bug:`401` Fix line number reporting in log output regarding invalid + ``known_hosts`` line entries. Thanks to Dylan Thacker-Smith for catch & + patch. * :support:`525 backported` Update the vendored Windows API addon to a more recent edition. Also fixes :issue:`193`, :issue:`488`, :issue:`498`. Thanks to Jason Coombs. -- cgit v1.2.3 From 4565fb517ceb54aa994ff96380b2c8f24df43968 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Tue, 3 Nov 2015 16:23:38 -0800 Subject: Reword changelog re #502 & add attribution --- sites/www/changelog.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index bd890b4e..3310eb32 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,8 +2,9 @@ Changelog ========= -* :bug:`502` Fix an issue in server mode, when processing an exec request. - A command that is not a valid UTF-8 string, caused an UnicodeDecodeError. +* :bug:`502 major` Fix 'exec' requests in server mode to use ``get_string`` + instead of ``get_text`` to avoid ``UnicodeDecodeError`` on non-UTF-8 input. + Thanks to Anselm Kruis for the patch & discussion. * :bug:`401` Fix line number reporting in log output regarding invalid ``known_hosts`` line entries. Thanks to Dylan Thacker-Smith for catch & patch. -- cgit v1.2.3 From b0435808802cf435fd2865b7b8af2326064df82c Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Tue, 3 Nov 2015 16:58:02 -0800 Subject: 80-col --- paramiko/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/paramiko/client.py b/paramiko/client.py index 5a215a81..07175763 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -256,7 +256,8 @@ class SSHClient (ClosingContextManager): :param socket sock: an open socket or socket-like object (such as a `.Channel`) to use for communication to the target host - :param bool gss_auth: ``True`` if you want to use GSS-API authentication + :param bool gss_auth: + ``True`` if you want to use GSS-API authentication :param bool gss_kex: Perform GSS-API Key Exchange and user authentication :param bool gss_deleg_creds: Delegate GSS-API client credentials or not -- cgit v1.2.3 From c9441c4a202dd53d4cb00946943745f580efa084 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 4 Nov 2015 12:44:21 -0800 Subject: Patch up a missed spot re: 2FA plus keys, thanks @mattrobenolt --- paramiko/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paramiko/client.py b/paramiko/client.py index f30aba2f..8d899a15 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -504,7 +504,7 @@ class SSHClient (ClosingContextManager): try: key = pkey_class.from_private_key_file(key_filename, password) self._log(DEBUG, 'Trying key %s from %s' % (hexlify(key.get_fingerprint()), key_filename)) - self._transport.auth_publickey(username, key) + allowed_types = set(self._transport.auth_publickey(username, key)) two_factor = (allowed_types & two_factor_types) if not two_factor: return -- cgit v1.2.3 From 2a99a8c9a4bde66720e9357963ce1896830528a1 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 4 Nov 2015 12:51:16 -0800 Subject: Changelog closes #467 --- sites/www/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 3310eb32..6ea85c45 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,10 @@ Changelog ========= +* :feature:`467` (also :issue:`139`, :issue:`412`) Fully enable two-factor + authentication (e.g. when a server requires ``AuthenticationMethods + pubkey,keyboard-interactive``). Thanks to ``@perryjrandall`` for the patch + and to ``@nevins-b`` and Matt Robenolt for additional support. * :bug:`502 major` Fix 'exec' requests in server mode to use ``get_string`` instead of ``get_text`` to avoid ``UnicodeDecodeError`` on non-UTF-8 input. Thanks to Anselm Kruis for the patch & discussion. -- cgit v1.2.3 From 5b077c2022f150a8143a306df0ae8dae6427a4a8 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 4 Nov 2015 14:50:49 -0800 Subject: Remove vestigial extra 'stat' call in SFTPClient.get() Was apparently not removed when getfo() was born. --- paramiko/sftp_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index da6f6e87..57225558 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -717,7 +717,6 @@ class SFTPClient(BaseSFTP, ClosingContextManager): .. 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) -- cgit v1.2.3 From 935711b5a17370494a7b2b8b4587f5466badf1e8 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 4 Nov 2015 14:53:41 -0800 Subject: Changelog closes #194 --- sites/www/changelog.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 6ea85c45..304f10a6 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,11 @@ Changelog ========= +* :bug:`194 major` (also :issue:`562`, :issue:`530`, :issue:`576`) Streamline + use of ``stat`` when downloading SFTP files via `SFTPClient.get + `; this avoids triggering bugs in some + off-spec SFTP servers such as IBM Sterling. Thanks to ``@muraleee`` for the + initial report and to Torkil Gustavsen for the patch. * :feature:`467` (also :issue:`139`, :issue:`412`) Fully enable two-factor authentication (e.g. when a server requires ``AuthenticationMethods pubkey,keyboard-interactive``). Thanks to ``@perryjrandall`` for the patch -- cgit v1.2.3 From 1fe8c0de7fc6d6dce3b6ece69be10972480dad8f Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 5 Nov 2015 14:46:29 -0800 Subject: Typo fix --- sites/www/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 304f10a6..9c4ee012 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -113,7 +113,7 @@ Changelog use of the ``shlex`` module. Thanks to Yan Kalchevskiy. * :support:`422 backported` Clean up some unused imports. Courtesy of Olle Lundberg. -* :support:`421 backported` Modernize threading calls to user newer API. Thanks +* :support:`421 backported` Modernize threading calls to use newer API. Thanks to Olle Lundberg. * :support:`419 backported` Modernize a bunch of the codebase internals to leverage decorators. Props to ``@beckjake`` for realizing we're no longer on -- cgit v1.2.3 From 91bb2a1405047ec0920575f3ebbd39e8f7215a21 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 5 Nov 2015 14:48:07 -0800 Subject: Modernize a couple dev-reqs --- dev-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 90cfd477..9e4564a5 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -4,8 +4,8 @@ tox>=1.4,<1.5 invoke>=0.11.1 invocations>=0.11.0 sphinx>=1.1.3 -alabaster>=0.6.1 -releases>=0.5.2 +alabaster>=0.7.5 +releases>=1.0.0 semantic_version>=2.4,<2.5 wheel==0.24 twine==1.5 -- cgit v1.2.3 From 96705e26cf9ac7c9c3f6e8cd28e7e408dc5b856a Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 5 Nov 2015 14:48:19 -0800 Subject: Cut 1.16 --- sites/www/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 9c4ee012..084d13de 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,7 @@ Changelog ========= +* :release:`1.16.0 <2015-11-04>` * :bug:`194 major` (also :issue:`562`, :issue:`530`, :issue:`576`) Streamline use of ``stat`` when downloading SFTP files via `SFTPClient.get `; this avoids triggering bugs in some -- cgit v1.2.3 From cd4073122e1cda1fec4bf0bae7ba0dede6fd3635 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 5 Nov 2015 15:15:51 -0800 Subject: Passthru sdist/wheel options for release task --- tasks.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tasks.py b/tasks.py index 3d55a778..d2bed606 100644 --- a/tasks.py +++ b/tasks.py @@ -25,7 +25,10 @@ def coverage(ctx): # Until we stop bundling docs w/ releases. Need to discover use cases first. @task -def release(ctx): +def release(ctx, sdist=True, wheel=True): + """ + Wraps invocations.packaging.release to add baked-in docs folder. + """ # Build docs first. Use terribad workaround pending invoke #146 ctx.run("inv docs") # Move the built docs into where Epydocs used to live @@ -34,7 +37,7 @@ def release(ctx): # TODO: make it easier to yank out this config val from the docs coll copytree('sites/docs/_build', target) # Publish - publish(ctx) + publish(ctx, sdist=sdist, wheel=wheel) # Remind print("\n\nDon't forget to update RTD's versions page for new minor releases!") -- cgit v1.2.3 From e51b24ac8060bda099bcc5ea6c18f96f062aaad8 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 5 Nov 2015 17:19:16 -0800 Subject: Missed a spot re: 3.2 specific crap in Travis config --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c2c20a60..55cba46d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,8 +13,8 @@ install: - pip install coveralls # For coveralls.io specifically - pip install -r dev-requirements.txt script: - # Main tests, w/ coverage! (but skip coverage on 3.2, coverage.py dropped it) - - "[[ $TRAVIS_PYTHON_VERSION != 3.2 ]] && inv test --coverage || inv test" + # Main tests, w/ coverage! + - inv test --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 -- cgit v1.2.3 From a90f247a27eadae138eff5ee78c91c99dbc3596f Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 6 Nov 2015 15:10:44 -0800 Subject: Hacky cleanup of non-gc'd clients in a loopy test. Re #612 --- tests/test_client.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 04cab439..f71efd5a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -193,12 +193,18 @@ class SSHClientTest (unittest.TestCase): (['dss', 'rsa', 'ecdsa'], ['dss']), # Try ECDSA but fail (['rsa', 'ecdsa'], ['ecdsa']), # ECDSA success ): - self._test_connection( - key_filename=[ - test_path('test_{0}.key'.format(x)) for x in attempt - ], - allowed_keys=[types_[x] for x in accept], - ) + try: + self._test_connection( + key_filename=[ + test_path('test_{0}.key'.format(x)) for x in attempt + ], + allowed_keys=[types_[x] for x in accept], + ) + finally: + # Clean up to avoid occasional gc-related deadlocks. + # TODO: use nose test generators after nose port + self.tearDown() + self.setUp() def test_multiple_key_files_failure(self): """ -- cgit v1.2.3 From ba0b12fb09f7334e930fc2b5a02d7e7824695627 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 6 Nov 2015 15:10:44 -0800 Subject: Hacky cleanup of non-gc'd clients in a loopy test. Re #612 --- tests/test_client.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 3d2e75c9..e080221e 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -193,12 +193,18 @@ class SSHClientTest (unittest.TestCase): (['dss', 'rsa', 'ecdsa'], ['dss']), # Try ECDSA but fail (['rsa', 'ecdsa'], ['ecdsa']), # ECDSA success ): - self._test_connection( - key_filename=[ - test_path('test_{0}.key'.format(x)) for x in attempt - ], - allowed_keys=[types_[x] for x in accept], - ) + try: + self._test_connection( + key_filename=[ + test_path('test_{0}.key'.format(x)) for x in attempt + ], + allowed_keys=[types_[x] for x in accept], + ) + finally: + # Clean up to avoid occasional gc-related deadlocks. + # TODO: use nose test generators after nose port + self.tearDown() + self.setUp() def test_multiple_key_files_failure(self): """ -- cgit v1.2.3 From 39c13bccf1d4015eb6800de019c9fc7a387f1401 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 6 Nov 2015 16:00:48 -0800 Subject: Docstring reformat --- paramiko/packet.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/paramiko/packet.py b/paramiko/packet.py index b922000c..8aaab75a 100644 --- a/paramiko/packet.py +++ b/paramiko/packet.py @@ -204,9 +204,10 @@ class Packetizer (object): def handshake_timed_out(self): """ Checks if the handshake has timed out. - If `start_handshake` wasn't called before the call to this function - the return value will always be `False`. - If the handshake completed before a time out was reached the return value will be `False` + + If `start_handshake` wasn't called before the call to this function, + the return value will always be `False`. If the handshake completed + before a timeout was reached, the return value will be `False` :return: handshake time out status, as a `bool` """ -- cgit v1.2.3 From 894896f6ce0d6f60093a694e142658fde706aff0 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 4 Dec 2015 09:29:45 -0800 Subject: Add missing versionadded note to new exception class --- paramiko/ssh_exception.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py index 02f3e52e..016a411e 100644 --- a/paramiko/ssh_exception.py +++ b/paramiko/ssh_exception.py @@ -156,6 +156,8 @@ class NoValidConnectionsError(socket.error): It is implied/assumed that all the errors given to a single instance of this class are from connecting to the same hostname + port (and thus that the differences are in the resolution of the hostname - e.g. IPv4 vs v6). + + .. versionadded:: 1.16 """ def __init__(self, errors): """ -- cgit v1.2.3 From b83e8789c9ae9f06ae9310e0f4ac8a246791394b Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 6 Dec 2015 12:54:00 -0800 Subject: 80-col tweak --- paramiko/channel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/paramiko/channel.py b/paramiko/channel.py index 7601a34b..b6803f63 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -286,7 +286,8 @@ class Channel (ClosingContextManager): return an exit status in some cases (like bad servers). :return: - ``True`` if `recv_exit_status` will return immediately, else ``False``. + ``True`` if `recv_exit_status` will return immediately, else + ``False``. .. versionadded:: 1.7.3 """ -- cgit v1.2.3 From d6dea28a3a100cac728e83d2a9db65b413f33025 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 6 Dec 2015 12:54:28 -0800 Subject: Add warning to Channel.recv_exit_status re: call order. Closes #448...kinda --- paramiko/channel.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/paramiko/channel.py b/paramiko/channel.py index b6803f63..8a1c0f87 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -301,6 +301,17 @@ class Channel (ClosingContextManager): it does, or until the channel is closed. If no exit status is provided by the server, -1 is returned. + .. warning:: + In some situations, receiving remote output larger than the current + `.Transport` or session's ``window_size`` (e.g. that set by the + ``default_window_size`` kwarg for `.Transport.__init__`) will cause + `.recv_exit_status` to hang indefinitely if it is called prior to a + sufficiently large `.read` (or if there are no threads calling + `.read` in the background). + + In these cases, ensuring that `.recv_exit_status` is called *after* + `.read` (or, again, using threads) can avoid the hang. + :return: the exit code (as an `int`) of the process on the server. .. versionadded:: 1.2 -- cgit v1.2.3 From 91fe9206276b0962a269743b8c267e21ab111f35 Mon Sep 17 00:00:00 2001 From: Lucas Mehl Date: Wed, 9 Dec 2015 13:13:44 -0600 Subject: Update and prettify README, add Travis & Coveralls badges --- README | 134 -------------------------------------------------- README.rst | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 134 deletions(-) delete mode 100644 README create mode 100644 README.rst diff --git a/README b/README deleted file mode 100644 index a645b140..00000000 --- a/README +++ /dev/null @@ -1,134 +0,0 @@ - -======== -paramiko -======== - -:Paramiko: Python SSH module -:Copyright: Copyright (c) 2003-2009 Robey Pointer -:Copyright: Copyright (c) 2013-2015 Jeff Forcier -: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.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 -the protocol that replaced telnet and rsh for secure access to remote -shells, but the protocol also includes the ability to open arbitrary -channels to remote services across the encrypted tunnel (this is how sftp -works, for example). - -it is written entirely in python (no C or platform-dependent code) and is -released under the GNU LGPL (lesser GPL). - -the package and its API is fairly well documented in the "doc/" folder -that should have come with this archive. - - -Requirements ------------- - - - Python 2.6 or better - this includes Python - 3.2 and higher as well. - - pycrypto 2.1 or better - - ecdsa 0.9 or better - -If you have setuptools, you can build and install paramiko and all its -dependencies with this command (as root):: - - easy_install ./ - - -Portability ------------ - -i code and test this library on Linux and MacOS X. for that reason, i'm -pretty sure that it works for all posix platforms, including MacOS. it -should also work on Windows, though i don't test it as frequently there. -if you run into Windows problems, send me a patch: portability is important -to me. - -some python distributions don't include the utf-8 string encodings, for -reasons of space (misdirected as that is). if your distribution is -missing encodings, you'll see an error like this:: - - LookupError: no codec search functions registered: can't find encoding - -this means you need to copy string encodings over from a working system. -(it probably only happens on embedded systems, not normal python -installs.) Valeriy Pogrebitskiy says the best place to look is -``.../lib/python*/encodings/__init__.py``. - - -Bugs & Support --------------- - -Please file bug reports at https://github.com/paramiko/paramiko/. There is currently no mailing list but we plan to create a new one ASAP. - - -Demo ----- - -several demo scripts come with paramiko to demonstrate how to use it. -probably the simplest demo of all is this:: - - import paramiko, base64 - key = paramiko.RSAKey(data=base64.decodestring('AAA...')) - client = paramiko.SSHClient() - client.get_host_keys().add('ssh.example.com', 'ssh-rsa', key) - client.connect('ssh.example.com', username='strongbad', password='thecheat') - stdin, stdout, stderr = client.exec_command('ls') - for line in stdout: - print '... ' + line.strip('\n') - client.close() - -...which prints out the results of executing ``ls`` on a remote server. -(the host key 'AAA...' should of course be replaced by the actual base64 -encoding of the host key. if you skip host key verification, the -connection is not secure!) - -the following example scripts (in demos/) get progressively more detailed: - -:demo_simple.py: - calls invoke_shell() and emulates a terminal/tty through which you can - execute commands interactively on a remote server. think of it as a - poor man's ssh command-line client. - -:demo.py: - same as demo_simple.py, but allows you to authenticiate using a - private key, attempts to use an SSH-agent if present, and uses the long - form of some of the API calls. - -:forward.py: - command-line script to set up port-forwarding across an ssh transport. - (requires python 2.3.) - -:demo_sftp.py: - opens an sftp session and does a few simple file operations. - -:demo_server.py: - an ssh server that listens on port 2200 and accepts a login for - 'robey' (password 'foo'), and pretends to be a BBS. meant to be a - very simple demo of writing an ssh server. - -:demo_keygen.py: - an key generator similar to openssh ssh-keygen(1) program with - paramiko keys generation and progress functions. - -Use ---- - -the demo scripts are probably the best example of how to use this package. -there is also a lot of documentation, generated with Sphinx autodoc, in the doc/ folder. - -there are also unit tests here:: - - $ python ./test.py - -which will verify that most of the core components are working correctly. diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..e78bda76 --- /dev/null +++ b/README.rst @@ -0,0 +1,163 @@ +======== +Paramiko +======== + +.. Continuous integration and code coverage badges + +.. image:: https://travis-ci.org/paramiko/paramiko.svg?branch=master + :target: https://travis-ci.org/paramiko/paramiko +.. image:: https://coveralls.io/repos/paramiko/paramiko/badge.svg?branch=master&service=github + :target: https://coveralls.io/github/paramiko/paramiko?branch=master + +:Paramiko: Python SSH module +:Copyright: Copyright (c) 2003-2009 Robey Pointer +:Copyright: Copyright (c) 2013-2015 Jeff Forcier +:License: `LGPL `_ +:Homepage: http://www.paramiko.org/ +:API docs: http://docs.paramiko.org +:Development: https://github.com/paramiko/paramiko + + +What +---- + +"Paramiko" is a combination of the esperanto words for "paranoid" and +"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 +the protocol that replaced Telnet and rsh for secure access to remote +shells, but the protocol also includes the ability to open arbitrary +channels to remote services across the encrypted tunnel (this is how SFTP +works, for example). + +It is written entirely in Python (no C or platform-dependent code) and is +released under the GNU Lesser General Public License (`LGPL +`_). + +The package and its API is fairly well documented in the "doc/" folder +that should have come with this archive. + + +Requirements +------------ + +- `Python `_ 2.6, 2.7, or 3.3+ (3.2 should also work, + but it is not recommended) +- `pycrypto `_ 2.1+ +- `ecdsa `_ 0.9+ + + +Installation +------------ + +For most users, the recommended method to install is via pip:: + + pip install paramiko + +For more detailed instructions, see the `Installing +`_ page on the main Paramiko website. + + +Portability Issues +------------------ + +Paramiko primarily supports POSIX platforms with standard OpenSSH +implementations, and is most frequently tested on Linux and OS X. Windows is +supported as well, though it may not be as straightforward. + +Some Windows users whose Python is 64-bit have found that the PyCrypto +dependency ``winrandom`` may not install properly, leading to an +``ImportError``. In this scenario, you may need to compile ``winrandom`` +yourself. See `Fabric #194 `_ +for info. + +Some Python distributions don't include the UTF-8 string encodings, for +reasons of space (misguided as that is). If your distribution is +missing encodings, you'll see an error like this:: + + LookupError: no codec search functions registered: can't find encoding + +This means you need to copy string encodings over from a working system +(it probably only happens on embedded systems, not normal Python +installs). Valeriy Pogrebitskiy says the best place to look is +``.../lib/python*/encodings/__init__.py``. + + +Bugs & Support +-------------- + +:Bug Reports: `Github `_ +:Mailing List: ``paramiko@librelist.com`` (see the `LibreList website + `_ for usage details). +:IRC: ``#paramiko`` on Freenode + + +Kerberos Support +---------------- + +Paramiko ships with optional Kerberos/GSSAPI support; for info on the extra +dependencies for this, see the `GSS-API section +`_ +on the main Paramiko website. + + +Demo +---- + +Several demo scripts come with Paramiko to demonstrate how to use it. +Probably the simplest demo of all is this:: + + import paramiko, base64 + key = paramiko.RSAKey(data=base64.decodestring('AAA...')) + client = paramiko.SSHClient() + client.get_host_keys().add('ssh.example.com', 'ssh-rsa', key) + client.connect('ssh.example.com', username='strongbad', password='thecheat') + stdin, stdout, stderr = client.exec_command('ls') + for line in stdout: + print '... ' + line.strip('\n') + client.close() + +This prints out the results of executing ``ls`` on a remote server. The host +key 'AAA...' should of course be replaced by the actual base64 encoding of the +host key. If you skip host key verification, the connection is not secure! + +The following example scripts (in demos/) get progressively more detailed: + +:demo_simple.py: + Calls invoke_shell() and emulates a terminal/TTY through which you can + execute commands interactively on a remote server. Think of it as a + poor man's SSH command-line client. + +:demo.py: + Same as demo_simple.py, but allows you to authenticate using a private + key, attempts to use an SSH agent if present, and uses the long form of + some of the API calls. + +:forward.py: + Command-line script to set up port-forwarding across an SSH transport. + +:demo_sftp.py: + Opens an SFTP session and does a few simple file operations. + +:demo_server.py: + An SSH server that listens on port 2200 and accepts a login for + 'robey' (password 'foo'), and pretends to be a BBS. Meant to be a + very simple demo of writing an SSH server. + +:demo_keygen.py: + A key generator similar to OpenSSH ``ssh-keygen(1)`` program with + Paramiko keys generation and progress functions. + +Use +--- + +The demo scripts are probably the best example of how to use this package. +There is also a lot of documentation, generated with Sphinx autodoc, in the +doc/ folder. + +There are also unit tests here:: + + $ python ./test.py + +Which will verify that most of the core components are working correctly. -- cgit v1.2.3 From 1c3a65ec924b14dddb593a6dedc12f88a7431126 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 10 Dec 2015 18:18:27 -0800 Subject: ECDSA module req is 0.11 as of Paramiko 1.15+ --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index e78bda76..6c240e3d 100644 --- a/README.rst +++ b/README.rst @@ -45,7 +45,7 @@ Requirements - `Python `_ 2.6, 2.7, or 3.3+ (3.2 should also work, but it is not recommended) - `pycrypto `_ 2.1+ -- `ecdsa `_ 0.9+ +- `ecdsa `_ 0.11+ Installation -- cgit v1.2.3 From 8ca9a1d56447a4b42d5847161c3214ffa17cbfe6 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 10 Dec 2015 18:19:26 -0800 Subject: Changelog re #636 --- sites/www/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 278f7450..0eaa3f25 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,8 @@ Changelog ========= +* :support:`636` Clean up and enhance the README (and rename it to + ``README.rst`` from just ``README``). Thanks to ``@LucasRMehl``. * :bug:`401` Fix line number reporting in log output regarding invalid ``known_hosts`` line entries. Thanks to Dylan Thacker-Smith for catch & patch. -- cgit v1.2.3 From 99db33d25a21fb87f497af063d736d113733567c Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 10 Dec 2015 18:32:31 -0800 Subject: Fix ambiguous API refs causing sphinx to fart --- paramiko/channel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paramiko/channel.py b/paramiko/channel.py index 8a1c0f87..44a4b291 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -306,11 +306,11 @@ class Channel (ClosingContextManager): `.Transport` or session's ``window_size`` (e.g. that set by the ``default_window_size`` kwarg for `.Transport.__init__`) will cause `.recv_exit_status` to hang indefinitely if it is called prior to a - sufficiently large `.read` (or if there are no threads calling - `.read` in the background). + sufficiently large `~Channel..read` (or if there are no threads + calling `~Channel.read` in the background). In these cases, ensuring that `.recv_exit_status` is called *after* - `.read` (or, again, using threads) can avoid the hang. + `~Channel.read` (or, again, using threads) can avoid the hang. :return: the exit code (as an `int`) of the process on the server. -- cgit v1.2.3 From 0fc285fe50156cb2196bc8e9dddbccebf225d2fd Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 17 Dec 2015 07:12:18 -0500 Subject: Removed an out of date paragraph from the README It dates to 6ea60572afe6bb2fc30b197946ea653451fc240e and I'm quite certain it's no longer relevant. I've never heard of such an error in my professional life. --- README.rst | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.rst b/README.rst index 6c240e3d..0dcf30e5 100644 --- a/README.rst +++ b/README.rst @@ -72,17 +72,6 @@ dependency ``winrandom`` may not install properly, leading to an yourself. See `Fabric #194 `_ for info. -Some Python distributions don't include the UTF-8 string encodings, for -reasons of space (misguided as that is). If your distribution is -missing encodings, you'll see an error like this:: - - LookupError: no codec search functions registered: can't find encoding - -This means you need to copy string encodings over from a working system -(it probably only happens on embedded systems, not normal Python -installs). Valeriy Pogrebitskiy says the best place to look is -``.../lib/python*/encodings/__init__.py``. - Bugs & Support -------------- -- cgit v1.2.3 From d0d0c32b92bebfec3ff2627b30d3d4732f10ce89 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 17 Dec 2015 07:12:18 -0500 Subject: Removed an out of date paragraph from the README It dates to 6ea60572afe6bb2fc30b197946ea653451fc240e and I'm quite certain it's no longer relevant. I've never heard of such an error in my professional life. --- README.rst | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.rst b/README.rst index e78bda76..d2ed826c 100644 --- a/README.rst +++ b/README.rst @@ -72,17 +72,6 @@ dependency ``winrandom`` may not install properly, leading to an yourself. See `Fabric #194 `_ for info. -Some Python distributions don't include the UTF-8 string encodings, for -reasons of space (misguided as that is). If your distribution is -missing encodings, you'll see an error like this:: - - LookupError: no codec search functions registered: can't find encoding - -This means you need to copy string encodings over from a working system -(it probably only happens on embedded systems, not normal Python -installs). Valeriy Pogrebitskiy says the best place to look is -``.../lib/python*/encodings/__init__.py``. - Bugs & Support -------------- -- cgit v1.2.3