From 8a836942d545ffa44ed139099dfea4f96d336add Mon Sep 17 00:00:00 2001 From: Yan Kalchevskiy Date: Tue, 16 Jul 2013 14:02:24 +0700 Subject: Add support quoted values for SSHConfig (#157) --- tests/test_util.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) (limited to 'tests') diff --git a/tests/test_util.py b/tests/test_util.py index 69c75518..4e67e071 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -333,3 +333,71 @@ IdentityFile something_%l_using_fqdn """ config = paramiko.util.parse_ssh_config(StringIO(test_config)) assert config.lookup('meh') # will die during lookup() if bug regresses + + def test_quoted_host_names(self): + test_config_file = """\ +Host "param pam" param "pam" + Port 1111 + +Host "param2" + Port 2222 + +Host param3 parara + Port 3333 + +Host param4 "p a r" "p" "par" para + Port 4444 +""" + res = { + 'param pam': {'hostname': 'param pam', 'port': '1111'}, + 'param': {'hostname': 'param', 'port': '1111'}, + 'pam': {'hostname': 'pam', 'port': '1111'}, + + 'param2': {'hostname': 'param2', 'port': '2222'}, + + 'param3': {'hostname': 'param3', 'port': '3333'}, + 'parara': {'hostname': 'parara', 'port': '3333'}, + + 'param4': {'hostname': 'param4', 'port': '4444'}, + 'p a r': {'hostname': 'p a r', 'port': '4444'}, + 'p': {'hostname': 'p', 'port': '4444'}, + 'par': {'hostname': 'par', 'port': '4444'}, + 'para': {'hostname': 'para', 'port': '4444'}, + } + f = StringIO(test_config_file) + config = paramiko.util.parse_ssh_config(f) + for host, values in res.items(): + self.assertEquals( + paramiko.util.lookup_ssh_host_config(host, config), + values + ) + + def test_quoted_params_in_config(self): + test_config_file = """\ +Host "param pam" param "pam" + IdentityFile id_rsa + +Host "param2" + IdentityFile "test rsa key" + +Host param3 parara + IdentityFile id_rsa + IdentityFile "test rsa key" +""" + res = { + 'param pam': {'hostname': 'param pam', 'identityfile': ['id_rsa']}, + 'param': {'hostname': 'param', 'identityfile': ['id_rsa']}, + 'pam': {'hostname': 'pam', 'identityfile': ['id_rsa']}, + + 'param2': {'hostname': 'param2', 'identityfile': ['test rsa key']}, + + 'param3': {'hostname': 'param3', 'identityfile': ['id_rsa', 'test rsa key']}, + 'parara': {'hostname': 'parara', 'identityfile': ['id_rsa', 'test rsa key']}, + } + f = StringIO(test_config_file) + config = paramiko.util.parse_ssh_config(f) + for host, values in res.items(): + self.assertEquals( + paramiko.util.lookup_ssh_host_config(host, config), + values + ) -- cgit v1.2.3 From f258d1e207a21d4342dbc431fcc4a4dafe893e80 Mon Sep 17 00:00:00 2001 From: Yan Kalchevksiy Date: Sat, 15 Feb 2014 17:20:31 +0700 Subject: Moved get_hosts function into method. --- paramiko/config.py | 46 +++++++++++++++++++++++++--------------------- tests/test_util.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 21 deletions(-) (limited to 'tests') diff --git a/paramiko/config.py b/paramiko/config.py index dce472ee..5abf181f 100644 --- a/paramiko/config.py +++ b/paramiko/config.py @@ -54,26 +54,6 @@ class SSHConfig (object): :param file file_obj: a file-like object to read the config file from """ - def get_hosts(val): - i, length = 0, len(val) - hosts = [] - while i < length: - if val[i] == '"': - end = val.find('"', i + 1) - if end < 0: - raise Exception("Unparsable host %s" % val) - hosts.append(val[i + 1:end]) - i = end + 1 - elif not val[i].isspace(): - end = i + 1 - while end < length and not val[end].isspace(): - end += 1 - hosts.append(val[i:end]) - i = end + 1 - else: - i += 1 - - return hosts host = {"host": ['*'], "config": {}} for line in file_obj: @@ -90,7 +70,7 @@ class SSHConfig (object): if key == 'host': self._config.append(host) host = { - 'host': get_hosts(value), + 'host': self._get_hosts(value), 'config': {} } else: @@ -222,6 +202,30 @@ class SSHConfig (object): config[k] = config[k].replace(find, str(replace)) return config + def _get_hosts(self, host): + """ + Return a list of host_names from host value. + """ + i, length = 0, len(host) + hosts = [] + while i < length: + if host[i] == '"': + end = host.find('"', i + 1) + if end < 0: + raise Exception("Unparsable host %s" % host) + hosts.append(host[i + 1:end]) + i = end + 1 + elif not host[i].isspace(): + end = i + 1 + while end < length and not host[end].isspace() and host[end] != '"': + end += 1 + hosts.append(host[i:end]) + i = end + else: + i += 1 + + return hosts + class LazyFqdn(object): """ diff --git a/tests/test_util.py b/tests/test_util.py index 4e67e071..8142d416 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -401,3 +401,35 @@ Host param3 parara paramiko.util.lookup_ssh_host_config(host, config), values ) + + def test_quoted_host_in_config(self): + conf = SSHConfig() + correct_data = { + 'param': ['param'], + '"param"': ['param'], + + 'param pam': ['param', 'pam'], + '"param" "pam"': ['param', 'pam'], + '"param" pam': ['param', 'pam'], + 'param "pam"': ['param', 'pam'], + + 'param "pam" p': ['param', 'pam', 'p'], + '"param" pam "p"': ['param', 'pam', 'p'], + + '"pa ram"': ['pa ram'], + '"pa ram" pam': ['pa ram', 'pam'], + 'param "p a m"': ['param', 'p a m'], + } + incorrect_data = [ + 'param"', + '"param', + 'param "pam', + 'param "pam" "p a', + ] + for host, values in correct_data.items(): + self.assertEquals( + conf._get_hosts(host), + values + ) + for host in incorrect_data: + self.assertRaises(Exception, conf._get_hosts, host) -- cgit v1.2.3 From 80148a3039499fb52d7b5c7a583c14258ccf9cae Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Wed, 13 Aug 2014 16:56:14 +0200 Subject: Change window and packet size to match opensshs'. Update tests to match the new numbers. --- paramiko/transport.py | 4 ++-- tests/test_transport.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'tests') diff --git a/paramiko/transport.py b/paramiko/transport.py index 406626a7..bda05443 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -231,8 +231,8 @@ class Transport (threading.Thread): self.channel_events = {} # (id -> Event) self.channels_seen = {} # (id -> True) self._channel_counter = 1 - self.window_size = 65536 - self.max_packet_size = 34816 + self.max_packet_size = 2 ** 15 + self.window_size = 64 * self.max_packet_size self._forward_agent_handler = None self._x11_handler = None self._tcp_handler = None diff --git a/tests/test_transport.py b/tests/test_transport.py index 485a18e8..d84747b6 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -585,12 +585,13 @@ class TransportTest(ParamikoTest): self.assertEqual(chan.send_ready(), True) total = 0 K = '*' * 1024 - while total < 1024 * 1024: + limit = 1+(64 * 2 ** 15) + while total < limit: chan.send(K) total += len(K) if not chan.send_ready(): break - self.assertTrue(total < 1024 * 1024) + self.assertTrue(total < limit) schan.close() chan.close() -- cgit v1.2.3 From 2dec0a7671d83f21a290e1a2b3ea9159da45757a Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Thu, 14 Aug 2014 15:13:58 +0200 Subject: Add a utility method for value clamping. --- paramiko/util.py | 3 +++ tests/test_util.py | 5 +++++ 2 files changed, 8 insertions(+) (limited to 'tests') diff --git a/paramiko/util.py b/paramiko/util.py index f4ee3adc..d029f52e 100644 --- a/paramiko/util.py +++ b/paramiko/util.py @@ -320,3 +320,6 @@ def constant_time_bytes_eq(a, b): for i in (xrange if PY2 else range)(len(a)): res |= byte_ord(a[i]) ^ byte_ord(b[i]) return res == 0 + +def clamp_value(minimum, val, maximum): + return max(minimum, min(val, maximum)) diff --git a/tests/test_util.py b/tests/test_util.py index 69c75518..24f58076 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -333,3 +333,8 @@ IdentityFile something_%l_using_fqdn """ config = paramiko.util.parse_ssh_config(StringIO(test_config)) assert config.lookup('meh') # will die during lookup() if bug regresses + + def test_12_clamp_value(self): + self.assertEqual(32768, paramiko.util.clamp_value(32767, 32768, 32769)) + self.assertEqual(32767, paramiko.util.clamp_value(32767, 32765, 32769)) + self.assertEqual(32769, paramiko.util.clamp_value(32767, 32770, 32769)) -- cgit v1.2.3 From c0df755ead0eb54d798dd3f49b7dd86b8ad6baeb Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Thu, 14 Aug 2014 14:22:59 +0200 Subject: Add sanitation methods for window and packet size. --- paramiko/transport.py | 21 +++++--- tests/test_transport.py | 124 ++++++++++++++++++++++++++++-------------------- 2 files changed, 87 insertions(+), 58 deletions(-) (limited to 'tests') diff --git a/paramiko/transport.py b/paramiko/transport.py index 78d34414..da67408f 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -31,7 +31,7 @@ from hashlib import md5, sha1 import paramiko from paramiko import util from paramiko.auth_handler import AuthHandler -from paramiko.channel import Channel +from paramiko.channel import Channel, MIN_PACKET_SIZE, MAX_WINDOW_SIZE from paramiko.common import xffffffff, cMSG_CHANNEL_OPEN, cMSG_IGNORE, \ cMSG_GLOBAL_REQUEST, DEBUG, MSG_KEXINIT, MSG_IGNORE, MSG_DISCONNECT, \ MSG_DEBUG, ERROR, WARNING, cMSG_UNIMPLEMENTED, INFO, cMSG_KEXINIT, \ @@ -57,7 +57,7 @@ from paramiko.server import ServerInterface from paramiko.sftp_client import SFTPClient from paramiko.ssh_exception import (SSHException, BadAuthenticationType, ChannelException, ProxyCommandFailure) -from paramiko.util import retry_on_signal +from paramiko.util import retry_on_signal, clamp_value from Crypto.Cipher import Blowfish, AES, DES3, ARC4 try: @@ -644,10 +644,8 @@ class Transport (threading.Thread): raise SSHException('SSH session not active') self.lock.acquire() try: - if window_size is None: - window_size = self.default_window_size - if max_packet_size is None: - max_packet_size = self.default_max_packet_size + window_size = self._sanitize_window_size(window_size) + max_packet_size = self._sanitize_packet_size(max_packet_size) chanid = self._next_channel() m = Message() m.add_byte(cMSG_CHANNEL_OPEN) @@ -1434,6 +1432,17 @@ class Transport (threading.Thread): finally: self.lock.release() + def _sanitize_window_size(self, window_size): + if window_size is None: + window_size = self.default_window_size + return clamp_value(MIN_PACKET_SIZE, window_size, MAX_WINDOW_SIZE) + + def _sanitize_packet_size(self, max_packet_size): + if max_packet_size is None: + max_packet_size = self.default_max_packet_size + return clamp_value(MIN_PACKET_SIZE, max_packet_size, MAX_WINDOW_SIZE) + + def run(self): # (use the exposed "run" method, because if we specify a thread target # of a private method, threading.Thread will keep a reference to it diff --git a/tests/test_transport.py b/tests/test_transport.py index d84747b6..f5795d0e 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -31,7 +31,9 @@ from paramiko import Transport, SecurityOptions, ServerInterface, RSAKey, DSSKey SSHException, ChannelException from paramiko import AUTH_FAILED, AUTH_SUCCESSFUL from paramiko import OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED -from paramiko.common import MSG_KEXINIT, cMSG_CHANNEL_WINDOW_ADJUST +from paramiko.common import MSG_KEXINIT, cMSG_CHANNEL_WINDOW_ADJUST, \ + MIN_PACKET_SIZE, MAX_WINDOW_SIZE, \ + DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE from paramiko.py3compat import bytes from paramiko.message import Message from tests.loop import LoopSocket @@ -55,7 +57,7 @@ class NullServer (ServerInterface): paranoid_did_password = False paranoid_did_public_key = False paranoid_key = DSSKey.from_private_key_file(test_path('test_dss.key')) - + def get_allowed_auths(self, username): if username == 'slowdive': return 'publickey,password' @@ -78,24 +80,24 @@ class NullServer (ServerInterface): def check_channel_shell_request(self, channel): return True - + def check_global_request(self, kind, msg): self._global_request = kind return False - + def check_channel_x11_request(self, channel, single_connection, auth_protocol, auth_cookie, screen_number): self._x11_single_connection = single_connection self._x11_auth_protocol = auth_protocol self._x11_auth_cookie = auth_cookie self._x11_screen_number = screen_number return True - + def check_port_forward_request(self, addr, port): self._listen = socket.socket() self._listen.bind(('127.0.0.1', 0)) self._listen.listen(1) return self._listen.getsockname()[1] - + def cancel_port_forward_request(self, addr, port): self._listen.close() self._listen = None @@ -123,12 +125,12 @@ class TransportTest(ParamikoTest): 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) - + if client_options is not None: client_options(self.tc.get_security_options()) if server_options is not None: server_options(self.ts.get_security_options()) - + event = threading.Event() self.server = NullServer() self.assertTrue(not event.isSet()) @@ -155,7 +157,7 @@ class TransportTest(ParamikoTest): self.assertTrue(False) except TypeError: pass - + def test_2_compute_key(self): 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' @@ -208,7 +210,7 @@ class TransportTest(ParamikoTest): event.wait(1.0) self.assertTrue(event.isSet()) self.assertTrue(self.ts.is_active()) - + def test_4_special(self): """ verify that the client can demand odd handshake settings, and can @@ -222,7 +224,7 @@ class TransportTest(ParamikoTest): self.assertEqual('aes256-cbc', self.tc.remote_cipher) self.assertEqual(12, self.tc.packetizer.get_mac_size_out()) self.assertEqual(12, self.tc.packetizer.get_mac_size_in()) - + self.tc.send_ignore(1024) self.tc.renegotiate_keys() self.ts.send_ignore(1024) @@ -236,7 +238,7 @@ class TransportTest(ParamikoTest): self.tc.set_keepalive(1) time.sleep(2) self.assertEqual('keepalive@lag.net', self.server._global_request) - + def test_6_exec_command(self): """ verify that exec_command() does something reasonable. @@ -250,7 +252,7 @@ class TransportTest(ParamikoTest): self.assertTrue(False) except SSHException: pass - + chan = self.tc.open_session() chan.exec_command('yes') schan = self.ts.accept(1.0) @@ -264,7 +266,7 @@ class TransportTest(ParamikoTest): f = chan.makefile_stderr() self.assertEqual('This is on stderr.\n', f.readline()) self.assertEqual('', f.readline()) - + # now try it with combined stdout/stderr chan = self.tc.open_session() chan.exec_command('yes') @@ -273,7 +275,7 @@ class TransportTest(ParamikoTest): schan.send_stderr('This is on stderr.\n') schan.close() - chan.set_combine_stderr(True) + chan.set_combine_stderr(True) f = chan.makefile() self.assertEqual('Hello there.\n', f.readline()) self.assertEqual('This is on stderr.\n', f.readline()) @@ -320,7 +322,7 @@ class TransportTest(ParamikoTest): schan.shutdown_write() schan.send_exit_status(23) schan.close() - + f = chan.makefile() self.assertEqual('Hello there.\n', f.readline()) self.assertEqual('', f.readline()) @@ -342,14 +344,14 @@ class TransportTest(ParamikoTest): chan.invoke_shell() schan = self.ts.accept(1.0) - # nothing should be ready + # nothing should be ready r, w, e = select.select([chan], [], [], 0.1) self.assertEqual([], r) self.assertEqual([], w) self.assertEqual([], e) - + schan.send('hello\n') - + # something should be ready now (give it 1 second to appear) for i in range(10): r, w, e = select.select([chan], [], [], 0.1) @@ -361,7 +363,7 @@ class TransportTest(ParamikoTest): self.assertEqual([], e) self.assertEqual(b'hello\n', chan.recv(6)) - + # and, should be dead again now r, w, e = select.select([chan], [], [], 0.1) self.assertEqual([], r) @@ -369,7 +371,7 @@ class TransportTest(ParamikoTest): self.assertEqual([], e) schan.close() - + # detect eof? for i in range(10): r, w, e = select.select([chan], [], [], 0.1) @@ -380,14 +382,14 @@ class TransportTest(ParamikoTest): self.assertEqual([], w) self.assertEqual([], e) self.assertEqual(bytes(), chan.recv(16)) - + # make sure the pipe is still open for now... p = chan._pipe self.assertEqual(False, p._closed) chan.close() # ...and now is closed. self.assertEqual(True, p._closed) - + def test_B_renegotiate(self): """ verify that a transport can correctly renegotiate mid-stream. @@ -402,7 +404,7 @@ class TransportTest(ParamikoTest): for i in range(20): chan.send('x' * 1024) chan.close() - + # allow a few seconds for the rekeying to complete for i in range(50): if self.tc.H != self.tc.session_id: @@ -441,28 +443,28 @@ class TransportTest(ParamikoTest): chan = self.tc.open_session() chan.exec_command('yes') schan = self.ts.accept(1.0) - + requested = [] def handler(c, addr_port): addr, port = addr_port requested.append((addr, port)) self.tc._queue_incoming_channel(c) - + self.assertEqual(None, getattr(self.server, '_x11_screen_number', None)) cookie = chan.request_x11(0, single_connection=True, handler=handler) self.assertEqual(0, self.server._x11_screen_number) self.assertEqual('MIT-MAGIC-COOKIE-1', self.server._x11_auth_protocol) self.assertEqual(cookie, self.server._x11_auth_cookie) self.assertEqual(True, self.server._x11_single_connection) - + x11_server = self.ts.open_x11_channel(('localhost', 6093)) x11_client = self.tc.accept() self.assertEqual('localhost', requested[0][0]) self.assertEqual(6093, requested[0][1]) - + x11_server.send('hello') self.assertEqual(b'hello', x11_client.recv(5)) - + x11_server.close() x11_client.close() chan.close() @@ -477,13 +479,13 @@ class TransportTest(ParamikoTest): chan = self.tc.open_session() chan.exec_command('yes') schan = self.ts.accept(1.0) - + requested = [] def handler(c, origin_addr_port, server_addr_port): requested.append(origin_addr_port) requested.append(server_addr_port) self.tc._queue_incoming_channel(c) - + port = self.tc.request_port_forward('127.0.0.1', 0, handler) self.assertEqual(port, self.server._listen.getsockname()[1]) @@ -492,14 +494,14 @@ class TransportTest(ParamikoTest): ss, _ = self.server._listen.accept() sch = self.ts.open_forwarded_tcpip_channel(ss.getsockname(), ss.getpeername()) cch = self.tc.accept() - + sch.send('hello') self.assertEqual(b'hello', cch.recv(5)) sch.close() cch.close() ss.close() cs.close() - + # now cancel it. self.tc.cancel_port_forward('127.0.0.1', port) self.assertTrue(self.server._listen is None) @@ -513,7 +515,7 @@ class TransportTest(ParamikoTest): chan = self.tc.open_session() chan.exec_command('yes') schan = self.ts.accept(1.0) - + # open a port on the "server" that the client will ask to forward to. greeting_server = socket.socket() greeting_server.bind(('127.0.0.1', 0)) @@ -524,13 +526,13 @@ class TransportTest(ParamikoTest): sch = self.ts.accept(1.0) cch = socket.socket() cch.connect(self.server._tcpip_dest) - + ss, _ = greeting_server.accept() ss.send(b'Hello!\n') ss.close() sch.send(cch.recv(8192)) sch.close() - + self.assertEqual(b'Hello!\n', cs.recv(7)) cs.close() @@ -544,14 +546,14 @@ class TransportTest(ParamikoTest): chan.invoke_shell() schan = self.ts.accept(1.0) - # nothing should be ready + # nothing should be ready r, w, e = select.select([chan], [], [], 0.1) self.assertEqual([], r) self.assertEqual([], w) self.assertEqual([], e) - + schan.send_stderr('hello\n') - + # something should be ready now (give it 1 second to appear) for i in range(10): r, w, e = select.select([chan], [], [], 0.1) @@ -563,7 +565,7 @@ class TransportTest(ParamikoTest): self.assertEqual([], e) self.assertEqual(b'hello\n', chan.recv_stderr(6)) - + # and, should be dead again now r, w, e = select.select([chan], [], [], 0.1) self.assertEqual([], r) @@ -600,10 +602,10 @@ class TransportTest(ParamikoTest): def test_I_rekey_deadlock(self): """ Regression test for deadlock when in-transit messages are received after MSG_KEXINIT is sent - + Note: When this test fails, it may leak threads. """ - + # Test for an obscure deadlocking bug that can occur if we receive # certain messages while initiating a key exchange. # @@ -620,7 +622,7 @@ class TransportTest(ParamikoTest): # NeedRekeyException. # 4. In response to NeedRekeyException, the transport thread sends # MSG_KEXINIT to the remote host. - # + # # On the remote host (using any SSH implementation): # 5. The MSG_CHANNEL_DATA is received, and MSG_CHANNEL_WINDOW_ADJUST is sent. # 6. The MSG_KEXINIT is received, and a corresponding MSG_KEXINIT is sent. @@ -655,7 +657,7 @@ class TransportTest(ParamikoTest): self.done_event = done_event self.watchdog_event = threading.Event() self.last = None - + def run(self): try: for i in range(1, 1+self.iterations): @@ -667,7 +669,7 @@ class TransportTest(ParamikoTest): finally: self.done_event.set() self.watchdog_event.set() - + class ReceiveThread(threading.Thread): def __init__(self, chan, done_event): threading.Thread.__init__(self, None, None, self.__class__.__name__) @@ -675,7 +677,7 @@ class TransportTest(ParamikoTest): self.chan = chan self.done_event = done_event self.watchdog_event = threading.Event() - + def run(self): try: while not self.done_event.isSet(): @@ -688,10 +690,10 @@ class TransportTest(ParamikoTest): finally: self.done_event.set() self.watchdog_event.set() - + self.setup_test_server() self.ts.packetizer.REKEY_BYTES = 2048 - + chan = self.tc.open_session() chan.exec_command('yes') schan = self.ts.accept(1.0) @@ -713,7 +715,7 @@ class TransportTest(ParamikoTest): self._send_message(m2) return _negotiate_keys(self, m) self.tc._handler_table[MSG_KEXINIT] = _negotiate_keys_wrapper - + # Parameters for the test iterations = 500 # The deadlock does not happen every time, but it # should after many iterations. @@ -725,12 +727,12 @@ class TransportTest(ParamikoTest): # Start the sending thread st = SendThread(schan, iterations, done_event) st.start() - + # Start the receiving thread rt = ReceiveThread(chan, done_event) rt.start() - # Act as a watchdog timer, checking + # Act as a watchdog timer, checking deadlocked = False while not deadlocked and not done_event.isSet(): for event in (st.watchdog_event, rt.watchdog_event): @@ -741,7 +743,7 @@ class TransportTest(ParamikoTest): deadlocked = True break event.clear() - + # Tell the threads to stop (if they haven't already stopped). Note # that if one or more threads are deadlocked, they might hang around # forever (until the process exits). @@ -753,3 +755,21 @@ class TransportTest(ParamikoTest): # Close the channels schan.close() chan.close() + + def test_J_sanitze_packet_size(self): + """ + verify that we conform to the rfc of packet and window sizes. + """ + for val, correct in [(32767, MIN_PACKET_SIZE), + (None, DEFAULT_MAX_PACKET_SIZE), + (2**32, MAX_WINDOW_SIZE)]: + self.assertEqual(self.tc._sanitize_packet_size(val), correct) + + def test_K_sanitze_window_size(self): + """ + verify that we conform to the rfc of packet and window sizes. + """ + for val, correct in [(32767, MIN_PACKET_SIZE), + (None, DEFAULT_WINDOW_SIZE), + (2**32, MAX_WINDOW_SIZE)]: + self.assertEqual(self.tc._sanitize_window_size(val), correct) -- cgit v1.2.3 From f88189835eebab72a3792f67de4aacc7fca68df6 Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Fri, 15 Aug 2014 12:21:44 +0200 Subject: Document what is breaking in the client tests. --- tests/test_client.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'tests') diff --git a/tests/test_client.py b/tests/test_client.py index 7e5c80b4..93574fc8 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -221,6 +221,8 @@ class SSHClientTest (unittest.TestCase): """ # Unclear why this is borked on Py3, but it is, and does not seem worth # pursuing at the moment. + # XXX: It's the release of the references to e.g packetizer that fails + # in py3... if not PY2: return threading.Thread(target=self._run).start() -- cgit v1.2.3 From d23be11a7a8403cc18f89e4396bb8e4d2f8ae72e Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Fri, 15 Aug 2014 15:30:37 +0200 Subject: Strip whitespace. --- tests/test_buffered_pipe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/test_buffered_pipe.py b/tests/test_buffered_pipe.py index a53081a9..1045a925 100644 --- a/tests/test_buffered_pipe.py +++ b/tests/test_buffered_pipe.py @@ -48,12 +48,12 @@ class BufferedPipeTest(ParamikoTest): self.assertTrue(p.read_ready()) data = p.read(6) self.assertEqual(b'hello.', data) - + p.feed('plus/minus') self.assertEqual(b'plu', p.read(3)) self.assertEqual(b's/m', p.read(3)) self.assertEqual(b'inus', p.read(4)) - + p.close() self.assertTrue(not p.read_ready()) self.assertEqual(b'', p.read(1)) -- cgit v1.2.3 From 888c17a9c4e8d8b1460aa42197803501a04091b4 Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Fri, 15 Aug 2014 15:32:35 +0200 Subject: Strip whitespace. --- tests/test_transport.py | 102 ++++++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 51 deletions(-) (limited to 'tests') diff --git a/tests/test_transport.py b/tests/test_transport.py index 485a18e8..ac744b26 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -55,7 +55,7 @@ class NullServer (ServerInterface): paranoid_did_password = False paranoid_did_public_key = False paranoid_key = DSSKey.from_private_key_file(test_path('test_dss.key')) - + def get_allowed_auths(self, username): if username == 'slowdive': return 'publickey,password' @@ -78,24 +78,24 @@ class NullServer (ServerInterface): def check_channel_shell_request(self, channel): return True - + def check_global_request(self, kind, msg): self._global_request = kind return False - + def check_channel_x11_request(self, channel, single_connection, auth_protocol, auth_cookie, screen_number): self._x11_single_connection = single_connection self._x11_auth_protocol = auth_protocol self._x11_auth_cookie = auth_cookie self._x11_screen_number = screen_number return True - + def check_port_forward_request(self, addr, port): self._listen = socket.socket() self._listen.bind(('127.0.0.1', 0)) self._listen.listen(1) return self._listen.getsockname()[1] - + def cancel_port_forward_request(self, addr, port): self._listen.close() self._listen = None @@ -123,12 +123,12 @@ class TransportTest(ParamikoTest): 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) - + if client_options is not None: client_options(self.tc.get_security_options()) if server_options is not None: server_options(self.ts.get_security_options()) - + event = threading.Event() self.server = NullServer() self.assertTrue(not event.isSet()) @@ -155,7 +155,7 @@ class TransportTest(ParamikoTest): self.assertTrue(False) except TypeError: pass - + def test_2_compute_key(self): 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' @@ -208,7 +208,7 @@ class TransportTest(ParamikoTest): event.wait(1.0) self.assertTrue(event.isSet()) self.assertTrue(self.ts.is_active()) - + def test_4_special(self): """ verify that the client can demand odd handshake settings, and can @@ -222,7 +222,7 @@ class TransportTest(ParamikoTest): self.assertEqual('aes256-cbc', self.tc.remote_cipher) self.assertEqual(12, self.tc.packetizer.get_mac_size_out()) self.assertEqual(12, self.tc.packetizer.get_mac_size_in()) - + self.tc.send_ignore(1024) self.tc.renegotiate_keys() self.ts.send_ignore(1024) @@ -236,7 +236,7 @@ class TransportTest(ParamikoTest): self.tc.set_keepalive(1) time.sleep(2) self.assertEqual('keepalive@lag.net', self.server._global_request) - + def test_6_exec_command(self): """ verify that exec_command() does something reasonable. @@ -250,7 +250,7 @@ class TransportTest(ParamikoTest): self.assertTrue(False) except SSHException: pass - + chan = self.tc.open_session() chan.exec_command('yes') schan = self.ts.accept(1.0) @@ -264,7 +264,7 @@ class TransportTest(ParamikoTest): f = chan.makefile_stderr() self.assertEqual('This is on stderr.\n', f.readline()) self.assertEqual('', f.readline()) - + # now try it with combined stdout/stderr chan = self.tc.open_session() chan.exec_command('yes') @@ -273,7 +273,7 @@ class TransportTest(ParamikoTest): schan.send_stderr('This is on stderr.\n') schan.close() - chan.set_combine_stderr(True) + chan.set_combine_stderr(True) f = chan.makefile() self.assertEqual('Hello there.\n', f.readline()) self.assertEqual('This is on stderr.\n', f.readline()) @@ -320,7 +320,7 @@ class TransportTest(ParamikoTest): schan.shutdown_write() schan.send_exit_status(23) schan.close() - + f = chan.makefile() self.assertEqual('Hello there.\n', f.readline()) self.assertEqual('', f.readline()) @@ -342,14 +342,14 @@ class TransportTest(ParamikoTest): chan.invoke_shell() schan = self.ts.accept(1.0) - # nothing should be ready + # nothing should be ready r, w, e = select.select([chan], [], [], 0.1) self.assertEqual([], r) self.assertEqual([], w) self.assertEqual([], e) - + schan.send('hello\n') - + # something should be ready now (give it 1 second to appear) for i in range(10): r, w, e = select.select([chan], [], [], 0.1) @@ -361,7 +361,7 @@ class TransportTest(ParamikoTest): self.assertEqual([], e) self.assertEqual(b'hello\n', chan.recv(6)) - + # and, should be dead again now r, w, e = select.select([chan], [], [], 0.1) self.assertEqual([], r) @@ -369,7 +369,7 @@ class TransportTest(ParamikoTest): self.assertEqual([], e) schan.close() - + # detect eof? for i in range(10): r, w, e = select.select([chan], [], [], 0.1) @@ -380,14 +380,14 @@ class TransportTest(ParamikoTest): self.assertEqual([], w) self.assertEqual([], e) self.assertEqual(bytes(), chan.recv(16)) - + # make sure the pipe is still open for now... p = chan._pipe self.assertEqual(False, p._closed) chan.close() # ...and now is closed. self.assertEqual(True, p._closed) - + def test_B_renegotiate(self): """ verify that a transport can correctly renegotiate mid-stream. @@ -402,7 +402,7 @@ class TransportTest(ParamikoTest): for i in range(20): chan.send('x' * 1024) chan.close() - + # allow a few seconds for the rekeying to complete for i in range(50): if self.tc.H != self.tc.session_id: @@ -441,28 +441,28 @@ class TransportTest(ParamikoTest): chan = self.tc.open_session() chan.exec_command('yes') schan = self.ts.accept(1.0) - + requested = [] def handler(c, addr_port): addr, port = addr_port requested.append((addr, port)) self.tc._queue_incoming_channel(c) - + self.assertEqual(None, getattr(self.server, '_x11_screen_number', None)) cookie = chan.request_x11(0, single_connection=True, handler=handler) self.assertEqual(0, self.server._x11_screen_number) self.assertEqual('MIT-MAGIC-COOKIE-1', self.server._x11_auth_protocol) self.assertEqual(cookie, self.server._x11_auth_cookie) self.assertEqual(True, self.server._x11_single_connection) - + x11_server = self.ts.open_x11_channel(('localhost', 6093)) x11_client = self.tc.accept() self.assertEqual('localhost', requested[0][0]) self.assertEqual(6093, requested[0][1]) - + x11_server.send('hello') self.assertEqual(b'hello', x11_client.recv(5)) - + x11_server.close() x11_client.close() chan.close() @@ -477,13 +477,13 @@ class TransportTest(ParamikoTest): chan = self.tc.open_session() chan.exec_command('yes') schan = self.ts.accept(1.0) - + requested = [] def handler(c, origin_addr_port, server_addr_port): requested.append(origin_addr_port) requested.append(server_addr_port) self.tc._queue_incoming_channel(c) - + port = self.tc.request_port_forward('127.0.0.1', 0, handler) self.assertEqual(port, self.server._listen.getsockname()[1]) @@ -492,14 +492,14 @@ class TransportTest(ParamikoTest): ss, _ = self.server._listen.accept() sch = self.ts.open_forwarded_tcpip_channel(ss.getsockname(), ss.getpeername()) cch = self.tc.accept() - + sch.send('hello') self.assertEqual(b'hello', cch.recv(5)) sch.close() cch.close() ss.close() cs.close() - + # now cancel it. self.tc.cancel_port_forward('127.0.0.1', port) self.assertTrue(self.server._listen is None) @@ -513,7 +513,7 @@ class TransportTest(ParamikoTest): chan = self.tc.open_session() chan.exec_command('yes') schan = self.ts.accept(1.0) - + # open a port on the "server" that the client will ask to forward to. greeting_server = socket.socket() greeting_server.bind(('127.0.0.1', 0)) @@ -524,13 +524,13 @@ class TransportTest(ParamikoTest): sch = self.ts.accept(1.0) cch = socket.socket() cch.connect(self.server._tcpip_dest) - + ss, _ = greeting_server.accept() ss.send(b'Hello!\n') ss.close() sch.send(cch.recv(8192)) sch.close() - + self.assertEqual(b'Hello!\n', cs.recv(7)) cs.close() @@ -544,14 +544,14 @@ class TransportTest(ParamikoTest): chan.invoke_shell() schan = self.ts.accept(1.0) - # nothing should be ready + # nothing should be ready r, w, e = select.select([chan], [], [], 0.1) self.assertEqual([], r) self.assertEqual([], w) self.assertEqual([], e) - + schan.send_stderr('hello\n') - + # something should be ready now (give it 1 second to appear) for i in range(10): r, w, e = select.select([chan], [], [], 0.1) @@ -563,7 +563,7 @@ class TransportTest(ParamikoTest): self.assertEqual([], e) self.assertEqual(b'hello\n', chan.recv_stderr(6)) - + # and, should be dead again now r, w, e = select.select([chan], [], [], 0.1) self.assertEqual([], r) @@ -599,10 +599,10 @@ class TransportTest(ParamikoTest): def test_I_rekey_deadlock(self): """ Regression test for deadlock when in-transit messages are received after MSG_KEXINIT is sent - + Note: When this test fails, it may leak threads. """ - + # Test for an obscure deadlocking bug that can occur if we receive # certain messages while initiating a key exchange. # @@ -619,7 +619,7 @@ class TransportTest(ParamikoTest): # NeedRekeyException. # 4. In response to NeedRekeyException, the transport thread sends # MSG_KEXINIT to the remote host. - # + # # On the remote host (using any SSH implementation): # 5. The MSG_CHANNEL_DATA is received, and MSG_CHANNEL_WINDOW_ADJUST is sent. # 6. The MSG_KEXINIT is received, and a corresponding MSG_KEXINIT is sent. @@ -654,7 +654,7 @@ class TransportTest(ParamikoTest): self.done_event = done_event self.watchdog_event = threading.Event() self.last = None - + def run(self): try: for i in range(1, 1+self.iterations): @@ -666,7 +666,7 @@ class TransportTest(ParamikoTest): finally: self.done_event.set() self.watchdog_event.set() - + class ReceiveThread(threading.Thread): def __init__(self, chan, done_event): threading.Thread.__init__(self, None, None, self.__class__.__name__) @@ -674,7 +674,7 @@ class TransportTest(ParamikoTest): self.chan = chan self.done_event = done_event self.watchdog_event = threading.Event() - + def run(self): try: while not self.done_event.isSet(): @@ -687,10 +687,10 @@ class TransportTest(ParamikoTest): finally: self.done_event.set() self.watchdog_event.set() - + self.setup_test_server() self.ts.packetizer.REKEY_BYTES = 2048 - + chan = self.tc.open_session() chan.exec_command('yes') schan = self.ts.accept(1.0) @@ -712,7 +712,7 @@ class TransportTest(ParamikoTest): self._send_message(m2) return _negotiate_keys(self, m) self.tc._handler_table[MSG_KEXINIT] = _negotiate_keys_wrapper - + # Parameters for the test iterations = 500 # The deadlock does not happen every time, but it # should after many iterations. @@ -724,12 +724,12 @@ class TransportTest(ParamikoTest): # Start the sending thread st = SendThread(schan, iterations, done_event) st.start() - + # Start the receiving thread rt = ReceiveThread(chan, done_event) rt.start() - # Act as a watchdog timer, checking + # Act as a watchdog timer, checking deadlocked = False while not deadlocked and not done_event.isSet(): for event in (st.watchdog_event, rt.watchdog_event): @@ -740,7 +740,7 @@ class TransportTest(ParamikoTest): deadlocked = True break event.clear() - + # Tell the threads to stop (if they haven't already stopped). Note # that if one or more threads are deadlocked, they might hang around # forever (until the process exits). -- cgit v1.2.3 From 163caabf5e4a2a63216c2192dc476d4184ab81b3 Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Fri, 15 Aug 2014 15:33:25 +0200 Subject: Remove all occurences of ParamikoTest. Sorry paramiko, it's time to put on the big boy pants. You no longer support old as hell versions of python. --- tests/test_buffered_pipe.py | 5 ++--- tests/test_transport.py | 5 +++-- tests/test_util.py | 5 ++--- tests/util.py | 10 ---------- 4 files changed, 7 insertions(+), 18 deletions(-) (limited to 'tests') diff --git a/tests/test_buffered_pipe.py b/tests/test_buffered_pipe.py index 1045a925..3b8f97ad 100644 --- a/tests/test_buffered_pipe.py +++ b/tests/test_buffered_pipe.py @@ -22,11 +22,10 @@ Some unit tests for BufferedPipe. import threading import time +import unittest from paramiko.buffered_pipe import BufferedPipe, PipeTimeout from paramiko import pipe -from tests.util import ParamikoTest - def delay_thread(p): p.feed('a') @@ -40,7 +39,7 @@ def close_thread(p): p.close() -class BufferedPipeTest(ParamikoTest): +class BufferedPipeTest(unittest.TestCase): def test_1_buffered_pipe(self): p = BufferedPipe() self.assertTrue(not p.read_ready()) diff --git a/tests/test_transport.py b/tests/test_transport.py index ac744b26..5c77a321 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -26,6 +26,7 @@ import socket import time import threading import random +import unittest from paramiko import Transport, SecurityOptions, ServerInterface, RSAKey, DSSKey, \ SSHException, ChannelException @@ -35,7 +36,7 @@ from paramiko.common import MSG_KEXINIT, cMSG_CHANNEL_WINDOW_ADJUST from paramiko.py3compat import bytes from paramiko.message import Message from tests.loop import LoopSocket -from tests.util import ParamikoTest, test_path +from tests.util import test_path LONG_BANNER = """\ @@ -105,7 +106,7 @@ class NullServer (ServerInterface): return OPEN_SUCCEEDED -class TransportTest(ParamikoTest): +class TransportTest(unittest.TestCase): def setUp(self): self.socks = LoopSocket() self.sockc = LoopSocket() diff --git a/tests/test_util.py b/tests/test_util.py index 69c75518..643d9171 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -24,13 +24,12 @@ from binascii import hexlify import errno import os from hashlib import sha1 +import unittest import paramiko.util from paramiko.util import lookup_ssh_host_config as host_config from paramiko.py3compat import StringIO, byte_ord -from tests.util import ParamikoTest - test_config_file = """\ Host * User robey @@ -60,7 +59,7 @@ BGQ3GQ/Fc7SX6gkpXkwcZryoi4kNFhHu5LvHcZPdxXV1D+uTMfGS1eyd2Yz/DoNWXNAl8TI0cAsW\ from paramiko import * -class UtilTest(ParamikoTest): +class UtilTest(unittest.TestCase): def test_1_import(self): """ verify that all the classes can be imported from paramiko. diff --git a/tests/util.py b/tests/util.py index 66d2696c..b546a7e1 100644 --- a/tests/util.py +++ b/tests/util.py @@ -1,17 +1,7 @@ import os -import unittest root_path = os.path.dirname(os.path.realpath(__file__)) - -class ParamikoTest(unittest.TestCase): - # for Python 2.3 and below - if not hasattr(unittest.TestCase, 'assertTrue'): - assertTrue = unittest.TestCase.failUnless - if not hasattr(unittest.TestCase, 'assertFalse'): - assertFalse = unittest.TestCase.failIf - - def test_path(filename): return os.path.join(root_path, filename) -- cgit v1.2.3 From d05ef512bab1849e4fecafbc2a0c23465bf6b2c6 Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Fri, 15 Aug 2014 15:41:49 +0200 Subject: Don't end a line with whitespace. This might be stripped by editors at will, which will make some tests brake. --- tests/test_util.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/test_util.py b/tests/test_util.py index 69c75518..28c162e5 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -41,7 +41,12 @@ Host *.example.com \tUser bjork Port=3333 Host * - \t \t Crazy something dumb +""" + +dont_strip_whitespace_please = "\t \t Crazy something dumb " + +test_config_file += dont_strip_whitespace_please +test_config_file += """ Host spoo.example.com Crazy something else """ -- cgit v1.2.3 From 74bd98fcf5cdd1fa63e5caf085e5b8fdafb0cb4e Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Fri, 15 Aug 2014 12:23:02 +0200 Subject: Let packetizer handle 0-length sends from channel. If the channel is closed the send method returs a response length of 0. This is not handled correctly by the packetizer and puts it in an infinite loop. (Fixes #156 for real :-) We make sure we don't do more than 10 iteration on a 0 length respose, but raise an EOFError. --- paramiko/packet.py | 10 ++++++++++ tests/test_packetizer.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) (limited to 'tests') diff --git a/paramiko/packet.py b/paramiko/packet.py index e97d92f0..f516ff9b 100644 --- a/paramiko/packet.py +++ b/paramiko/packet.py @@ -231,6 +231,7 @@ class Packetizer (object): def write_all(self, out): self.__keepalive_last = time.time() + iteration_with_zero_as_return_value = 0 while len(out) > 0: retry_write = False try: @@ -254,6 +255,15 @@ class Packetizer (object): n = 0 if self.__closed: n = -1 + else: + if n == 0 and iteration_with_zero_as_return_value > 10: + # We shouldn't retry the write, but we didn't + # manage to send anything over the socket. This might be an + # indication that we have lost contact with the remote side, + # but are yet to receive an EOFError or other socket errors. + # Let's give it some iteration to try and catch up. + n = -1 + iteration_with_zero_as_return_value += 1 if n < 0: raise EOFError() if n == len(out): diff --git a/tests/test_packetizer.py b/tests/test_packetizer.py index a8c0f973..8faec03c 100644 --- a/tests/test_packetizer.py +++ b/tests/test_packetizer.py @@ -74,3 +74,49 @@ class PacketizerTest (unittest.TestCase): self.assertEqual(100, m.get_int()) self.assertEqual(1, m.get_int()) self.assertEqual(900, m.get_int()) + + def test_3_closed(self): + rsock = LoopSocket() + wsock = LoopSocket() + rsock.link(wsock) + p = Packetizer(wsock) + p.set_log(util.get_logger('paramiko.transport')) + p.set_hexdump(True) + cipher = AES.new(zero_byte * 16, AES.MODE_CBC, x55 * 16) + p.set_outbound_cipher(cipher, 16, sha1, 12, x1f * 20) + + # message has to be at least 16 bytes long, so we'll have at least one + # block of data encrypted that contains zero random padding bytes + m = Message() + m.add_byte(byte_chr(100)) + m.add_int(100) + m.add_int(1) + m.add_int(900) + wsock.send = lambda x: 0 + from functools import wraps + import errno + import os + import signal + + class TimeoutError(Exception): + pass + + def timeout(seconds=1, error_message=os.strerror(errno.ETIME)): + def decorator(func): + def _handle_timeout(signum, frame): + raise TimeoutError(error_message) + + def wrapper(*args, **kwargs): + signal.signal(signal.SIGALRM, _handle_timeout) + signal.alarm(seconds) + try: + result = func(*args, **kwargs) + finally: + signal.alarm(0) + return result + + return wraps(func)(wrapper) + + return decorator + send = timeout()(p.send_message) + self.assertRaises(EOFError, send, m) -- cgit v1.2.3 From c3ba66b90e9da0cce9c88fbc45894fde492edfd0 Mon Sep 17 00:00:00 2001 From: Jelmer Vernooij Date: Sat, 5 Jul 2014 23:51:38 +0200 Subject: Support passing in "buffer" objects again where bytestrings are expected. This fixes bzr's use of paramiko. Fixes issue #343/#285. --- paramiko/py3compat.py | 4 ++++ tests/test_file.py | 10 ++++++++++ 2 files changed, 14 insertions(+) (limited to 'tests') diff --git a/paramiko/py3compat.py b/paramiko/py3compat.py index 8842b988..57c096b2 100644 --- a/paramiko/py3compat.py +++ b/paramiko/py3compat.py @@ -39,6 +39,8 @@ if PY2: return s elif isinstance(s, unicode): return s.encode(encoding) + elif isinstance(s, buffer): + return s else: raise TypeError("Expected unicode or bytes, got %r" % s) @@ -49,6 +51,8 @@ if PY2: return s.decode(encoding) elif isinstance(s, unicode): return s + elif isinstance(s, buffer): + return s.decode(encoding) else: raise TypeError("Expected unicode or bytes, got %r" % s) diff --git a/tests/test_file.py b/tests/test_file.py index c6edd7af..22a34aca 100755 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -23,6 +23,7 @@ Some unit tests for the BufferedFile abstraction. import unittest from paramiko.file import BufferedFile from paramiko.common import linefeed_byte, crlf, cr_byte +import sys class LoopbackFile (BufferedFile): @@ -151,6 +152,15 @@ class BufferedFileTest (unittest.TestCase): b'need to close them again.\n') f.close() + def test_8_buffering(self): + """ + verify that buffered objects can be written + """ + if sys.version_info[0] == 2: + f = LoopbackFile('r+', 16) + f.write(buffer(b'Too small.')) + f.close() + if __name__ == '__main__': from unittest import main main() -- cgit v1.2.3 From 12e752d11aa22ee6ba959115725e386ca779c1cb Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 25 Aug 2014 22:44:54 -0700 Subject: Rework re #239 to work off post-1.13 codebase. Closes #239 --- paramiko/config.py | 2 +- sites/www/changelog.rst | 2 ++ tests/test_util.py | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/paramiko/config.py b/paramiko/config.py index 77fa13d7..f650ee24 100644 --- a/paramiko/config.py +++ b/paramiko/config.py @@ -55,7 +55,7 @@ class SSHConfig (object): """ host = {"host": ['*'], "config": {}} for line in file_obj: - line = line.rstrip('\n').lstrip() + line = line.rstrip('\r\n').lstrip() if (line == '') or (line[0] == '#'): continue if '=' in line: diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index cdd513f4..903e6378 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,8 @@ Changelog ========= +* :bug:`239` Add Windows-style CRLF support to SSH config file parsing. Props + to Christopher Swenson. * :support:`229` Fix a couple of incorrectly-copied docstrings' ``.. versionadded::`` RST directives. Thanks to Aarni Koskela for the catch. * :support:`169 backported` Minor refactor of diff --git a/tests/test_util.py b/tests/test_util.py index 6bde4045..ecf8db72 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -338,3 +338,9 @@ IdentityFile something_%l_using_fqdn """ config = paramiko.util.parse_ssh_config(StringIO(test_config)) assert config.lookup('meh') # will die during lookup() if bug regresses + + def test_13_config_dos_crlf_succeeds(self): + config_file = StringIO("host abcqwerty\r\nHostName 127.0.0.1\r\n") + config = paramiko.SSHConfig() + config.parse(config_file) + self.assertEqual(config.lookup("abcqwerty")["hostname"], "127.0.0.1") -- cgit v1.2.3 From f4de3307b1133c0b207f5af40227c64a8cb5389d Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 5 Sep 2014 11:16:52 -0700 Subject: 80-col tweaks --- paramiko/sftp_client.py | 14 +++++++++----- tests/test_sftp.py | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) (limited to 'tests') diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index 39f08fbc..ee25aa6a 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -222,8 +222,9 @@ class SFTPClient(BaseSFTP): nums = list() while True: try: - # Send out a bunch of readdir requests so that we can read the responses later on - # Section 6.7 of the SSH file transfer RFC explains this + # Send out a bunch of readdir requests so that we can read the + # responses later on Section 6.7 of the SSH file transfer RFC + # explains this # http://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt for i in range(read_ahead_requests): num = self._async_request(type(None), CMD_READDIR, handle) @@ -232,8 +233,10 @@ class SFTPClient(BaseSFTP): # For each of our sent requests # Read and parse the corresponding packets - # If we're at the end of our queued requests, then fire off some more requests - # Exit the loop when we've reached the end of the directory handle + # If we're at the end of our queued requests, then fire off + # some more requests + # Exit the loop when we've reached the end of the directory + # handle for num in nums: t, pkt_data = self._read_packet() msg = Message(pkt_data) @@ -245,7 +248,8 @@ class SFTPClient(BaseSFTP): for i in range(count): filename = msg.get_string() longname = msg.get_string() - attr = SFTPAttributes._from_msg(msg, filename, longname) + attr = SFTPAttributes._from_msg( + msg, filename, longname) if (filename != '.') and (filename != '..'): yield attr diff --git a/tests/test_sftp.py b/tests/test_sftp.py index 2b6aa3b6..26929bdb 100755 --- a/tests/test_sftp.py +++ b/tests/test_sftp.py @@ -279,8 +279,8 @@ class SFTPTest (unittest.TestCase): def test_7_listdir(self): """ - verify that a folder can be created, a bunch of files can be placed in it, - and those files show up in sftp.listdir. + verify that a folder can be created, a bunch of files can be placed in + it, and those files show up in sftp.listdir. """ try: sftp.open(FOLDER + '/duck.txt', 'w').close() -- cgit v1.2.3 From e5fc6a6ecc064f6b2b9862a9405b27f40385f821 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 5 Sep 2014 11:38:27 -0700 Subject: Add quick test re #131 --- tests/test_sftp.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'tests') diff --git a/tests/test_sftp.py b/tests/test_sftp.py index 26929bdb..1ae9781d 100755 --- a/tests/test_sftp.py +++ b/tests/test_sftp.py @@ -298,6 +298,26 @@ class SFTPTest (unittest.TestCase): sftp.remove(FOLDER + '/fish.txt') sftp.remove(FOLDER + '/tertiary.py') + def test_7_5_listdir_iter(self): + """ + listdir_iter version of above test + """ + try: + sftp.open(FOLDER + '/duck.txt', 'w').close() + sftp.open(FOLDER + '/fish.txt', 'w').close() + sftp.open(FOLDER + '/tertiary.py', 'w').close() + + x = [x.filename for x in sftp.listdir_iter(FOLDER)] + self.assertEqual(len(x), 3) + self.assertTrue('duck.txt' in x) + self.assertTrue('fish.txt' in x) + self.assertTrue('tertiary.py' in x) + self.assertTrue('random' not in x) + finally: + sftp.remove(FOLDER + '/duck.txt') + sftp.remove(FOLDER + '/fish.txt') + sftp.remove(FOLDER + '/tertiary.py') + def test_8_setstat(self): """ verify that the setstat functions (chown, chmod, utime, truncate) work. -- cgit v1.2.3 From 76c2073e9e098bec89837ae980167ee9b0a6efcf Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 5 Sep 2014 14:22:11 -0700 Subject: Add a (failing :() test re: ECDSA private keys Re #218 --- tests/test_client.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'tests') diff --git a/tests/test_client.py b/tests/test_client.py index 7e5c80b4..3576ef32 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -148,6 +148,40 @@ class SSHClientTest (unittest.TestCase): stdout.close() stderr.close() + def test_2_5_client_ecdsa(self): + """ + verify that SSHClient works with an ECDSA key. + """ + threading.Thread(target=self._run).start() + host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key')) + public_host_key = paramiko.RSAKey(data=host_key.asbytes()) + + self.tc = paramiko.SSHClient() + self.tc.get_host_keys().add('[%s]:%d' % (self.addr, self.port), 'ssh-rsa', public_host_key) + self.tc.connect(self.addr, self.port, username='slowdive', key_filename=test_path('test_ecdsa.key')) + + self.event.wait(1.0) + self.assertTrue(self.event.isSet()) + self.assertTrue(self.ts.is_active()) + self.assertEqual('slowdive', self.ts.get_username()) + self.assertEqual(True, self.ts.is_authenticated()) + + stdin, stdout, stderr = self.tc.exec_command('yes') + schan = self.ts.accept(1.0) + + schan.send('Hello there.\n') + schan.send_stderr('This is on stderr.\n') + schan.close() + + self.assertEqual('Hello there.\n', stdout.readline()) + self.assertEqual('', stdout.readline()) + self.assertEqual('This is on stderr.\n', stderr.readline()) + self.assertEqual('', stderr.readline()) + + stdin.close() + stdout.close() + stderr.close() + def test_3_multiple_key_files(self): """ verify that SSHClient accepts and tries multiple key files. -- cgit v1.2.3 From 6c8a5eb8fbf023194634f3077479a70ac9f024a2 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 5 Sep 2014 16:05:30 -0700 Subject: Refactor horrible old copypasta --- tests/test_client.py | 95 ++++++++++++---------------------------------------- 1 file changed, 21 insertions(+), 74 deletions(-) (limited to 'tests') diff --git a/tests/test_client.py b/tests/test_client.py index 3576ef32..0697d289 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -80,24 +80,30 @@ class SSHClientTest (unittest.TestCase): server = NullServer() self.ts.start_server(self.event, server) - def test_1_client(self): + def _test_connection(self, **kwargs): """ - verify that the SSHClient stuff works too. + kwargs get passed directly into SSHClient.connect(). """ + # Server setup threading.Thread(target=self._run).start() host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key')) public_host_key = paramiko.RSAKey(data=host_key.asbytes()) + # Client setup self.tc = paramiko.SSHClient() self.tc.get_host_keys().add('[%s]:%d' % (self.addr, self.port), 'ssh-rsa', public_host_key) - self.tc.connect(self.addr, self.port, username='slowdive', password='pygmalion') + # Actual connection + self.tc.connect(self.addr, self.port, username='slowdive', **kwargs) + + # Authentication successful? self.event.wait(1.0) self.assertTrue(self.event.isSet()) self.assertTrue(self.ts.is_active()) self.assertEqual('slowdive', self.ts.get_username()) self.assertEqual(True, self.ts.is_authenticated()) + # Command execution functions? stdin, stdout, stderr = self.tc.exec_command('yes') schan = self.ts.accept(1.0) @@ -110,95 +116,36 @@ class SSHClientTest (unittest.TestCase): self.assertEqual('This is on stderr.\n', stderr.readline()) self.assertEqual('', stderr.readline()) + # Cleanup stdin.close() stdout.close() stderr.close() + def test_1_client(self): + """ + verify that the SSHClient stuff works too. + """ + self._test_connection(password='pygmalion') + def test_2_client_dsa(self): """ verify that SSHClient works with a DSA key. """ - threading.Thread(target=self._run).start() - host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key')) - public_host_key = paramiko.RSAKey(data=host_key.asbytes()) - - self.tc = paramiko.SSHClient() - self.tc.get_host_keys().add('[%s]:%d' % (self.addr, self.port), 'ssh-rsa', public_host_key) - self.tc.connect(self.addr, self.port, username='slowdive', key_filename=test_path('test_dss.key')) - - self.event.wait(1.0) - self.assertTrue(self.event.isSet()) - self.assertTrue(self.ts.is_active()) - self.assertEqual('slowdive', self.ts.get_username()) - self.assertEqual(True, self.ts.is_authenticated()) - - stdin, stdout, stderr = self.tc.exec_command('yes') - schan = self.ts.accept(1.0) - - schan.send('Hello there.\n') - schan.send_stderr('This is on stderr.\n') - schan.close() - - self.assertEqual('Hello there.\n', stdout.readline()) - self.assertEqual('', stdout.readline()) - self.assertEqual('This is on stderr.\n', stderr.readline()) - self.assertEqual('', stderr.readline()) - - stdin.close() - stdout.close() - stderr.close() + self._test_connection(key_filename=test_path('test_dss.key')) def test_2_5_client_ecdsa(self): """ verify that SSHClient works with an ECDSA key. """ - threading.Thread(target=self._run).start() - host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key')) - public_host_key = paramiko.RSAKey(data=host_key.asbytes()) - - self.tc = paramiko.SSHClient() - self.tc.get_host_keys().add('[%s]:%d' % (self.addr, self.port), 'ssh-rsa', public_host_key) - self.tc.connect(self.addr, self.port, username='slowdive', key_filename=test_path('test_ecdsa.key')) - - self.event.wait(1.0) - self.assertTrue(self.event.isSet()) - self.assertTrue(self.ts.is_active()) - self.assertEqual('slowdive', self.ts.get_username()) - self.assertEqual(True, self.ts.is_authenticated()) - - stdin, stdout, stderr = self.tc.exec_command('yes') - schan = self.ts.accept(1.0) - - schan.send('Hello there.\n') - schan.send_stderr('This is on stderr.\n') - schan.close() - - self.assertEqual('Hello there.\n', stdout.readline()) - self.assertEqual('', stdout.readline()) - self.assertEqual('This is on stderr.\n', stderr.readline()) - self.assertEqual('', stderr.readline()) - - stdin.close() - stdout.close() - stderr.close() + self._test_connection(key_filename=test_path('test_ecdsa.key')) def test_3_multiple_key_files(self): """ verify that SSHClient accepts and tries multiple key files. """ - threading.Thread(target=self._run).start() - host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key')) - public_host_key = paramiko.RSAKey(data=host_key.asbytes()) - - self.tc = paramiko.SSHClient() - self.tc.get_host_keys().add('[%s]:%d' % (self.addr, self.port), 'ssh-rsa', public_host_key) - self.tc.connect(self.addr, self.port, username='slowdive', key_filename=[test_path('test_rsa.key'), test_path('test_dss.key')]) - - self.event.wait(1.0) - self.assertTrue(self.event.isSet()) - self.assertTrue(self.ts.is_active()) - self.assertEqual('slowdive', self.ts.get_username()) - self.assertEqual(True, self.ts.is_authenticated()) + self._test_connection(key_filename=[ + test_path('test_rsa.key'), test_path('test_dss.key') + ]) def test_4_auto_add_policy(self): """ -- cgit v1.2.3 From f7f3b4560f8a6a44d3738db798cba977bb5daf4f Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 5 Sep 2014 16:08:05 -0700 Subject: Add an explicit RSA test, which fails (!) --- tests/test_client.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'tests') diff --git a/tests/test_client.py b/tests/test_client.py index 0697d289..1791363a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -133,6 +133,12 @@ class SSHClientTest (unittest.TestCase): """ self._test_connection(key_filename=test_path('test_dss.key')) + def test_client_rsa(self): + """ + verify that SSHClient works with an RSA key. + """ + self._test_connection(key_filename=test_path('test_rsa.key')) + def test_2_5_client_ecdsa(self): """ verify that SSHClient works with an ECDSA key. -- cgit v1.2.3 From a8fd0bd9b2811a989fb646842a2dc17f9d94fd01 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 5 Sep 2014 17:54:33 -0700 Subject: Set things up for cleaner test key tomfoolery --- tests/test_client.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/test_client.py b/tests/test_client.py index 1791363a..7ccf92b0 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -29,10 +29,25 @@ import warnings import os from tests.util import test_path import paramiko -from paramiko.common import PY2 +from paramiko.common import PY2, b + + +def _fingerprint_to_bytes(fingerprint): + """ + Takes ssh-keygen style fingerprint, returns hex-y bytestring. + """ + encoded = b(''.join([r'\x{0}'.format(x) for x in fingerprint.split(':')])) + return encoded.decode('string-escape') class NullServer (paramiko.ServerInterface): + def __init__(self, *args, **kwargs): + # Allow tests to enable/disable specific key types + self.__allowed_keys = kwargs.pop('allowed_keys', []) + self.__fingerprints = { + 'dss': '44:78:f0:b9:a2:3c:c5:18:20:09:ff:75:5b:c1:d2:6c', + } + super(NullServer, self).__init__(*args, **kwargs) def get_allowed_auths(self, username): if username == 'slowdive': @@ -45,7 +60,16 @@ class NullServer (paramiko.ServerInterface): return paramiko.AUTH_FAILED def check_auth_publickey(self, username, key): - if (key.get_name() == 'ssh-dss') and key.get_fingerprint() == b'\x44\x78\xf0\xb9\xa2\x3c\xc5\x18\x20\x09\xff\x75\x5b\xc1\xd2\x6c': + type_ = key.get_name() + fingerprint = key.get_fingerprint() + if not type_.startswith('ssh-'): + return paramiko.AUTH_FAILED + # TODO: honor allowed_keys + try: + expected = self.__fingerprints[type_[4:]] + except KeyError: + return paramiko.AUTH_FAILED + if fingerprint == _fingerprint_to_bytes(expected): return paramiko.AUTH_SUCCESSFUL return paramiko.AUTH_FAILED -- cgit v1.2.3 From 70e44d6386197ec77ca0f4fec8bba47ee22c3198 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 5 Sep 2014 17:55:22 -0700 Subject: Add RSA key fingerprint, now that test passes --- tests/test_client.py | 1 + 1 file changed, 1 insertion(+) (limited to 'tests') diff --git a/tests/test_client.py b/tests/test_client.py index 7ccf92b0..cabc0c6f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -46,6 +46,7 @@ class NullServer (paramiko.ServerInterface): self.__allowed_keys = kwargs.pop('allowed_keys', []) self.__fingerprints = { 'dss': '44:78:f0:b9:a2:3c:c5:18:20:09:ff:75:5b:c1:d2:6c', + 'rsa': '60:73:38:44:cb:51:86:65:7f:de:da:a2:2b:5a:57:d5', } super(NullServer, self).__init__(*args, **kwargs) -- cgit v1.2.3 From 7d72ce55b8b14a36fcd9e46253ed268eab816c5e Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 5 Sep 2014 18:18:36 -0700 Subject: More cleanup to support ECDSA key, and now it works! --- tests/test_client.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'tests') diff --git a/tests/test_client.py b/tests/test_client.py index cabc0c6f..1db2046d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -45,8 +45,9 @@ class NullServer (paramiko.ServerInterface): # Allow tests to enable/disable specific key types self.__allowed_keys = kwargs.pop('allowed_keys', []) self.__fingerprints = { - 'dss': '44:78:f0:b9:a2:3c:c5:18:20:09:ff:75:5b:c1:d2:6c', - 'rsa': '60:73:38:44:cb:51:86:65:7f:de:da:a2:2b:5a:57:d5', + 'ssh-dss': '44:78:f0:b9:a2:3c:c5:18:20:09:ff:75:5b:c1:d2:6c', + 'ssh-rsa': '60:73:38:44:cb:51:86:65:7f:de:da:a2:2b:5a:57:d5', + 'ecdsa-sha2-nistp256': '25:19:eb:55:e6:a1:47:ff:4f:38:d2:75:6f:a5:d5:60', } super(NullServer, self).__init__(*args, **kwargs) @@ -61,16 +62,12 @@ class NullServer (paramiko.ServerInterface): return paramiko.AUTH_FAILED def check_auth_publickey(self, username, key): - type_ = key.get_name() - fingerprint = key.get_fingerprint() - if not type_.startswith('ssh-'): - return paramiko.AUTH_FAILED # TODO: honor allowed_keys try: - expected = self.__fingerprints[type_[4:]] + expected = self.__fingerprints[key.get_name()] except KeyError: return paramiko.AUTH_FAILED - if fingerprint == _fingerprint_to_bytes(expected): + if key.get_fingerprint() == _fingerprint_to_bytes(expected): return paramiko.AUTH_SUCCESSFUL return paramiko.AUTH_FAILED -- cgit v1.2.3 From d8047a2e6d1aa06e113e0a9dc9e8f3e89ce2f70e Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 5 Sep 2014 19:23:15 -0700 Subject: Factor fingerprint data out of class --- tests/test_client.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'tests') diff --git a/tests/test_client.py b/tests/test_client.py index 1db2046d..0293ec75 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -32,6 +32,12 @@ import paramiko from paramiko.common import PY2, b +FINGERPRINTS = { + 'ssh-dss': '44:78:f0:b9:a2:3c:c5:18:20:09:ff:75:5b:c1:d2:6c', + 'ssh-rsa': '60:73:38:44:cb:51:86:65:7f:de:da:a2:2b:5a:57:d5', + 'ecdsa-sha2-nistp256': '25:19:eb:55:e6:a1:47:ff:4f:38:d2:75:6f:a5:d5:60', +} + def _fingerprint_to_bytes(fingerprint): """ Takes ssh-keygen style fingerprint, returns hex-y bytestring. @@ -44,11 +50,6 @@ class NullServer (paramiko.ServerInterface): def __init__(self, *args, **kwargs): # Allow tests to enable/disable specific key types self.__allowed_keys = kwargs.pop('allowed_keys', []) - self.__fingerprints = { - 'ssh-dss': '44:78:f0:b9:a2:3c:c5:18:20:09:ff:75:5b:c1:d2:6c', - 'ssh-rsa': '60:73:38:44:cb:51:86:65:7f:de:da:a2:2b:5a:57:d5', - 'ecdsa-sha2-nistp256': '25:19:eb:55:e6:a1:47:ff:4f:38:d2:75:6f:a5:d5:60', - } super(NullServer, self).__init__(*args, **kwargs) def get_allowed_auths(self, username): @@ -64,7 +65,7 @@ class NullServer (paramiko.ServerInterface): def check_auth_publickey(self, username, key): # TODO: honor allowed_keys try: - expected = self.__fingerprints[key.get_name()] + expected = FINGERPRINTS[key.get_name()] except KeyError: return paramiko.AUTH_FAILED if key.get_fingerprint() == _fingerprint_to_bytes(expected): -- cgit v1.2.3 From dd0c618ea02024e06806ab99c79fcff8affeba03 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 5 Sep 2014 19:32:50 -0700 Subject: Overhaul multi-key test to set up multiple scenarios --- tests/test_client.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) (limited to 'tests') diff --git a/tests/test_client.py b/tests/test_client.py index 0293ec75..ae10b814 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -95,20 +95,26 @@ class SSHClientTest (unittest.TestCase): if hasattr(self, attr): getattr(self, attr).close() - def _run(self): + def _run(self, allowed_keys=None): + if allowed_keys is None: + allowed_keys = FINGERPRINTS.keys() self.socks, addr = self.sockl.accept() self.ts = paramiko.Transport(self.socks) host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key')) self.ts.add_server_key(host_key) - server = NullServer() + server = NullServer(allowed_keys=allowed_keys) self.ts.start_server(self.event, server) def _test_connection(self, **kwargs): """ - kwargs get passed directly into SSHClient.connect(). + (Most) kwargs get passed directly into SSHClient.connect(). + + The exception is ``allowed_keys`` which is stripped and handed to the + ``NullServer`` used for testing. """ + run_kwargs = {'allowed_keys': kwargs.pop('allowed_keys', None)} # Server setup - threading.Thread(target=self._run).start() + threading.Thread(target=self._run, kwargs=run_kwargs).start() host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key')) public_host_key = paramiko.RSAKey(data=host_key.asbytes()) @@ -172,9 +178,23 @@ class SSHClientTest (unittest.TestCase): """ verify that SSHClient accepts and tries multiple key files. """ - self._test_connection(key_filename=[ - test_path('test_rsa.key'), test_path('test_dss.key') - ]) + # This is dumb :( + types_ = { + 'rsa': 'ssh-rsa', + 'dss': 'ssh-dss', + 'ecdsa': 'ecdsa-sha2-nistp256', + } + # Various combos of attempted & valid keys + for attempt, accept in ( + (['rsa', 'dss'], ['dss']), # Original test #3 + (['dss', 'rsa'], ['dss']), # Ordering matters sometimes, sadly + ): + self._test_connection( + key_filename=[ + test_path('test_{0}.key'.format(x)) for x in attempt + ], + allowed_keys=[types_[x] for x in accept], + ) def test_4_auto_add_policy(self): """ -- cgit v1.2.3 From 689ac4d9c6a30eccfba21d068239dc5b06c16bc4 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 5 Sep 2014 19:39:02 -0700 Subject: Add a couple more permutations --- tests/test_client.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'tests') diff --git a/tests/test_client.py b/tests/test_client.py index ae10b814..a1ef490c 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -185,9 +185,12 @@ class SSHClientTest (unittest.TestCase): 'ecdsa': 'ecdsa-sha2-nistp256', } # Various combos of attempted & valid keys + # TODO: try every possible combo using itertools functions for attempt, accept in ( (['rsa', 'dss'], ['dss']), # Original test #3 (['dss', 'rsa'], ['dss']), # Ordering matters sometimes, sadly + (['dss', 'rsa', 'ecdsa'], ['dss']), # Try ECDSA but fail + (['rsa', 'ecdsa'], ['ecdsa']), # ECDSA success ): self._test_connection( key_filename=[ -- cgit v1.2.3 From 5c81c60090b991f7fc4c87c850460a4adc48986e Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 5 Sep 2014 20:35:29 -0700 Subject: Forgot to actually implement allowed-keys/reverse testing --- tests/test_client.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/test_client.py b/tests/test_client.py index a1ef490c..4ba66828 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -30,6 +30,7 @@ import os from tests.util import test_path import paramiko from paramiko.common import PY2, b +from paramiko.ssh_exception import PasswordRequiredException FINGERPRINTS = { @@ -63,12 +64,14 @@ class NullServer (paramiko.ServerInterface): return paramiko.AUTH_FAILED def check_auth_publickey(self, username, key): - # TODO: honor allowed_keys try: expected = FINGERPRINTS[key.get_name()] except KeyError: return paramiko.AUTH_FAILED - if key.get_fingerprint() == _fingerprint_to_bytes(expected): + if ( + key.get_name() in self.__allowed_keys and + key.get_fingerprint() == _fingerprint_to_bytes(expected) + ): return paramiko.AUTH_SUCCESSFUL return paramiko.AUTH_FAILED @@ -199,6 +202,16 @@ class SSHClientTest (unittest.TestCase): allowed_keys=[types_[x] for x in accept], ) + def test_multiple_key_files_failure(self): + """ + Expect failure when multiple keys in play and none are accepted + """ + self.assertRaises(PasswordRequiredException, + self._test_connection, + key_filename=[test_path('test_rsa.key')], + allowed_keys=['ecdsa-sha2-nistp256'], + ) + def test_4_auto_add_policy(self): """ verify that SSHClient's AutoAddPolicy works. -- cgit v1.2.3 From ff419627b3676a598d10cd581282d21ec33136bb Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 5 Sep 2014 20:45:09 -0700 Subject: Fix a Python 3 encoding dealie --- tests/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/test_client.py b/tests/test_client.py index 4ba66828..b0ea0dc7 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -44,7 +44,7 @@ def _fingerprint_to_bytes(fingerprint): Takes ssh-keygen style fingerprint, returns hex-y bytestring. """ encoded = b(''.join([r'\x{0}'.format(x) for x in fingerprint.split(':')])) - return encoded.decode('string-escape') + return encoded.decode('string-escape' if PY2 else 'unicode_escape') class NullServer (paramiko.ServerInterface): -- cgit v1.2.3 From 774bc5e3fff41a53b986d1663e6e0d35bf695195 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 5 Sep 2014 20:54:22 -0700 Subject: Yup, that was indeed too fucking clever. Bad bitprophet! --- tests/test_client.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) (limited to 'tests') diff --git a/tests/test_client.py b/tests/test_client.py index b0ea0dc7..76fda68d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -34,18 +34,11 @@ from paramiko.ssh_exception import PasswordRequiredException FINGERPRINTS = { - 'ssh-dss': '44:78:f0:b9:a2:3c:c5:18:20:09:ff:75:5b:c1:d2:6c', - 'ssh-rsa': '60:73:38:44:cb:51:86:65:7f:de:da:a2:2b:5a:57:d5', - 'ecdsa-sha2-nistp256': '25:19:eb:55:e6:a1:47:ff:4f:38:d2:75:6f:a5:d5:60', + 'ssh-dss': b'\x44\x78\xf0\xb9\xa2\x3c\xc5\x18\x20\x09\xff\x75\x5b\xc1\xd2\x6c', + 'ssh-rsa': b'\x60\x73\x38\x44\xcb\x51\x86\x65\x7f\xde\xda\xa2\x2b\x5a\x57\xd5', + 'ecdsa-sha2-nistp256': b'\x25\x19\xeb\x55\xe6\xa1\x47\xff\x4f\x38\xd2\x75\x6f\xa5\xd5\x60', } -def _fingerprint_to_bytes(fingerprint): - """ - Takes ssh-keygen style fingerprint, returns hex-y bytestring. - """ - encoded = b(''.join([r'\x{0}'.format(x) for x in fingerprint.split(':')])) - return encoded.decode('string-escape' if PY2 else 'unicode_escape') - class NullServer (paramiko.ServerInterface): def __init__(self, *args, **kwargs): @@ -70,7 +63,7 @@ class NullServer (paramiko.ServerInterface): return paramiko.AUTH_FAILED if ( key.get_name() in self.__allowed_keys and - key.get_fingerprint() == _fingerprint_to_bytes(expected) + key.get_fingerprint() == expected ): return paramiko.AUTH_SUCCESSFUL return paramiko.AUTH_FAILED -- cgit v1.2.3 From 6a6ac4d78421667b810f5e3a017fb669853133f9 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sat, 6 Sep 2014 15:59:49 -0700 Subject: Bah humbug --- tests/test_client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/test_client.py b/tests/test_client.py index 76fda68d..7c094628 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -30,7 +30,7 @@ import os from tests.util import test_path import paramiko from paramiko.common import PY2, b -from paramiko.ssh_exception import PasswordRequiredException +from paramiko.ssh_exception import SSHException FINGERPRINTS = { @@ -199,7 +199,9 @@ class SSHClientTest (unittest.TestCase): """ Expect failure when multiple keys in play and none are accepted """ - self.assertRaises(PasswordRequiredException, + # Until #387 is fixed we have to catch a high-up exception since + # various platforms trigger different errors here >_< + self.assertRaises(SSHException, self._test_connection, key_filename=[test_path('test_rsa.key')], allowed_keys=['ecdsa-sha2-nistp256'], -- cgit v1.2.3