Coverage for /pythoncovmergedfiles/medio/medio/src/paramiko/paramiko/client.py: 16%
280 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:36 +0000
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:36 +0000
1# Copyright (C) 2006-2007 Robey Pointer <robeypointer@gmail.com>
2#
3# This file is part of paramiko.
4#
5# Paramiko is free software; you can redistribute it and/or modify it under the
6# terms of the GNU Lesser General Public License as published by the Free
7# Software Foundation; either version 2.1 of the License, or (at your option)
8# any later version.
9#
10# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13# details.
14#
15# You should have received a copy of the GNU Lesser General Public License
16# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19"""
20SSH client & key policies
21"""
23from binascii import hexlify
24import getpass
25import inspect
26import os
27import socket
28import warnings
29from errno import ECONNREFUSED, EHOSTUNREACH
31from paramiko.agent import Agent
32from paramiko.common import DEBUG
33from paramiko.config import SSH_PORT
34from paramiko.dsskey import DSSKey
35from paramiko.ecdsakey import ECDSAKey
36from paramiko.ed25519key import Ed25519Key
37from paramiko.hostkeys import HostKeys
38from paramiko.rsakey import RSAKey
39from paramiko.ssh_exception import (
40 SSHException,
41 BadHostKeyException,
42 NoValidConnectionsError,
43)
44from paramiko.transport import Transport
45from paramiko.util import ClosingContextManager
48class SSHClient(ClosingContextManager):
49 """
50 A high-level representation of a session with an SSH server. This class
51 wraps `.Transport`, `.Channel`, and `.SFTPClient` to take care of most
52 aspects of authenticating and opening channels. A typical use case is::
54 client = SSHClient()
55 client.load_system_host_keys()
56 client.connect('ssh.example.com')
57 stdin, stdout, stderr = client.exec_command('ls -l')
59 You may pass in explicit overrides for authentication and server host key
60 checking. The default mechanism is to try to use local key files or an
61 SSH agent (if one is running).
63 Instances of this class may be used as context managers.
65 .. versionadded:: 1.6
66 """
68 def __init__(self):
69 """
70 Create a new SSHClient.
71 """
72 self._system_host_keys = HostKeys()
73 self._host_keys = HostKeys()
74 self._host_keys_filename = None
75 self._log_channel = None
76 self._policy = RejectPolicy()
77 self._transport = None
78 self._agent = None
80 def load_system_host_keys(self, filename=None):
81 """
82 Load host keys from a system (read-only) file. Host keys read with
83 this method will not be saved back by `save_host_keys`.
85 This method can be called multiple times. Each new set of host keys
86 will be merged with the existing set (new replacing old if there are
87 conflicts).
89 If ``filename`` is left as ``None``, an attempt will be made to read
90 keys from the user's local "known hosts" file, as used by OpenSSH,
91 and no exception will be raised if the file can't be read. This is
92 probably only useful on posix.
94 :param str filename: the filename to read, or ``None``
96 :raises: ``IOError`` --
97 if a filename was provided and the file could not be read
98 """
99 if filename is None:
100 # try the user's .ssh key file, and mask exceptions
101 filename = os.path.expanduser("~/.ssh/known_hosts")
102 try:
103 self._system_host_keys.load(filename)
104 except IOError:
105 pass
106 return
107 self._system_host_keys.load(filename)
109 def load_host_keys(self, filename):
110 """
111 Load host keys from a local host-key file. Host keys read with this
112 method will be checked after keys loaded via `load_system_host_keys`,
113 but will be saved back by `save_host_keys` (so they can be modified).
114 The missing host key policy `.AutoAddPolicy` adds keys to this set and
115 saves them, when connecting to a previously-unknown server.
117 This method can be called multiple times. Each new set of host keys
118 will be merged with the existing set (new replacing old if there are
119 conflicts). When automatically saving, the last hostname is used.
121 :param str filename: the filename to read
123 :raises: ``IOError`` -- if the filename could not be read
124 """
125 self._host_keys_filename = filename
126 self._host_keys.load(filename)
128 def save_host_keys(self, filename):
129 """
130 Save the host keys back to a file. Only the host keys loaded with
131 `load_host_keys` (plus any added directly) will be saved -- not any
132 host keys loaded with `load_system_host_keys`.
134 :param str filename: the filename to save to
136 :raises: ``IOError`` -- if the file could not be written
137 """
139 # update local host keys from file (in case other SSH clients
140 # have written to the known_hosts file meanwhile.
141 if self._host_keys_filename is not None:
142 self.load_host_keys(self._host_keys_filename)
144 with open(filename, "w") as f:
145 for hostname, keys in self._host_keys.items():
146 for keytype, key in keys.items():
147 f.write(
148 "{} {} {}\n".format(
149 hostname, keytype, key.get_base64()
150 )
151 )
153 def get_host_keys(self):
154 """
155 Get the local `.HostKeys` object. This can be used to examine the
156 local host keys or change them.
158 :return: the local host keys as a `.HostKeys` object.
159 """
160 return self._host_keys
162 def set_log_channel(self, name):
163 """
164 Set the channel for logging. The default is ``"paramiko.transport"``
165 but it can be set to anything you want.
167 :param str name: new channel name for logging
168 """
169 self._log_channel = name
171 def set_missing_host_key_policy(self, policy):
172 """
173 Set policy to use when connecting to servers without a known host key.
175 Specifically:
177 * A **policy** is a "policy class" (or instance thereof), namely some
178 subclass of `.MissingHostKeyPolicy` such as `.RejectPolicy` (the
179 default), `.AutoAddPolicy`, `.WarningPolicy`, or a user-created
180 subclass.
181 * A host key is **known** when it appears in the client object's cached
182 host keys structures (those manipulated by `load_system_host_keys`
183 and/or `load_host_keys`).
185 :param .MissingHostKeyPolicy policy:
186 the policy to use when receiving a host key from a
187 previously-unknown server
188 """
189 if inspect.isclass(policy):
190 policy = policy()
191 self._policy = policy
193 def _families_and_addresses(self, hostname, port):
194 """
195 Yield pairs of address families and addresses to try for connecting.
197 :param str hostname: the server to connect to
198 :param int port: the server port to connect to
199 :returns: Yields an iterable of ``(family, address)`` tuples
200 """
201 guess = True
202 addrinfos = socket.getaddrinfo(
203 hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM
204 )
205 for (family, socktype, proto, canonname, sockaddr) in addrinfos:
206 if socktype == socket.SOCK_STREAM:
207 yield family, sockaddr
208 guess = False
210 # some OS like AIX don't indicate SOCK_STREAM support, so just
211 # guess. :( We only do this if we did not get a single result marked
212 # as socktype == SOCK_STREAM.
213 if guess:
214 for family, _, _, _, sockaddr in addrinfos:
215 yield family, sockaddr
217 def connect(
218 self,
219 hostname,
220 port=SSH_PORT,
221 username=None,
222 password=None,
223 pkey=None,
224 key_filename=None,
225 timeout=None,
226 allow_agent=True,
227 look_for_keys=True,
228 compress=False,
229 sock=None,
230 gss_auth=False,
231 gss_kex=False,
232 gss_deleg_creds=True,
233 gss_host=None,
234 banner_timeout=None,
235 auth_timeout=None,
236 channel_timeout=None,
237 gss_trust_dns=True,
238 passphrase=None,
239 disabled_algorithms=None,
240 transport_factory=None,
241 ):
242 """
243 Connect to an SSH server and authenticate to it. The server's host key
244 is checked against the system host keys (see `load_system_host_keys`)
245 and any local host keys (`load_host_keys`). If the server's hostname
246 is not found in either set of host keys, the missing host key policy
247 is used (see `set_missing_host_key_policy`). The default policy is
248 to reject the key and raise an `.SSHException`.
250 Authentication is attempted in the following order of priority:
252 - The ``pkey`` or ``key_filename`` passed in (if any)
254 - ``key_filename`` may contain OpenSSH public certificate paths
255 as well as regular private-key paths; when files ending in
256 ``-cert.pub`` are found, they are assumed to match a private
257 key, and both components will be loaded. (The private key
258 itself does *not* need to be listed in ``key_filename`` for
259 this to occur - *just* the certificate.)
261 - Any key we can find through an SSH agent
262 - Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in
263 ``~/.ssh/``
265 - When OpenSSH-style public certificates exist that match an
266 existing such private key (so e.g. one has ``id_rsa`` and
267 ``id_rsa-cert.pub``) the certificate will be loaded alongside
268 the private key and used for authentication.
270 - Plain username/password auth, if a password was given
272 If a private key requires a password to unlock it, and a password is
273 passed in, that password will be used to attempt to unlock the key.
275 :param str hostname: the server to connect to
276 :param int port: the server port to connect to
277 :param str username:
278 the username to authenticate as (defaults to the current local
279 username)
280 :param str password:
281 Used for password authentication; is also used for private key
282 decryption if ``passphrase`` is not given.
283 :param str passphrase:
284 Used for decrypting private keys.
285 :param .PKey pkey: an optional private key to use for authentication
286 :param str key_filename:
287 the filename, or list of filenames, of optional private key(s)
288 and/or certs to try for authentication
289 :param float timeout:
290 an optional timeout (in seconds) for the TCP connect
291 :param bool allow_agent:
292 set to False to disable connecting to the SSH agent
293 :param bool look_for_keys:
294 set to False to disable searching for discoverable private key
295 files in ``~/.ssh/``
296 :param bool compress: set to True to turn on compression
297 :param socket sock:
298 an open socket or socket-like object (such as a `.Channel`) to use
299 for communication to the target host
300 :param bool gss_auth:
301 ``True`` if you want to use GSS-API authentication
302 :param bool gss_kex:
303 Perform GSS-API Key Exchange and user authentication
304 :param bool gss_deleg_creds: Delegate GSS-API client credentials or not
305 :param str gss_host:
306 The targets name in the kerberos database. default: hostname
307 :param bool gss_trust_dns:
308 Indicates whether or not the DNS is trusted to securely
309 canonicalize the name of the host being connected to (default
310 ``True``).
311 :param float banner_timeout: an optional timeout (in seconds) to wait
312 for the SSH banner to be presented.
313 :param float auth_timeout: an optional timeout (in seconds) to wait for
314 an authentication response.
315 :param float channel_timeout: an optional timeout (in seconds) to wait
316 for a channel open response.
317 :param dict disabled_algorithms:
318 an optional dict passed directly to `.Transport` and its keyword
319 argument of the same name.
320 :param transport_factory:
321 an optional callable which is handed a subset of the constructor
322 arguments (primarily those related to the socket, GSS
323 functionality, and algorithm selection) and generates a
324 `.Transport` instance to be used by this client. Defaults to
325 `.Transport.__init__`.
327 :raises BadHostKeyException:
328 if the server's host key could not be verified.
329 :raises AuthenticationException: if authentication failed.
330 :raises socket.error:
331 if a socket error (other than connection-refused or
332 host-unreachable) occurred while connecting.
333 :raises NoValidConnectionsError:
334 if all valid connection targets for the requested hostname (eg IPv4
335 and IPv6) yielded connection-refused or host-unreachable socket
336 errors.
337 :raises SSHException:
338 if there was any other error connecting or establishing an SSH
339 session.
341 .. versionchanged:: 1.15
342 Added the ``banner_timeout``, ``gss_auth``, ``gss_kex``,
343 ``gss_deleg_creds`` and ``gss_host`` arguments.
344 .. versionchanged:: 2.3
345 Added the ``gss_trust_dns`` argument.
346 .. versionchanged:: 2.4
347 Added the ``passphrase`` argument.
348 .. versionchanged:: 2.6
349 Added the ``disabled_algorithms`` argument.
350 .. versionchanged:: 2.12
351 Added the ``transport_factory`` argument.
352 """
353 if not sock:
354 errors = {}
355 # Try multiple possible address families (e.g. IPv4 vs IPv6)
356 to_try = list(self._families_and_addresses(hostname, port))
357 for af, addr in to_try:
358 try:
359 sock = socket.socket(af, socket.SOCK_STREAM)
360 if timeout is not None:
361 try:
362 sock.settimeout(timeout)
363 except:
364 pass
365 sock.connect(addr)
366 # Break out of the loop on success
367 break
368 except socket.error as e:
369 # As mentioned in socket docs it is better
370 # to close sockets explicitly
371 if sock:
372 sock.close()
373 # Raise anything that isn't a straight up connection error
374 # (such as a resolution error)
375 if e.errno not in (ECONNREFUSED, EHOSTUNREACH):
376 raise
377 # Capture anything else so we know how the run looks once
378 # iteration is complete. Retain info about which attempt
379 # this was.
380 errors[addr] = e
382 # Make sure we explode usefully if no address family attempts
383 # succeeded. We've no way of knowing which error is the "right"
384 # one, so we construct a hybrid exception containing all the real
385 # ones, of a subclass that client code should still be watching for
386 # (socket.error)
387 if len(errors) == len(to_try):
388 raise NoValidConnectionsError(errors)
390 if transport_factory is None:
391 transport_factory = Transport
392 t = self._transport = transport_factory(
393 sock,
394 gss_kex=gss_kex,
395 gss_deleg_creds=gss_deleg_creds,
396 disabled_algorithms=disabled_algorithms,
397 )
398 t.use_compression(compress=compress)
399 t.set_gss_host(
400 # t.hostname may be None, but GSS-API requires a target name.
401 # Therefore use hostname as fallback.
402 gss_host=gss_host or hostname,
403 trust_dns=gss_trust_dns,
404 gssapi_requested=gss_auth or gss_kex,
405 )
406 if self._log_channel is not None:
407 t.set_log_channel(self._log_channel)
408 if banner_timeout is not None:
409 t.banner_timeout = banner_timeout
410 if auth_timeout is not None:
411 t.auth_timeout = auth_timeout
412 if channel_timeout is not None:
413 t.channel_timeout = channel_timeout
415 if port == SSH_PORT:
416 server_hostkey_name = hostname
417 else:
418 server_hostkey_name = "[{}]:{}".format(hostname, port)
419 our_server_keys = None
421 our_server_keys = self._system_host_keys.get(server_hostkey_name)
422 if our_server_keys is None:
423 our_server_keys = self._host_keys.get(server_hostkey_name)
424 if our_server_keys is not None:
425 keytype = our_server_keys.keys()[0]
426 sec_opts = t.get_security_options()
427 other_types = [x for x in sec_opts.key_types if x != keytype]
428 sec_opts.key_types = [keytype] + other_types
430 t.start_client(timeout=timeout)
432 # If GSS-API Key Exchange is performed we are not required to check the
433 # host key, because the host is authenticated via GSS-API / SSPI as
434 # well as our client.
435 if not self._transport.gss_kex_used:
436 server_key = t.get_remote_server_key()
437 if our_server_keys is None:
438 # will raise exception if the key is rejected
439 self._policy.missing_host_key(
440 self, server_hostkey_name, server_key
441 )
442 else:
443 our_key = our_server_keys.get(server_key.get_name())
444 if our_key != server_key:
445 if our_key is None:
446 our_key = list(our_server_keys.values())[0]
447 raise BadHostKeyException(hostname, server_key, our_key)
449 if username is None:
450 username = getpass.getuser()
452 if key_filename is None:
453 key_filenames = []
454 elif isinstance(key_filename, str):
455 key_filenames = [key_filename]
456 else:
457 key_filenames = key_filename
459 self._auth(
460 username,
461 password,
462 pkey,
463 key_filenames,
464 allow_agent,
465 look_for_keys,
466 gss_auth,
467 gss_kex,
468 gss_deleg_creds,
469 t.gss_host,
470 passphrase,
471 )
473 def close(self):
474 """
475 Close this SSHClient and its underlying `.Transport`.
477 This should be called anytime you are done using the client object.
479 .. warning::
480 Paramiko registers garbage collection hooks that will try to
481 automatically close connections for you, but this is not presently
482 reliable. Failure to explicitly close your client after use may
483 lead to end-of-process hangs!
484 """
485 if self._transport is None:
486 return
487 self._transport.close()
488 self._transport = None
490 if self._agent is not None:
491 self._agent.close()
492 self._agent = None
494 def exec_command(
495 self,
496 command,
497 bufsize=-1,
498 timeout=None,
499 get_pty=False,
500 environment=None,
501 ):
502 """
503 Execute a command on the SSH server. A new `.Channel` is opened and
504 the requested command is executed. The command's input and output
505 streams are returned as Python ``file``-like objects representing
506 stdin, stdout, and stderr.
508 :param str command: the command to execute
509 :param int bufsize:
510 interpreted the same way as by the built-in ``file()`` function in
511 Python
512 :param int timeout:
513 set command's channel timeout. See `.Channel.settimeout`
514 :param bool get_pty:
515 Request a pseudo-terminal from the server (default ``False``).
516 See `.Channel.get_pty`
517 :param dict environment:
518 a dict of shell environment variables, to be merged into the
519 default environment that the remote command executes within.
521 .. warning::
522 Servers may silently reject some environment variables; see the
523 warning in `.Channel.set_environment_variable` for details.
525 :return:
526 the stdin, stdout, and stderr of the executing command, as a
527 3-tuple
529 :raises: `.SSHException` -- if the server fails to execute the command
531 .. versionchanged:: 1.10
532 Added the ``get_pty`` kwarg.
533 """
534 chan = self._transport.open_session(timeout=timeout)
535 if get_pty:
536 chan.get_pty()
537 chan.settimeout(timeout)
538 if environment:
539 chan.update_environment(environment)
540 chan.exec_command(command)
541 stdin = chan.makefile_stdin("wb", bufsize)
542 stdout = chan.makefile("r", bufsize)
543 stderr = chan.makefile_stderr("r", bufsize)
544 return stdin, stdout, stderr
546 def invoke_shell(
547 self,
548 term="vt100",
549 width=80,
550 height=24,
551 width_pixels=0,
552 height_pixels=0,
553 environment=None,
554 ):
555 """
556 Start an interactive shell session on the SSH server. A new `.Channel`
557 is opened and connected to a pseudo-terminal using the requested
558 terminal type and size.
560 :param str term:
561 the terminal type to emulate (for example, ``"vt100"``)
562 :param int width: the width (in characters) of the terminal window
563 :param int height: the height (in characters) of the terminal window
564 :param int width_pixels: the width (in pixels) of the terminal window
565 :param int height_pixels: the height (in pixels) of the terminal window
566 :param dict environment: the command's environment
567 :return: a new `.Channel` connected to the remote shell
569 :raises: `.SSHException` -- if the server fails to invoke a shell
570 """
571 chan = self._transport.open_session()
572 chan.get_pty(term, width, height, width_pixels, height_pixels)
573 chan.invoke_shell()
574 return chan
576 def open_sftp(self):
577 """
578 Open an SFTP session on the SSH server.
580 :return: a new `.SFTPClient` session object
581 """
582 return self._transport.open_sftp_client()
584 def get_transport(self):
585 """
586 Return the underlying `.Transport` object for this SSH connection.
587 This can be used to perform lower-level tasks, like opening specific
588 kinds of channels.
590 :return: the `.Transport` for this connection
591 """
592 return self._transport
594 def _key_from_filepath(self, filename, klass, password):
595 """
596 Attempt to derive a `.PKey` from given string path ``filename``:
598 - If ``filename`` appears to be a cert, the matching private key is
599 loaded.
600 - Otherwise, the filename is assumed to be a private key, and the
601 matching public cert will be loaded if it exists.
602 """
603 cert_suffix = "-cert.pub"
604 # Assume privkey, not cert, by default
605 if filename.endswith(cert_suffix):
606 key_path = filename[: -len(cert_suffix)]
607 cert_path = filename
608 else:
609 key_path = filename
610 cert_path = filename + cert_suffix
611 # Blindly try the key path; if no private key, nothing will work.
612 key = klass.from_private_key_file(key_path, password)
613 # TODO: change this to 'Loading' instead of 'Trying' sometime; probably
614 # when #387 is released, since this is a critical log message users are
615 # likely testing/filtering for (bah.)
616 msg = "Trying discovered key {} in {}".format(
617 hexlify(key.get_fingerprint()), key_path
618 )
619 self._log(DEBUG, msg)
620 # Attempt to load cert if it exists.
621 if os.path.isfile(cert_path):
622 key.load_certificate(cert_path)
623 self._log(DEBUG, "Adding public certificate {}".format(cert_path))
624 return key
626 def _auth(
627 self,
628 username,
629 password,
630 pkey,
631 key_filenames,
632 allow_agent,
633 look_for_keys,
634 gss_auth,
635 gss_kex,
636 gss_deleg_creds,
637 gss_host,
638 passphrase,
639 ):
640 """
641 Try, in order:
643 - The key(s) passed in, if one was passed in.
644 - Any key we can find through an SSH agent (if allowed).
645 - Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in ~/.ssh/
646 (if allowed).
647 - Plain username/password auth, if a password was given.
649 (The password might be needed to unlock a private key [if 'passphrase'
650 isn't also given], or for two-factor authentication [for which it is
651 required].)
652 """
653 saved_exception = None
654 two_factor = False
655 allowed_types = set()
656 two_factor_types = {"keyboard-interactive", "password"}
657 if passphrase is None and password is not None:
658 passphrase = password
660 # If GSS-API support and GSS-PI Key Exchange was performed, we attempt
661 # authentication with gssapi-keyex.
662 if gss_kex and self._transport.gss_kex_used:
663 try:
664 self._transport.auth_gssapi_keyex(username)
665 return
666 except Exception as e:
667 saved_exception = e
669 # Try GSS-API authentication (gssapi-with-mic) only if GSS-API Key
670 # Exchange is not performed, because if we use GSS-API for the key
671 # exchange, there is already a fully established GSS-API context, so
672 # why should we do that again?
673 if gss_auth:
674 try:
675 return self._transport.auth_gssapi_with_mic(
676 username, gss_host, gss_deleg_creds
677 )
678 except Exception as e:
679 saved_exception = e
681 if pkey is not None:
682 try:
683 self._log(
684 DEBUG,
685 "Trying SSH key {}".format(
686 hexlify(pkey.get_fingerprint())
687 ),
688 )
689 allowed_types = set(
690 self._transport.auth_publickey(username, pkey)
691 )
692 two_factor = allowed_types & two_factor_types
693 if not two_factor:
694 return
695 except SSHException as e:
696 saved_exception = e
698 if not two_factor:
699 for key_filename in key_filenames:
700 for pkey_class in (RSAKey, DSSKey, ECDSAKey, Ed25519Key):
701 try:
702 key = self._key_from_filepath(
703 key_filename, pkey_class, passphrase
704 )
705 allowed_types = set(
706 self._transport.auth_publickey(username, key)
707 )
708 two_factor = allowed_types & two_factor_types
709 if not two_factor:
710 return
711 break
712 except SSHException as e:
713 saved_exception = e
715 if not two_factor and allow_agent:
716 if self._agent is None:
717 self._agent = Agent()
719 for key in self._agent.get_keys():
720 try:
721 id_ = hexlify(key.get_fingerprint())
722 self._log(DEBUG, "Trying SSH agent key {}".format(id_))
723 # for 2-factor auth a successfully auth'd key password
724 # will return an allowed 2fac auth method
725 allowed_types = set(
726 self._transport.auth_publickey(username, key)
727 )
728 two_factor = allowed_types & two_factor_types
729 if not two_factor:
730 return
731 break
732 except SSHException as e:
733 saved_exception = e
735 if not two_factor:
736 keyfiles = []
738 for keytype, name in [
739 (RSAKey, "rsa"),
740 (DSSKey, "dsa"),
741 (ECDSAKey, "ecdsa"),
742 (Ed25519Key, "ed25519"),
743 ]:
744 # ~/ssh/ is for windows
745 for directory in [".ssh", "ssh"]:
746 full_path = os.path.expanduser(
747 "~/{}/id_{}".format(directory, name)
748 )
749 if os.path.isfile(full_path):
750 # TODO: only do this append if below did not run
751 keyfiles.append((keytype, full_path))
752 if os.path.isfile(full_path + "-cert.pub"):
753 keyfiles.append((keytype, full_path + "-cert.pub"))
755 if not look_for_keys:
756 keyfiles = []
758 for pkey_class, filename in keyfiles:
759 try:
760 key = self._key_from_filepath(
761 filename, pkey_class, passphrase
762 )
763 # for 2-factor auth a successfully auth'd key will result
764 # in ['password']
765 allowed_types = set(
766 self._transport.auth_publickey(username, key)
767 )
768 two_factor = allowed_types & two_factor_types
769 if not two_factor:
770 return
771 break
772 except (SSHException, IOError) as e:
773 saved_exception = e
775 if password is not None:
776 try:
777 self._transport.auth_password(username, password)
778 return
779 except SSHException as e:
780 saved_exception = e
781 elif two_factor:
782 try:
783 self._transport.auth_interactive_dumb(username)
784 return
785 except SSHException as e:
786 saved_exception = e
788 # if we got an auth-failed exception earlier, re-raise it
789 if saved_exception is not None:
790 raise saved_exception
791 raise SSHException("No authentication methods available")
793 def _log(self, level, msg):
794 self._transport._log(level, msg)
797class MissingHostKeyPolicy:
798 """
799 Interface for defining the policy that `.SSHClient` should use when the
800 SSH server's hostname is not in either the system host keys or the
801 application's keys. Pre-made classes implement policies for automatically
802 adding the key to the application's `.HostKeys` object (`.AutoAddPolicy`),
803 and for automatically rejecting the key (`.RejectPolicy`).
805 This function may be used to ask the user to verify the key, for example.
806 """
808 def missing_host_key(self, client, hostname, key):
809 """
810 Called when an `.SSHClient` receives a server key for a server that
811 isn't in either the system or local `.HostKeys` object. To accept
812 the key, simply return. To reject, raised an exception (which will
813 be passed to the calling application).
814 """
815 pass
818class AutoAddPolicy(MissingHostKeyPolicy):
819 """
820 Policy for automatically adding the hostname and new host key to the
821 local `.HostKeys` object, and saving it. This is used by `.SSHClient`.
822 """
824 def missing_host_key(self, client, hostname, key):
825 client._host_keys.add(hostname, key.get_name(), key)
826 if client._host_keys_filename is not None:
827 client.save_host_keys(client._host_keys_filename)
828 client._log(
829 DEBUG,
830 "Adding {} host key for {}: {}".format(
831 key.get_name(), hostname, hexlify(key.get_fingerprint())
832 ),
833 )
836class RejectPolicy(MissingHostKeyPolicy):
837 """
838 Policy for automatically rejecting the unknown hostname & key. This is
839 used by `.SSHClient`.
840 """
842 def missing_host_key(self, client, hostname, key):
843 client._log(
844 DEBUG,
845 "Rejecting {} host key for {}: {}".format(
846 key.get_name(), hostname, hexlify(key.get_fingerprint())
847 ),
848 )
849 raise SSHException(
850 "Server {!r} not found in known_hosts".format(hostname)
851 )
854class WarningPolicy(MissingHostKeyPolicy):
855 """
856 Policy for logging a Python-style warning for an unknown host key, but
857 accepting it. This is used by `.SSHClient`.
858 """
860 def missing_host_key(self, client, hostname, key):
861 warnings.warn(
862 "Unknown {} host key for {}: {}".format(
863 key.get_name(), hostname, hexlify(key.get_fingerprint())
864 )
865 )