From 8843feb633827cb65066ce01f3e0347038cbb10c Mon Sep 17 00:00:00 2001 From: Robey Pointer Date: Tue, 9 May 2006 09:45:49 -0700 Subject: [PATCH] [project @ robey@lag.net-20060509164549-14e664f234b4b747] new parent exception for all auth failures, and new specific exception for bad host key --- paramiko/__init__.py | 12 ++++++++---- paramiko/auth_handler.py | 8 ++++---- paramiko/client.py | 19 +++++++++--------- paramiko/ssh_exception.py | 41 ++++++++++++++++++++++++++++++++++++--- paramiko/transport.py | 17 +++++++++------- tests/test_transport.py | 2 +- 6 files changed, 70 insertions(+), 29 deletions(-) diff --git a/paramiko/__init__.py b/paramiko/__init__.py index a376985..9cc6be3 100644 --- a/paramiko/__init__.py +++ b/paramiko/__init__.py @@ -60,8 +60,8 @@ if sys.version_info < (2, 2): __author__ = "Robey Pointer " __date__ = "11 Mar 2005" -__version__ = "1.5.4 (tentacool)" -__version_info__ = (1, 5, 4) +__version__ = "1.6 (u?)" +__version_info__ = (1, 6, 0) __license__ = "GNU Lesser General Public License (LGPL)" @@ -69,7 +69,9 @@ from transport import randpool, SecurityOptions, Transport from client import SSHClient, MissingHostKeyPolicy, AutoAddPolicy, RejectPolicy from auth_handler import AuthHandler from channel import Channel, ChannelFile -from ssh_exception import SSHException, PasswordRequiredException, BadAuthenticationType, ChannelException +from ssh_exception import SSHException, PasswordRequiredException, \ + BadAuthenticationType, ChannelException, BadHostKeyException, \ + AuthenticationException from server import ServerInterface, SubsystemHandler, InteractiveQuery from rsakey import RSAKey from dsskey import DSSKey @@ -96,7 +98,7 @@ for x in (Transport, SecurityOptions, Channel, SFTPServer, SSHException, SFTPHandle, SFTPServerInterface, BufferedFile, Agent, AgentKey, PKey, BaseSFTP, SFTPFile, ServerInterface, HostKeys, SSHClient, MissingHostKeyPolicy, AutoAddPolicy, RejectPolicy, ChannelException, - SSHConfig): + SSHConfig, BadHostKeyException, AuthenticationException): x.__module__ = 'paramiko' from common import AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED, \ @@ -119,9 +121,11 @@ __all__ = [ 'Transport', 'Agent', 'Message', 'SSHException', + 'AuthenticationException', 'PasswordRequiredException', 'BadAuthenticationType', 'ChannelException', + 'BadHostKeyException', 'SFTP', 'SFTPFile', 'SFTPHandle', diff --git a/paramiko/auth_handler.py b/paramiko/auth_handler.py index 6b5c952..f471f24 100644 --- a/paramiko/auth_handler.py +++ b/paramiko/auth_handler.py @@ -29,7 +29,8 @@ import encodings.utf_8 from paramiko.common import * from paramiko import util from paramiko.message import Message -from paramiko.ssh_exception import SSHException, BadAuthenticationType, PartialAuthentication +from paramiko.ssh_exception import SSHException, AuthenticationException, \ + BadAuthenticationType, PartialAuthentication from paramiko.server import InteractiveQuery @@ -158,14 +159,14 @@ class AuthHandler (object): if not self.transport.is_active(): e = self.transport.get_exception() if e is None: - e = SSHException('Authentication failed.') + e = AuthenticationException('Authentication failed.') raise e if event.isSet(): break if not self.is_authenticated(): e = self.transport.get_exception() if e is None: - e = SSHException('Authentication failed.') + e = AuthenticationException('Authentication failed.') # this is horrible. python Exception isn't yet descended from # object, so type(e) won't work. :( if issubclass(e.__class__, PartialAuthentication): @@ -410,4 +411,3 @@ class AuthHandler (object): MSG_USERAUTH_INFO_RESPONSE: _parse_userauth_info_response, } - diff --git a/paramiko/client.py b/paramiko/client.py index d3898dc..b7ffbee 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -186,7 +186,7 @@ class SSHClient (object): """ return self._host_keys - def set_log_channel(self, channel): + def set_log_channel(self, name): """ Set the channel for logging. The default is C{"paramiko.transport"} but it can be set to anything you want. @@ -194,7 +194,7 @@ class SSHClient (object): @param name: new channel name for logging @type name: str """ - self._log_channel = channel + self._log_channel = name def set_missing_host_key_policy(self, policy): """ @@ -245,8 +245,11 @@ class SSHClient (object): for authentication @type key_filename: str - @raise SSHException: if there was an error authenticating or verifying - the server's host key + @raise BadHostKeyException: if the server's host key could not be + verified + @raise AuthenticationException: if authentication failed + @raise SSHException: if there was any other error connecting or + establishing an SSH session """ t = self._transport = Transport((hostname, port)) if self._log_channel is not None: @@ -254,7 +257,6 @@ class SSHClient (object): t.start_client() server_key = t.get_remote_server_key() - server_key_hex = hexify(server_key.get_fingerprint()) keytype = server_key.get_name() our_server_key = self._system_host_keys.get(hostname, {}).get(keytype, None) @@ -263,14 +265,11 @@ class SSHClient (object): if our_server_key is None: # will raise exception if the key is rejected; let that fall out self._policy.missing_host_key(self, hostname, server_key) - # if this continues, assume the key is ok + # if the callback returns, assume the key is ok our_server_key = server_key - - our_server_key_hex = hexify(our_server_key.get_fingerprint()) if server_key != our_server_key: - raise SSHException('Host key for server %s does not match! (%s != %s)' % - (hostname, our_server_key_kex, server_key_hex)) + raise BadHostKeyException(hostname, server_key, our_server_key) if username is None: username = getpass.getuser() diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py index 99eaa64..55b0197 100644 --- a/paramiko/ssh_exception.py +++ b/paramiko/ssh_exception.py @@ -28,14 +28,25 @@ class SSHException (Exception): pass -class PasswordRequiredException (SSHException): +class AuthenticationException (SSHException): + """ + Exception raised when authentication failed for some reason. It may be + possible to retry with different credentials. (Other classes specify more + specific reasons.) + + @since: 1.6 + """ + pass + + +class PasswordRequiredException (AuthenticationException): """ Exception raised when a password is needed to unlock a private key file. """ pass -class BadAuthenticationType (SSHException): +class BadAuthenticationType (AuthenticationException): """ Exception raised when an authentication type (like password) is used, but the server isn't allowing that type. (It may only allow public-key, for @@ -58,7 +69,7 @@ class BadAuthenticationType (SSHException): return SSHException.__str__(self) + ' (allowed_types=%r)' % self.allowed_types -class PartialAuthentication (SSHException): +class PartialAuthentication (AuthenticationException): """ An internal exception thrown in the case of partial authentication. """ @@ -73,8 +84,32 @@ class ChannelException (SSHException): """ Exception raised when an attempt to open a new L{Channel} fails. + @ivar code: the error code returned by the server + @type code: int + @since: 1.6 """ def __init__(self, code, text): SSHException.__init__(self, text) self.code = code + + +class BadHostKeyException (SSHException): + """ + The host key given by the SSH server did not match what we were expecting. + + @ivar hostname: the hostname of the SSH server + @type hostname: str + @ivar key: the host key presented by the server + @type key: L{PKey} + @ivar expected_key: the host key expected + @type expected_key: L{PKey} + + @since: 1.6 + """ + def __init__(self, hostname, got_key, expected_key): + SSHException.__init__(self, 'Host key for server %s does not match!' % hostname) + self.hostname = hostname + self.key = got_key + self.expected_key = expected_key + diff --git a/paramiko/transport.py b/paramiko/transport.py index 95bc88d..d61207a 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -992,8 +992,9 @@ class Transport (threading.Thread): @raise BadAuthenticationType: if password authentication isn't allowed by the server for this user (and no event was passed in) - @raise SSHException: if the authentication failed (and no event was - passed in) + @raise AuthenticationException: if the authentication failed (and no + event was passed in) + @raise SSHException: if there was a network error """ if (not self.active) or (not self.initial_kex_done): # we should never try to send the password unless we're on a secure link @@ -1056,13 +1057,14 @@ class Transport (threading.Thread): complete (whether it was successful or not) @type event: threading.Event @return: list of auth types permissible for the next stage of - authentication (normally empty). + authentication (normally empty) @rtype: list @raise BadAuthenticationType: if public-key authentication isn't - allowed by the server for this user (and no event was passed in). - @raise SSHException: if the authentication failed (and no event was - passed in). + allowed by the server for this user (and no event was passed in) + @raise AuthenticationException: if the authentication failed (and no + event was passed in) + @raise SSHException: if there was a network error """ if (not self.active) or (not self.initial_kex_done): # we should never try to authenticate unless we're on a secure link @@ -1119,7 +1121,8 @@ class Transport (threading.Thread): @raise BadAuthenticationType: if public-key authentication isn't allowed by the server for this user - @raise SSHException: if the authentication failed + @raise AuthenticationException: if the authentication failed + @raise SSHException: if there was a network error @since: 1.5 """ diff --git a/tests/test_transport.py b/tests/test_transport.py index f73f0f7..75d3341 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -258,7 +258,7 @@ class TransportTest (unittest.TestCase): self.assert_(False) except: etype, evalue, etb = sys.exc_info() - self.assertEquals(SSHException, etype) + self.assert_(issubclass(etype, SSHException)) self.tc.auth_password(username='slowdive', password='pygmalion') event.wait(1.0) self.assert_(event.isSet())