From 27b800bf3f1c0ee7063221538d2913cec7c048c1 Mon Sep 17 00:00:00 2001 From: Torsten Landschoff Date: Fri, 12 Aug 2011 11:25:36 +0200 Subject: Issue #22: Try IPv4 as well as IPv6 when port is not open on IPv6. With this change, paramiko tries the next address family when the connection gets refused or the target port is unreachable. --- paramiko/client.py | 53 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/paramiko/client.py b/paramiko/client.py index c5a2d1ac..da08ad0a 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -25,6 +25,7 @@ import getpass import os import socket import warnings +from errno import ECONNREFUSED, EHOSTUNREACH from paramiko.agent import Agent from paramiko.common import * @@ -231,6 +232,29 @@ class SSHClient (object): """ self._policy = policy + def _families_and_addresses(self, hostname, port): + """ + Yield pairs of address families and addresses to try for connecting. + + @param hostname: the server to connect to + @type hostname: str + @param port: the server port to connect to + @type port: int + @rtype: generator + """ + guess = True + addrinfos = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM) + for (family, socktype, proto, canonname, sockaddr) in addrinfos: + if socktype == socket.SOCK_STREAM: + yield family, sockaddr + guess = False + + # some OS like AIX don't indicate SOCK_STREAM support, so just guess. :( + # We only do this if we did not get a single result marked as socktype == SOCK_STREAM. + if guess: + for family, _, _, _, sockaddr in addrinfos: + yield family, sockaddr + def connect(self, hostname, port=SSH_PORT, username=None, password=None, pkey=None, key_filename=None, timeout=None, allow_agent=True, look_for_keys=True, compress=False, sock=None): @@ -288,21 +312,22 @@ class SSHClient (object): @raise socket.error: if a socket error occurred while connecting """ if not sock: - for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM): - if socktype == socket.SOCK_STREAM: - af = family - addr = sockaddr - break - else: - # some OS like AIX don't indicate SOCK_STREAM support, so just guess. :( - af, _, _, _, addr = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM) - sock = socket.socket(af, socket.SOCK_STREAM) - if timeout is not None: + for af, addr in self._families_and_addresses(hostname, port): try: - sock.settimeout(timeout) - except: - pass - retry_on_signal(lambda: sock.connect(addr)) + sock = socket.socket(af, socket.SOCK_STREAM) + if timeout is not None: + try: + sock.settimeout(timeout) + except: + pass + retry_on_signal(lambda: sock.connect(addr)) + # Break out of the loop on success + break + except socket.error, e: + # If the port is not open on IPv6 for example, we may still try IPv4. + # Likewise if the host is not reachable using that address family. + if e.errno not in (ECONNREFUSED, EHOSTUNREACH): + raise t = self._transport = Transport(sock) t.use_compression(compress=compress) -- cgit v1.2.3 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 67ae41a03ef2c94443e809b65f5eb2c4e7cf8937 Mon Sep 17 00:00:00 2001 From: ThiefMaster Date: Thu, 1 Jan 2015 13:50:52 +0100 Subject: Fix typo --- paramiko/_winapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paramiko/_winapi.py b/paramiko/_winapi.py index f48e1890..d6aabf76 100644 --- a/paramiko/_winapi.py +++ b/paramiko/_winapi.py @@ -106,7 +106,7 @@ MapViewOfFile.restype = ctypes.wintypes.HANDLE class MemoryMap(object): """ - A memory map object which can have security attributes overrideden. + A memory map object which can have security attributes overridden. """ def __init__(self, name, length, security_attributes=None): self.name = name -- cgit v1.2.3 From fac347718a69179ba9404d5f5e825798f4ee9379 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 2 Jan 2015 14:24:31 -0800 Subject: Happy new year --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index 61c5c852..a645b140 100644 --- a/README +++ b/README @@ -5,7 +5,7 @@ paramiko :Paramiko: Python SSH module :Copyright: Copyright (c) 2003-2009 Robey Pointer -:Copyright: Copyright (c) 2013-2014 Jeff Forcier +:Copyright: Copyright (c) 2013-2015 Jeff Forcier :License: LGPL :Homepage: https://github.com/paramiko/paramiko/ :API docs: http://docs.paramiko.org -- 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 3700d6e151d7891a12b958cf8f2729576205d927 Mon Sep 17 00:00:00 2001 From: Ken Jordan Date: Thu, 22 Jan 2015 19:39:22 -0700 Subject: Updated agent.py to print a more appropriate exception when unable to connect to the SSH agent --- paramiko/agent.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/paramiko/agent.py b/paramiko/agent.py index a75ac59e..185666e7 100644 --- a/paramiko/agent.py +++ b/paramiko/agent.py @@ -32,7 +32,7 @@ from select import select from paramiko.common import asbytes, io_sleep from paramiko.py3compat import byte_chr -from paramiko.ssh_exception import SSHException +from paramiko.ssh_exception import SSHException, AuthenticationException from paramiko.message import Message from paramiko.pkey import PKey from paramiko.util import retry_on_signal @@ -109,9 +109,15 @@ class AgentProxyThread(threading.Thread): def run(self): try: (r, addr) = self.get_connection() + # Found that r should be either a socket from the socket library or None self.__inr = r - self.__addr = addr + self.__addr = addr # This should be an IP address as a string? or None self._agent.connect() + print("Conn Value: ", self._agent._conn) + print("Has fileno method: ", hasattr(self._agent._conn, 'fileno')) + print("Verify isinstance of int: ", isinstance(self._agent, int)) + if self._agent._conn is None or not (hasattr(self._agent._conn, 'fileno') or isinstance(self._agent, int)): + raise AuthenticationException("Unable to connect to SSH agent") self._communicate() except: #XXX Not sure what to do here ... raise or pass ? -- cgit v1.2.3 From b26dbc3f719e9844721d38d6264c5cb5e0ed4c2f Mon Sep 17 00:00:00 2001 From: Ken Jordan Date: Thu, 22 Jan 2015 19:41:15 -0700 Subject: Removed debug print statements --- paramiko/agent.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/paramiko/agent.py b/paramiko/agent.py index 185666e7..01bd0252 100644 --- a/paramiko/agent.py +++ b/paramiko/agent.py @@ -113,9 +113,6 @@ class AgentProxyThread(threading.Thread): self.__inr = r self.__addr = addr # This should be an IP address as a string? or None self._agent.connect() - print("Conn Value: ", self._agent._conn) - print("Has fileno method: ", hasattr(self._agent._conn, 'fileno')) - print("Verify isinstance of int: ", isinstance(self._agent, int)) if self._agent._conn is None or not (hasattr(self._agent._conn, 'fileno') or isinstance(self._agent, int)): raise AuthenticationException("Unable to connect to SSH agent") self._communicate() -- cgit v1.2.3 From 394e269a081e6cf07d38fa5a375f0a03374c0d2b Mon Sep 17 00:00:00 2001 From: jordo1ken Date: Fri, 30 Jan 2015 17:57:40 -0700 Subject: Update agent.py Updated logic for error checking. --- paramiko/agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paramiko/agent.py b/paramiko/agent.py index 01bd0252..f928881e 100644 --- a/paramiko/agent.py +++ b/paramiko/agent.py @@ -113,7 +113,7 @@ class AgentProxyThread(threading.Thread): self.__inr = r self.__addr = addr # This should be an IP address as a string? or None self._agent.connect() - if self._agent._conn is None or not (hasattr(self._agent._conn, 'fileno') or isinstance(self._agent, int)): + if not isinstance(self._agent, int) and (self._agent._conn is None or not hasattr(self._agent._conn, 'fileno')): raise AuthenticationException("Unable to connect to SSH agent") self._communicate() except: -- cgit v1.2.3 From c5d0d6a2919ca2158b3f6271f7449faeeb3c865f Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 4 Feb 2015 16:00:50 -0800 Subject: Changelog fixes #402, closes #479 --- sites/www/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index bb93f885..6520dde4 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,10 @@ Changelog ========= +* :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. * :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 d97c938db32c44a8253f6f872cc3b354492a21cc Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 6 Feb 2015 15:33:28 -0800 Subject: Fix docstring for Sphinx --- paramiko/client.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/paramiko/client.py b/paramiko/client.py index 312f71a9..dbe7fcba 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -177,11 +177,9 @@ class SSHClient (ClosingContextManager): """ Yield pairs of address families and addresses to try for connecting. - @param hostname: the server to connect to - @type hostname: str - @param port: the server port to connect to - @type port: int - @rtype: generator + :param str hostname: the server to connect to + :param int port: the server port to connect to + :returns: Yields an iterable of ``(family, address)`` tuples """ guess = True addrinfos = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM) -- cgit v1.2.3 From b42b5338de868c3a5343dd03a8ee3f8a968d2f65 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 6 Feb 2015 15:33:32 -0800 Subject: Comment --- paramiko/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/paramiko/client.py b/paramiko/client.py index dbe7fcba..b907d3b0 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -256,6 +256,7 @@ class SSHClient (ClosingContextManager): ``gss_deleg_creds`` and ``gss_host`` arguments. """ if not sock: + # Try multiple possible address families (e.g. IPv4 vs IPv6) for af, addr in self._families_and_addresses(hostname, port): try: sock = socket.socket(af, socket.SOCK_STREAM) -- cgit v1.2.3 From c2e346372c06c5eb4982055c689e1eb89955bddf Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 6 Feb 2015 16:20:51 -0800 Subject: Bump dev version. (might end up 2.0 tho) --- paramiko/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paramiko/_version.py b/paramiko/_version.py index 3bf9dac7..e82b8667 100644 --- a/paramiko/_version.py +++ b/paramiko/_version.py @@ -1,2 +1,2 @@ -__version_info__ = (1, 15, 2) +__version_info__ = (1, 16, 0) __version__ = '.'.join(map(str, __version_info__)) -- cgit v1.2.3 From f99e1d8775be20c471c39f8d7a91126b8419381d Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 6 Feb 2015 19:33:06 -0800 Subject: Raise usefully ambiguous error when every connect attempt fails. Re #22 --- paramiko/client.py | 24 ++++++++++++++++++++---- paramiko/ssh_exception.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/paramiko/client.py b/paramiko/client.py index b907d3b0..57e9919d 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -36,7 +36,9 @@ from paramiko.hostkeys import HostKeys from paramiko.py3compat import string_types from paramiko.resource import ResourceManager from paramiko.rsakey import RSAKey -from paramiko.ssh_exception import SSHException, BadHostKeyException +from paramiko.ssh_exception import ( + SSHException, BadHostKeyException, ConnectionError +) from paramiko.transport import Transport from paramiko.util import retry_on_signal, ClosingContextManager @@ -256,8 +258,10 @@ class SSHClient (ClosingContextManager): ``gss_deleg_creds`` and ``gss_host`` arguments. """ if not sock: + errors = {} # Try multiple possible address families (e.g. IPv4 vs IPv6) - for af, addr in self._families_and_addresses(hostname, port): + to_try = list(self._families_and_addresses(hostname, port)) + for af, addr in to_try: try: sock = socket.socket(af, socket.SOCK_STREAM) if timeout is not None: @@ -269,10 +273,22 @@ class SSHClient (ClosingContextManager): # Break out of the loop on success break except socket.error, e: - # If the port is not open on IPv6 for example, we may still try IPv4. - # Likewise if the host is not reachable using that address family. + # Raise anything that isn't a straight up connection error + # (such as a resolution error) if e.errno not in (ECONNREFUSED, EHOSTUNREACH): raise + # Capture anything else so we know how the run looks once + # iteration is complete. Retain info about which attempt + # this was. + errors[addr] = e + + # Make sure we explode usefully if no address family attempts + # succeeded. We've no way of knowing which error is the "right" + # one, so we construct a hybrid exception containing all the real + # ones, of a subclass that client code should still be watching for + # (socket.error) + if len(errors) == len(to_try): + raise ConnectionError(errors) t = self._transport = Transport(sock, gss_kex=gss_kex, gss_deleg_creds=gss_deleg_creds) t.use_compression(compress=compress) diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py index b99e42b3..7e6f2568 100644 --- a/paramiko/ssh_exception.py +++ b/paramiko/ssh_exception.py @@ -16,6 +16,8 @@ # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. +import socket + class SSHException (Exception): """ @@ -129,3 +131,31 @@ class ProxyCommandFailure (SSHException): self.error = error # for unpickling self.args = (command, error, ) + + +class ConnectionError(socket.error): + """ + High-level socket error wrapping 1+ actual socket.error objects. + + To see the wrapped exception objects, access the ``errors`` attribute. + ``errors`` is a dict whose keys are address tuples (e.g. ``('127.0.0.1', + 22)``) and whose values are the exception encountered trying to connect to + that address. + + 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). + """ + def __init__(self, errors): + """ + :param dict errors: + The errors dict to store, as described by class docstring. + """ + addrs = errors.keys() + body = ', '.join([x[0] for x in addrs[:-1]]) + tail = addrs[-1][0] + msg = "Unable to connect to port {0} at {1} or {2}" + super(ConnectionError, self).__init__( + msg.format(addrs[0][1], body, tail) + ) + self.errors = errors -- 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 4ca8d68c0443c4e5e17ae4fcee39dd6f2507c7cd Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 27 Feb 2015 13:19:35 -0800 Subject: Changelog closes #22 --- sites/www/changelog.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 6520dde4..0e8f92c4 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,11 @@ Changelog ========= +* :bug:`22 major` Try harder to connect to multiple network families (e.g. IPv4 + vs IPv6) in case of connection issues; this helps with problems such as hosts + which resolve both IPv4 and IPv6 addresses but are only listening on IPv4. + Thanks to Dries Desmet for original report and Torsten Landschoff for the + foundational patchset. * :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 27e6177f4b7bae87c9fc7aa3556d20100ff9a319 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 27 Feb 2015 13:29:32 -0800 Subject: Fix a Python 3 incompat bit from recent merge --- paramiko/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paramiko/client.py b/paramiko/client.py index 57e9919d..2e1a4dc4 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -272,7 +272,7 @@ class SSHClient (ClosingContextManager): retry_on_signal(lambda: sock.connect(addr)) # Break out of the loop on success break - except socket.error, e: + except socket.error as e: # Raise anything that isn't a straight up connection error # (such as a resolution error) if e.errno not in (ECONNREFUSED, EHOSTUNREACH): -- cgit v1.2.3 From 3ceab6a5f65db5be219b3dba17677e3101a0489f Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 27 Feb 2015 14:52:26 -0800 Subject: Some 80-col fixes --- paramiko/client.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/paramiko/client.py b/paramiko/client.py index 2e1a4dc4..6c48b269 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -196,10 +196,25 @@ class SSHClient (ClosingContextManager): for family, _, _, _, sockaddr in addrinfos: yield family, sockaddr - def connect(self, hostname, port=SSH_PORT, username=None, password=None, pkey=None, - key_filename=None, timeout=None, allow_agent=True, look_for_keys=True, - compress=False, sock=None, gss_auth=False, gss_kex=False, - gss_deleg_creds=True, gss_host=None, banner_timeout=None): + def connect( + self, + hostname, + port=SSH_PORT, + username=None, + password=None, + pkey=None, + key_filename=None, + timeout=None, + allow_agent=True, + look_for_keys=True, + compress=False, + sock=None, + gss_auth=False, + gss_kex=False, + gss_deleg_creds=True, + gss_host=None, + banner_timeout=None + ): """ Connect to an SSH server and authenticate to it. The server's host key is checked against the system host keys (see `load_system_host_keys`) @@ -230,8 +245,10 @@ class SSHClient (ClosingContextManager): :param str key_filename: the filename, or list of filenames, of optional private key(s) to try for authentication - :param float timeout: an optional timeout (in seconds) for the TCP connect - :param bool allow_agent: set to False to disable connecting to the SSH agent + :param float timeout: + an optional timeout (in seconds) for the TCP connect + :param bool allow_agent: + set to False to disable connecting to the SSH agent :param bool look_for_keys: set to False to disable searching for discoverable private key files in ``~/.ssh/`` @@ -240,9 +257,11 @@ class SSHClient (ClosingContextManager): 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_kex: Perform GSS-API Key Exchange and user 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 - :param str gss_host: The targets name in the kerberos database. default: hostname + :param str gss_host: + The targets name in the kerberos database. default: hostname :param float banner_timeout: an optional timeout (in seconds) to wait for the SSH banner to be presented. -- cgit v1.2.3 From ca0fd1024ecf61b1758bdd38350fbd4c4ccaaefb Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sat, 28 Feb 2015 19:54:52 -0800 Subject: Replace/add RFC links using ``:rfc:``, /ht @sigmavirus24 --- paramiko/channel.py | 2 +- paramiko/kex_gss.py | 27 +++++++++++++++------------ paramiko/ssh_gss.py | 2 +- sites/www/index.rst | 8 ++------ 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/paramiko/channel.py b/paramiko/channel.py index 8a97c974..7e39a15b 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -337,7 +337,7 @@ class Channel (ClosingContextManager): further x11 requests can be made from the server to the client, when an x11 application is run in a shell session. - From RFC4254:: + From :rfc:`4254`:: It is RECOMMENDED that the 'x11 authentication cookie' that is sent be a fake, random cookie, and that the cookie be checked and diff --git a/paramiko/kex_gss.py b/paramiko/kex_gss.py index 4e8380ef..d026807c 100644 --- a/paramiko/kex_gss.py +++ b/paramiko/kex_gss.py @@ -21,14 +21,15 @@ """ -This module provides GSS-API / SSPI Key Exchange as defined in RFC 4462. +This module provides GSS-API / SSPI Key Exchange as defined in :rfc:`4462`. .. note:: Credential delegation is not supported in server mode. .. note:: - `RFC 4462 Section 2.2 `_ says we are - not required to implement GSS-API error messages. Thus, in many methods - within this module, if an error occurs an exception will be thrown and the + `RFC 4462 Section 2.2 + `_ says we are not + required to implement GSS-API error messages. Thus, in many methods within + this module, if an error occurs an exception will be thrown and the connection will be terminated. .. seealso:: :doc:`/api/ssh_gss` @@ -55,8 +56,8 @@ c_MSG_KEXGSS_GROUPREQ, c_MSG_KEXGSS_GROUP = [byte_chr(c) for c in range(40, 42)] class KexGSSGroup1(object): """ - GSS-API / SSPI Authenticated Diffie-Hellman Key Exchange - as defined in `RFC 4462 Section 2 `_ + GSS-API / SSPI Authenticated Diffie-Hellman Key Exchange as defined in `RFC + 4462 Section 2 `_ """ # draft-ietf-secsh-transport-09.txt, page 17 P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF @@ -278,8 +279,9 @@ class KexGSSGroup1(object): class KexGSSGroup14(KexGSSGroup1): """ - GSS-API / SSPI Authenticated Diffie-Hellman Group14 Key Exchange - as defined in `RFC 4462 Section 2 `_ + GSS-API / SSPI Authenticated Diffie-Hellman Group14 Key Exchange as defined + in `RFC 4462 Section 2 + `_ """ P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF G = 2 @@ -288,8 +290,8 @@ class KexGSSGroup14(KexGSSGroup1): class KexGSSGex(object): """ - GSS-API / SSPI Authenticated Diffie-Hellman Group Exchange - as defined in `RFC 4462 Section 2 `_ + GSS-API / SSPI Authenticated Diffie-Hellman Group Exchange as defined in + `RFC 4462 Section 2 `_ """ NAME = "gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==" min_bits = 1024 @@ -590,8 +592,9 @@ class KexGSSGex(object): class NullHostKey(object): """ - This class represents the Null Host Key for GSS-API Key Exchange - as defined in `RFC 4462 Section 5 `_ + This class represents the Null Host Key for GSS-API Key Exchange as defined + in `RFC 4462 Section 5 + `_ """ def __init__(self): self.key = "" diff --git a/paramiko/ssh_gss.py b/paramiko/ssh_gss.py index ebf2cc80..aa28e2ec 100644 --- a/paramiko/ssh_gss.py +++ b/paramiko/ssh_gss.py @@ -20,7 +20,7 @@ """ -This module provides GSS-API / SSPI authentication as defined in RFC 4462. +This module provides GSS-API / SSPI authentication as defined in :rfc:`4462`. .. note:: Credential delegation is not supported in server mode. diff --git a/sites/www/index.rst b/sites/www/index.rst index 1b609709..8e7562af 100644 --- a/sites/www/index.rst +++ b/sites/www/index.rst @@ -26,11 +26,7 @@ Please see the sidebar to the left to begin. .. rubric:: Footnotes .. [#] - SSH is defined in RFCs - `4251 `_, - `4252 `_, - `4253 `_, and - `4254 `_; - the primary working implementation of the protocol is the `OpenSSH project + SSH is defined in :rfc:`4251`, :rfc:`4252`, :rfc:`4253` and :rfc:`4254`. The + primary working implementation of the protocol is the `OpenSSH project `_. Paramiko implements a large portion of the SSH feature set, but there are occasional gaps. -- 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 b0a5ca8e3747a34082895bbd170a617f76ebe7e5 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 5 Mar 2015 09:54:41 -0800 Subject: Rename new exception class to be less generic Re #22 --- paramiko/client.py | 4 ++-- paramiko/ssh_exception.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/paramiko/client.py b/paramiko/client.py index 6c48b269..e10e9da7 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -37,7 +37,7 @@ from paramiko.py3compat import string_types from paramiko.resource import ResourceManager from paramiko.rsakey import RSAKey from paramiko.ssh_exception import ( - SSHException, BadHostKeyException, ConnectionError + SSHException, BadHostKeyException, NoValidConnectionsError ) from paramiko.transport import Transport from paramiko.util import retry_on_signal, ClosingContextManager @@ -307,7 +307,7 @@ class SSHClient (ClosingContextManager): # ones, of a subclass that client code should still be watching for # (socket.error) if len(errors) == len(to_try): - raise ConnectionError(errors) + raise NoValidConnectionsError(errors) t = self._transport = Transport(sock, gss_kex=gss_kex, gss_deleg_creds=gss_deleg_creds) t.use_compression(compress=compress) diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py index 7e6f2568..169dad81 100644 --- a/paramiko/ssh_exception.py +++ b/paramiko/ssh_exception.py @@ -133,7 +133,7 @@ class ProxyCommandFailure (SSHException): self.args = (command, error, ) -class ConnectionError(socket.error): +class NoValidConnectionsError(socket.error): """ High-level socket error wrapping 1+ actual socket.error objects. @@ -155,7 +155,7 @@ class ConnectionError(socket.error): body = ', '.join([x[0] for x in addrs[:-1]]) tail = addrs[-1][0] msg = "Unable to connect to port {0} at {1} or {2}" - super(ConnectionError, self).__init__( + super(NoValidConnectionsError, self).__init__( msg.format(addrs[0][1], body, tail) ) self.errors = errors -- cgit v1.2.3 From 136e0deef9c949a1b223244b696e6d870cc12e34 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 5 Mar 2015 09:55:29 -0800 Subject: Add null errno to socket.error subclass. Makes downstream code less likely to break when they expect errno+msg style socket error objects. Re #22 --- paramiko/ssh_exception.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py index 169dad81..1fbebde8 100644 --- a/paramiko/ssh_exception.py +++ b/paramiko/ssh_exception.py @@ -135,7 +135,14 @@ class ProxyCommandFailure (SSHException): class NoValidConnectionsError(socket.error): """ - High-level socket error wrapping 1+ actual socket.error objects. + Multiple connection attempts were made and no families succeeded. + + This exception class wraps multiple "real" underlying connection errors, + all of which represent failed connection attempts. Because these errors are + not guaranteed to all be of the same error type (i.e. different errno, + class, message, etc) we expose a single unified error message and a + ``None`` errno so that instances of this class match most normal handling + of `socket.error` objects. To see the wrapped exception objects, access the ``errors`` attribute. ``errors`` is a dict whose keys are address tuples (e.g. ``('127.0.0.1', @@ -156,6 +163,7 @@ class NoValidConnectionsError(socket.error): tail = addrs[-1][0] msg = "Unable to connect to port {0} at {1} or {2}" super(NoValidConnectionsError, self).__init__( + None, # stand-in for errno msg.format(addrs[0][1], body, tail) ) self.errors = errors -- cgit v1.2.3 From cfeca480db0116c5c8d95ba1aaa9e5e5e02951ed Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 5 Mar 2015 09:55:36 -0800 Subject: Error message langauge tweak --- paramiko/ssh_exception.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py index 1fbebde8..2e09c6d6 100644 --- a/paramiko/ssh_exception.py +++ b/paramiko/ssh_exception.py @@ -161,7 +161,7 @@ class NoValidConnectionsError(socket.error): addrs = errors.keys() body = ', '.join([x[0] for x in addrs[:-1]]) tail = addrs[-1][0] - msg = "Unable to connect to port {0} at {1} or {2}" + msg = "Unable to connect to port {0} on {1} or {2}" super(NoValidConnectionsError, self).__init__( None, # stand-in for errno msg.format(addrs[0][1], body, tail) -- 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 7400ce4fd80fc6c0cfc1b3d96900ee2fb87f9ebe Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 29 Apr 2015 19:25:25 -0700 Subject: Packaging updates --- dev-requirements.txt | 8 +++++--- tasks.py | 24 +++--------------------- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 7a0ccbc5..059572cf 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,9 +1,11 @@ # Older junk tox>=1.4,<1.5 # For newer tasks like building Sphinx docs. -invoke>=0.7.0,<0.8 -invocations>=0.5.0 +invoke>=0.10 +invocations>=0.9.2 sphinx>=1.1.3 alabaster>=0.6.1 releases>=0.5.2 -wheel==0.23.0 +semantic_version>=2.4,<2.5 +wheel==0.24 +twine==1.5 diff --git a/tasks.py b/tasks.py index 1afce514..20ded03d 100644 --- a/tasks.py +++ b/tasks.py @@ -3,28 +3,10 @@ from os.path import join from shutil import rmtree, copytree from invoke import Collection, ctask as task -from invocations import docs as _docs +from invocations.docs import docs, www from invocations.packaging import publish -d = 'sites' - -# Usage doc/API site (published as docs.paramiko.org) -docs_path = join(d, 'docs') -docs_build = join(docs_path, '_build') -docs = Collection.from_module(_docs, name='docs', config={ - 'sphinx.source': docs_path, - 'sphinx.target': docs_build, -}) - -# Main/about/changelog site ((www.)?paramiko.org) -www_path = join(d, 'www') -www = Collection.from_module(_docs, name='www', config={ - 'sphinx.source': www_path, - 'sphinx.target': join(www_path, '_build'), -}) - - # Until we move to spec-based testing @task def test(ctx): @@ -45,9 +27,9 @@ def release(ctx): rmtree(target, ignore_errors=True) copytree(docs_build, target) # Publish - publish(ctx, wheel=True) + publish(ctx) # Remind print("\n\nDon't forget to update RTD's versions page for new minor releases!") -ns = Collection(test, coverage, release, docs=docs, www=www) +ns = Collection(test, coverage, release, docs, www) -- cgit v1.2.3 From 9c77538747881bb8cb3f6c7b220515cfd6943b92 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 29 Apr 2015 19:39:04 -0700 Subject: Not sure how this got nuked --- tasks.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tasks.py b/tasks.py index c7d28b16..3d575670 100644 --- a/tasks.py +++ b/tasks.py @@ -17,6 +17,11 @@ def test(ctx, coverage=False): ctx.run("{0} test.py {1}".format(runner, flags), pty=True) +@task +def coverage(ctx): + ctx.run("coverage run --source=paramiko test.py --verbose") + + # Until we stop bundling docs w/ releases. Need to discover use cases first. @task def release(ctx): -- 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 dd9135b24174ba8b02e0982ce2f9026cd22bef21 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 11 Jun 2015 16:42:07 -0700 Subject: Tweak docstring --- paramiko/ssh_exception.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py index 2e09c6d6..d053974a 100644 --- a/paramiko/ssh_exception.py +++ b/paramiko/ssh_exception.py @@ -140,9 +140,9 @@ class NoValidConnectionsError(socket.error): This exception class wraps multiple "real" underlying connection errors, all of which represent failed connection attempts. Because these errors are not guaranteed to all be of the same error type (i.e. different errno, - class, message, etc) we expose a single unified error message and a - ``None`` errno so that instances of this class match most normal handling - of `socket.error` objects. + `socket.error` subclass, message, etc) we expose a single unified error + message and a ``None`` errno so that instances of this class match most + normal handling of `socket.error` objects. To see the wrapped exception objects, access the ``errors`` attribute. ``errors`` is a dict whose keys are address tuples (e.g. ``('127.0.0.1', -- cgit v1.2.3 From 7a6e89bcbd75c495205ddf7520c044000c2e6a65 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 17 Aug 2015 21:00:05 -0700 Subject: Add TODO found while poking API from fabric v2 --- paramiko/sftp_client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index 89840eaa..6d48e692 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -589,6 +589,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): .. versionadded:: 1.4 """ + # TODO: make class initialize with self._cwd set to self.normalize('.') return self._cwd and u(self._cwd) def putfo(self, fl, remotepath, file_size=0, callback=None, confirm=True): -- cgit v1.2.3 From 6c5df3650a90ca2567e6f865de237c5fb1fadf5d Mon Sep 17 00:00:00 2001 From: Peter Odding Date: Sat, 5 Sep 2015 17:57:45 +0200 Subject: Try to fix `python setup.py bdist_dumb' on Mac OS X (paylogic/pip-accel#2) Additions based on /usr/lib/python2.7/distutils/archive_util.py from the Ubuntu 12.04 package python2.7 (2.7.3-0ubuntu3.8). I have not looked into the compatibility of the software licenses of Paramiko vs distutils however the original setup_helper.py code in Paramiko was clearly also copied from distutils so to be honest it's not like I'm changing the status quo. --- setup_helper.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/setup_helper.py b/setup_helper.py index ff6b0e16..5d23137b 100644 --- a/setup_helper.py +++ b/setup_helper.py @@ -30,9 +30,42 @@ import distutils.archive_util from distutils.dir_util import mkpath from distutils.spawn import spawn - -def make_tarball(base_name, base_dir, compress='gzip', - verbose=False, dry_run=False): +try: + from pwd import getpwnam +except ImportError: + getpwnam = None + +try: + from grp import getgrnam +except ImportError: + getgrnam = None + +def _get_gid(name): + """Returns a gid, given a group name.""" + if getgrnam is None or name is None: + return None + try: + result = getgrnam(name) + except KeyError: + result = None + if result is not None: + return result[2] + return None + +def _get_uid(name): + """Returns an uid, given a user name.""" + if getpwnam is None or name is None: + return None + try: + result = getpwnam(name) + except KeyError: + result = None + if result is not None: + return result[2] + return None + +def make_tarball(base_name, base_dir, compress='gzip', verbose=0, dry_run=0, + owner=None, group=None): """Create a tar file from all the files under 'base_dir'. This file may be compressed. @@ -75,11 +108,25 @@ def make_tarball(base_name, base_dir, compress='gzip', mkpath(os.path.dirname(archive_name), dry_run=dry_run) log.info('Creating tar file %s with mode %s' % (archive_name, mode)) + uid = _get_uid(owner) + gid = _get_gid(group) + + def _set_uid_gid(tarinfo): + if gid is not None: + tarinfo.gid = gid + tarinfo.gname = group + if uid is not None: + tarinfo.uid = uid + tarinfo.uname = owner + return tarinfo + if not dry_run: tar = tarfile.open(archive_name, mode=mode) # This recursively adds everything underneath base_dir - tar.add(base_dir) - tar.close() + try: + tar.add(base_dir, filter=_set_uid_gid) + finally: + tar.close() if compress and compress not in tarfile_compress_flag: spawn([compress] + compress_flags[compress] + [archive_name], -- cgit v1.2.3 From a7c8266fe784dffa2a5fdd1526437c6ba7ba1aab Mon Sep 17 00:00:00 2001 From: Peter Odding Date: Wed, 9 Sep 2015 22:18:24 +0200 Subject: Restore Python 2.6 compatibility for `python setup.py {s,b}dist' --- setup_helper.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup_helper.py b/setup_helper.py index 5d23137b..9e3834b3 100644 --- a/setup_helper.py +++ b/setup_helper.py @@ -124,7 +124,12 @@ def make_tarball(base_name, base_dir, compress='gzip', verbose=0, dry_run=0, tar = tarfile.open(archive_name, mode=mode) # This recursively adds everything underneath base_dir try: - tar.add(base_dir, filter=_set_uid_gid) + try: + # Support for the `filter' parameter was added in Python 2.7, + # earlier versions will raise TypeError. + tar.add(base_dir, filter=_set_uid_gid) + except TypeError: + tar.add(base_dir) finally: tar.close() -- cgit v1.2.3 From 97e134aa43c9632f34be278ca1d08f56cc83993a Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 10 Sep 2015 14:09:13 -0700 Subject: Changelog fixes #582 --- sites/www/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 0e8f92c4..6379dba9 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,8 @@ Changelog ========= +* :support:`582` Fix some old ``setup.py`` related helper code which was + breaking ``bdist_dumb`` on Mac OS X. Thanks to Peter Odding for the patch. * :bug:`22 major` Try harder to connect to multiple network families (e.g. IPv4 vs IPv6) in case of connection issues; this helps with problems such as hosts which resolve both IPv4 and IPv6 addresses but are only listening on IPv4. -- 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