diff options
author | Jeremy T. Bouse <jbouse@debian.org> | 2014-09-22 09:21:39 -0400 |
---|---|---|
committer | Jeremy T. Bouse <jbouse@debian.org> | 2014-09-22 09:21:39 -0400 |
commit | ccd643cdbd7ba752727d62051058a4454451414d (patch) | |
tree | 9bc89a6629132f474b3297735069d025145a965c /paramiko/client.py | |
parent | 588a4823436454e8968ee36ae95ff92e1cddc3f7 (diff) | |
download | python-paramiko-ccd643cdbd7ba752727d62051058a4454451414d.tar python-paramiko-ccd643cdbd7ba752727d62051058a4454451414d.tar.gz |
Imported Upstream version 1.15.0upstream/1.15.0
Diffstat (limited to 'paramiko/client.py')
-rw-r--r-- | paramiko/client.py | 103 |
1 files changed, 83 insertions, 20 deletions
diff --git a/paramiko/client.py b/paramiko/client.py index c1bf473..05686d9 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -30,16 +30,17 @@ from paramiko.agent import Agent from paramiko.common import DEBUG from paramiko.config import SSH_PORT from paramiko.dsskey import DSSKey +from paramiko.ecdsakey import ECDSAKey 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.transport import Transport -from paramiko.util import retry_on_signal +from paramiko.util import retry_on_signal, ClosingContextManager -class SSHClient (object): +class SSHClient (ClosingContextManager): """ A high-level representation of a session with an SSH server. This class wraps `.Transport`, `.Channel`, and `.SFTPClient` to take care of most @@ -54,6 +55,8 @@ class SSHClient (object): checking. The default mechanism is to try to use local key files or an SSH agent (if one is running). + Instances of this class may be used as context managers. + .. versionadded:: 1.6 """ @@ -171,7 +174,8 @@ class SSHClient (object): 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): + 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`) @@ -184,7 +188,8 @@ class SSHClient (object): - The ``pkey`` or ``key_filename`` passed in (if any) - Any key we can find through an SSH agent - - Any "id_rsa" or "id_dsa" key discoverable in ``~/.ssh/`` + - Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in + ``~/.ssh/`` - Plain username/password auth, if a password was given If a private key requires a password to unlock it, and a password is @@ -210,6 +215,12 @@ class SSHClient (object): :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_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 float banner_timeout: an optional timeout (in seconds) to wait + for the SSH banner to be presented. :raises BadHostKeyException: if the server's host key could not be verified @@ -217,6 +228,10 @@ class SSHClient (object): :raises SSHException: if there was any other error connecting or establishing an SSH session :raises socket.error: if a socket error occurred while connecting + + .. versionchanged:: 1.15 + Added the ``banner_timeout``, ``gss_auth``, ``gss_kex``, + ``gss_deleg_creds`` and ``gss_host`` arguments. """ if not sock: for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM): @@ -235,10 +250,18 @@ class SSHClient (object): pass retry_on_signal(lambda: sock.connect(addr)) - t = self._transport = Transport(sock) + t = self._transport = Transport(sock, gss_kex, gss_deleg_creds) t.use_compression(compress=compress) + if gss_kex and gss_host is None: + t.set_gss_host(hostname) + elif gss_kex and gss_host is not None: + t.set_gss_host(gss_host) + else: + pass if self._log_channel is not None: t.set_log_channel(self._log_channel) + if banner_timeout is not None: + t.banner_timeout = banner_timeout t.start_client() ResourceManager.register(self, t) @@ -249,17 +272,25 @@ class SSHClient (object): server_hostkey_name = hostname else: server_hostkey_name = "[%s]:%d" % (hostname, port) - our_server_key = self._system_host_keys.get(server_hostkey_name, {}).get(keytype, None) - if our_server_key is None: - our_server_key = self._host_keys.get(server_hostkey_name, {}).get(keytype, None) - if our_server_key is None: - # will raise exception if the key is rejected; let that fall out - self._policy.missing_host_key(self, server_hostkey_name, server_key) - # if the callback returns, assume the key is ok - our_server_key = server_key - - if server_key != our_server_key: - raise BadHostKeyException(hostname, server_key, our_server_key) + + # If GSS-API Key Exchange is performed we are not required to check the + # host key, because the host is authenticated via GSS-API / SSPI as + # well as our client. + if not self._transport.use_gss_kex: + our_server_key = self._system_host_keys.get(server_hostkey_name, + {}).get(keytype, None) + if our_server_key is None: + our_server_key = self._host_keys.get(server_hostkey_name, + {}).get(keytype, None) + if our_server_key is None: + # will raise exception if the key is rejected; let that fall out + self._policy.missing_host_key(self, server_hostkey_name, + server_key) + # if the callback returns, assume the key is ok + our_server_key = server_key + + if server_key != our_server_key: + raise BadHostKeyException(hostname, server_key, our_server_key) if username is None: username = getpass.getuser() @@ -270,7 +301,10 @@ class SSHClient (object): key_filenames = [key_filename] else: key_filenames = key_filename - self._auth(username, password, pkey, key_filenames, allow_agent, look_for_keys) + if gss_host is None: + gss_host = hostname + self._auth(username, password, pkey, key_filenames, allow_agent, + look_for_keys, gss_auth, gss_kex, gss_deleg_creds, gss_host) def close(self): """ @@ -354,13 +388,15 @@ class SSHClient (object): """ return self._transport - def _auth(self, username, password, pkey, key_filenames, allow_agent, look_for_keys): + def _auth(self, username, password, pkey, key_filenames, allow_agent, + look_for_keys, gss_auth, gss_kex, gss_deleg_creds, gss_host): """ Try, in order: - The key passed in, if one was passed in. - Any key we can find through an SSH agent (if allowed). - - Any "id_rsa" or "id_dsa" key discoverable in ~/.ssh/ (if allowed). + - Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in ~/.ssh/ + (if allowed). - Plain username/password auth, if a password was given. (The password might be needed to unlock a private key, or for @@ -370,6 +406,27 @@ class SSHClient (object): two_factor = False allowed_types = [] + # If GSS-API support and GSS-PI Key Exchange was performed, we attempt + # authentication with gssapi-keyex. + if gss_kex and self._transport.gss_kex_used: + try: + self._transport.auth_gssapi_keyex(username) + return + except Exception as e: + saved_exception = e + + # Try GSS-API authentication (gssapi-with-mic) only if GSS-API Key + # Exchange is not performed, because if we use GSS-API for the key + # exchange, there is already a fully established GSS-API context, so + # why should we do that again? + if gss_auth: + try: + self._transport.auth_gssapi_with_mic(username, gss_host, + gss_deleg_creds) + return + except Exception as e: + saved_exception = e + if pkey is not None: try: self._log(DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint())) @@ -382,7 +439,7 @@ class SSHClient (object): if not two_factor: for key_filename in key_filenames: - for pkey_class in (RSAKey, DSSKey): + for pkey_class in (RSAKey, DSSKey, ECDSAKey): try: key = pkey_class.from_private_key_file(key_filename, password) self._log(DEBUG, 'Trying key %s from %s' % (hexlify(key.get_fingerprint()), key_filename)) @@ -414,17 +471,23 @@ class SSHClient (object): keyfiles = [] rsa_key = os.path.expanduser('~/.ssh/id_rsa') dsa_key = os.path.expanduser('~/.ssh/id_dsa') + ecdsa_key = os.path.expanduser('~/.ssh/id_ecdsa') if os.path.isfile(rsa_key): keyfiles.append((RSAKey, rsa_key)) if os.path.isfile(dsa_key): keyfiles.append((DSSKey, dsa_key)) + if os.path.isfile(ecdsa_key): + keyfiles.append((ECDSAKey, ecdsa_key)) # look in ~/ssh/ for windows users: rsa_key = os.path.expanduser('~/ssh/id_rsa') dsa_key = os.path.expanduser('~/ssh/id_dsa') + ecdsa_key = os.path.expanduser('~/ssh/id_ecdsa') if os.path.isfile(rsa_key): keyfiles.append((RSAKey, rsa_key)) if os.path.isfile(dsa_key): keyfiles.append((DSSKey, dsa_key)) + if os.path.isfile(ecdsa_key): + keyfiles.append((ECDSAKey, ecdsa_key)) if not look_for_keys: keyfiles = [] |