Coverage for /pythoncovmergedfiles/medio/medio/src/paramiko/paramiko/client.py: 16%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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"""
23import getpass
24import inspect
25import os
26import socket
27import warnings
28from binascii import hexlify
29from errno import ECONNREFUSED, EHOSTUNREACH
31from paramiko.agent import Agent
32from paramiko.common import DEBUG
33from paramiko.config import SSH_PORT
34from paramiko.ecdsakey import ECDSAKey
35from paramiko.ed25519key import Ed25519Key
36from paramiko.hostkeys import HostKeys
37from paramiko.rsakey import RSAKey
38from paramiko.ssh_exception import (
39 BadHostKeyException,
40 NoValidConnectionsError,
41 SSHException,
42)
43from paramiko.transport import Transport
44from paramiko.util import ClosingContextManager
47class SSHClient(ClosingContextManager):
48 """
49 A high-level representation of a session with an SSH server. This class
50 wraps `.Transport`, `.Channel`, and `.SFTPClient` to take care of most
51 aspects of authenticating and opening channels. A typical use case is::
53 client = SSHClient()
54 client.load_system_host_keys()
55 client.connect('ssh.example.com')
56 stdin, stdout, stderr = client.exec_command('ls -l')
58 You may pass in explicit overrides for authentication and server host key
59 checking. The default mechanism is to try to use local key files or an
60 SSH agent (if one is running).
62 Instances of this class may be used as context managers.
64 .. versionadded:: 1.6
65 """
67 def __init__(self):
68 """
69 Create a new SSHClient.
70 """
71 self._system_host_keys = HostKeys()
72 self._host_keys = HostKeys()
73 self._host_keys_filename = None
74 self._log_channel = None
75 self._policy = RejectPolicy()
76 self._transport = None
77 self._agent = None
79 def load_system_host_keys(self, filename=None):
80 """
81 Load host keys from a system (read-only) file. Host keys read with
82 this method will not be saved back by `save_host_keys`.
84 This method can be called multiple times. Each new set of host keys
85 will be merged with the existing set (new replacing old if there are
86 conflicts).
88 If ``filename`` is left as ``None``, an attempt will be made to read
89 keys from the user's local "known hosts" file, as used by OpenSSH,
90 and no exception will be raised if the file can't be read. This is
91 probably only useful on posix.
93 :param str filename: the filename to read, or ``None``
95 :raises: ``IOError`` --
96 if a filename was provided and the file could not be read
97 """
98 if filename is None:
99 # try the user's .ssh key file, and mask exceptions
100 filename = os.path.expanduser("~/.ssh/known_hosts")
101 try:
102 self._system_host_keys.load(filename)
103 except IOError:
104 pass
105 return
106 self._system_host_keys.load(filename)
108 def load_host_keys(self, filename):
109 """
110 Load host keys from a local host-key file. Host keys read with this
111 method will be checked after keys loaded via `load_system_host_keys`,
112 but will be saved back by `save_host_keys` (so they can be modified).
113 The missing host key policy `.AutoAddPolicy` adds keys to this set and
114 saves them, when connecting to a previously-unknown server.
116 This method can be called multiple times. Each new set of host keys
117 will be merged with the existing set (new replacing old if there are
118 conflicts). When automatically saving, the last hostname is used.
120 :param str filename: the filename to read
122 :raises: ``IOError`` -- if the filename could not be read
123 """
124 self._host_keys_filename = filename
125 self._host_keys.load(filename)
127 def save_host_keys(self, filename):
128 """
129 Save the host keys back to a file. Only the host keys loaded with
130 `load_host_keys` (plus any added directly) will be saved -- not any
131 host keys loaded with `load_system_host_keys`.
133 :param str filename: the filename to save to
135 :raises: ``IOError`` -- if the file could not be written
136 """
138 # update local host keys from file (in case other SSH clients
139 # have written to the known_hosts file meanwhile.
140 if self._host_keys_filename is not None:
141 self.load_host_keys(self._host_keys_filename)
143 with open(filename, "w") as f:
144 for hostname, keys in self._host_keys.items():
145 for keytype, key in keys.items():
146 f.write(
147 "{} {} {}\n".format(
148 hostname, keytype, key.get_base64()
149 )
150 )
152 def get_host_keys(self):
153 """
154 Get the local `.HostKeys` object. This can be used to examine the
155 local host keys or change them.
157 :return: the local host keys as a `.HostKeys` object.
158 """
159 return self._host_keys
161 def set_log_channel(self, name):
162 """
163 Set the channel for logging. The default is ``"paramiko.transport"``
164 but it can be set to anything you want.
166 :param str name: new channel name for logging
167 """
168 self._log_channel = name
170 def set_missing_host_key_policy(self, policy):
171 """
172 Set policy to use when connecting to servers without a known host key.
174 Specifically:
176 * A **policy** is a "policy class" (or instance thereof), namely some
177 subclass of `.MissingHostKeyPolicy` such as `.RejectPolicy` (the
178 default), `.AutoAddPolicy`, `.WarningPolicy`, or a user-created
179 subclass.
180 * A host key is **known** when it appears in the client object's cached
181 host keys structures (those manipulated by `load_system_host_keys`
182 and/or `load_host_keys`).
184 :param .MissingHostKeyPolicy policy:
185 the policy to use when receiving a host key from a
186 previously-unknown server
187 """
188 if inspect.isclass(policy):
189 policy = policy()
190 self._policy = policy
192 def _families_and_addresses(self, hostname, port):
193 """
194 Yield pairs of address families and addresses to try for connecting.
196 :param str hostname: the server to connect to
197 :param int port: the server port to connect to
198 :returns: Yields an iterable of ``(family, address)`` tuples
199 """
200 guess = True
201 addrinfos = socket.getaddrinfo(
202 hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM
203 )
204 for family, socktype, proto, canonname, sockaddr in addrinfos:
205 if socktype == socket.SOCK_STREAM:
206 yield family, sockaddr
207 guess = False
209 # some OS like AIX don't indicate SOCK_STREAM support, so just
210 # guess. :( We only do this if we did not get a single result marked
211 # as socktype == SOCK_STREAM.
212 if guess:
213 for family, _, _, _, sockaddr in addrinfos:
214 yield family, sockaddr
216 def connect(
217 self,
218 hostname,
219 port=SSH_PORT,
220 username=None,
221 password=None,
222 pkey=None,
223 key_filename=None,
224 timeout=None,
225 allow_agent=True,
226 look_for_keys=True,
227 compress=False,
228 sock=None,
229 banner_timeout=None,
230 auth_timeout=None,
231 channel_timeout=None,
232 passphrase=None,
233 disabled_algorithms=None,
234 transport_factory=None,
235 auth_strategy=None,
236 ):
237 """
238 Connect to an SSH server and authenticate to it. The server's host key
239 is checked against the system host keys (see `load_system_host_keys`)
240 and any local host keys (`load_host_keys`). If the server's hostname
241 is not found in either set of host keys, the missing host key policy
242 is used (see `set_missing_host_key_policy`). The default policy is
243 to reject the key and raise an `.SSHException`.
245 Authentication is attempted in the following order of priority:
247 - The ``pkey`` or ``key_filename`` passed in (if any)
249 - ``key_filename`` may contain OpenSSH public certificate paths
250 as well as regular private-key paths; when files ending in
251 ``-cert.pub`` are found, they are assumed to match a private
252 key, and both components will be loaded. (The private key
253 itself does *not* need to be listed in ``key_filename`` for
254 this to occur - *just* the certificate.)
256 - Any key we can find through an SSH agent
257 - Any ``id_*`` keys discoverable in ``~/.ssh/``
259 - When OpenSSH-style public certificates exist that match an
260 existing such private key (so e.g. one has ``id_rsa`` and
261 ``id_rsa-cert.pub``) the certificate will be loaded alongside
262 the private key and used for authentication.
264 - Plain username/password auth, if a password was given
266 If a private key requires a password to unlock it, and a password is
267 passed in, that password will be used to attempt to unlock the key.
269 :param str hostname: the server to connect to
270 :param int port: the server port to connect to
271 :param str username:
272 the username to authenticate as (defaults to the current local
273 username)
274 :param str password:
275 Used for password authentication; is also used for private key
276 decryption if ``passphrase`` is not given.
277 :param str passphrase:
278 Used for decrypting private keys.
279 :param .PKey pkey: an optional private key to use for authentication
280 :param str key_filename:
281 the filename, or list of filenames, of optional private key(s)
282 and/or certs to try for authentication
283 :param float timeout:
284 an optional timeout (in seconds) for the TCP connect
285 :param bool allow_agent:
286 set to False to disable connecting to the SSH agent
287 :param bool look_for_keys:
288 set to False to disable searching for discoverable private key
289 files in ``~/.ssh/``
290 :param bool compress: set to True to turn on compression
291 :param socket sock:
292 an open socket or socket-like object (such as a `.Channel`) to use
293 for communication to the target host
294 :param float banner_timeout: an optional timeout (in seconds) to wait
295 for the SSH banner to be presented.
296 :param float auth_timeout: an optional timeout (in seconds) to wait for
297 an authentication response.
298 :param float channel_timeout: an optional timeout (in seconds) to wait
299 for a channel open response.
300 :param dict disabled_algorithms:
301 an optional dict passed directly to `.Transport` and its keyword
302 argument of the same name.
303 :param transport_factory:
304 an optional callable which is handed a subset of the constructor
305 arguments (primarily those related to the socket and algorithm
306 selection) and generates a `.Transport` instance to be used by this
307 client. Defaults to `.Transport.__init__`.
308 :param auth_strategy:
309 an optional instance of `.AuthStrategy`, triggering use of this
310 newer authentication mechanism instead of SSHClient's legacy auth
311 method.
313 .. warning::
314 This parameter is **incompatible** with all other
315 authentication-related parameters (such as, but not limited to,
316 ``password``, ``key_filename`` and ``allow_agent``) and will
317 trigger an exception if given alongside them.
319 :returns:
320 `.AuthResult` if ``auth_strategy`` is non-``None``; otherwise,
321 returns ``None``.
323 :raises BadHostKeyException:
324 if the server's host key could not be verified.
325 :raises AuthenticationException:
326 if authentication failed.
327 :raises UnableToAuthenticate:
328 if authentication failed (when ``auth_strategy`` is non-``None``;
329 and note that this is a subclass of ``AuthenticationException``).
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`` argument.
343 .. versionchanged:: 2.4
344 Added the ``passphrase`` argument.
345 .. versionchanged:: 2.6
346 Added the ``disabled_algorithms`` argument.
347 .. versionchanged:: 2.12
348 Added the ``transport_factory`` argument.
349 .. versionchanged:: 3.2
350 Added the ``auth_strategy`` argument.
351 """
352 if not sock:
353 errors = {}
354 # Try multiple possible address families (e.g. IPv4 vs IPv6)
355 to_try = list(self._families_and_addresses(hostname, port))
356 for af, addr in to_try:
357 try:
358 sock = socket.socket(af, socket.SOCK_STREAM)
359 if timeout is not None:
360 try:
361 sock.settimeout(timeout)
362 except:
363 pass
364 sock.connect(addr)
365 # Break out of the loop on success
366 break
367 except socket.error as e:
368 # As mentioned in socket docs it is better
369 # to close sockets explicitly
370 if sock:
371 sock.close()
372 # Raise anything that isn't a straight up connection error
373 # (such as a resolution error)
374 if e.errno not in (ECONNREFUSED, EHOSTUNREACH):
375 raise
376 # Capture anything else so we know how the run looks once
377 # iteration is complete. Retain info about which attempt
378 # this was.
379 errors[addr] = e
381 # Make sure we explode usefully if no address family attempts
382 # succeeded. We've no way of knowing which error is the "right"
383 # one, so we construct a hybrid exception containing all the real
384 # ones, of a subclass that client code should still be watching for
385 # (socket.error)
386 if len(errors) == len(to_try):
387 raise NoValidConnectionsError(errors)
389 if transport_factory is None:
390 transport_factory = Transport
391 t = self._transport = transport_factory(
392 sock,
393 disabled_algorithms=disabled_algorithms,
394 )
395 t.use_compression(compress=compress)
396 if self._log_channel is not None:
397 t.set_log_channel(self._log_channel)
398 if banner_timeout is not None:
399 t.banner_timeout = banner_timeout
400 if auth_timeout is not None:
401 t.auth_timeout = auth_timeout
402 if channel_timeout is not None:
403 t.channel_timeout = channel_timeout
405 if port == SSH_PORT:
406 server_hostkey_name = hostname
407 else:
408 server_hostkey_name = "[{}]:{}".format(hostname, port)
409 our_server_keys = None
411 our_server_keys = self._system_host_keys.get(server_hostkey_name)
412 if our_server_keys is None:
413 # TODO: this is getting us the test suite _test_connection host key
414 # setup by virtue of that code doing tc.get_host_keys().add() (that
415 # method wraps self._host_keys)
416 # TODO: it should be analogous to running paramiko w/ a
417 # ~/.ssh/known_hosts file
418 our_server_keys = self._host_keys.get(server_hostkey_name)
419 if our_server_keys is not None:
420 # TODO: keytype needs to turn into one of the rsa-sha2 keys if it's
421 # an RSAKey.
422 # TODO: where is the equivalent on our server-side? hopefully the
423 # tests exercise that lol
424 # TODO: also, is it just me or does this [0] mean we literally do
425 # not actually implement real HostKeyAlgorithms agreement like
426 # ssh.c does?! eesh
427 keytype = our_server_keys.keys()[0]
428 sec_opts = t.get_security_options()
429 # TODO: clean this up a bit, but it does help some tests pass!
430 if keytype == "ssh-rsa":
431 if "rsa-sha2-512" in sec_opts.key_types:
432 keytype = "rsa-sha2-512"
433 elif "rsa-sha2-256" in sec_opts.key_types:
434 keytype = "rsa-sha2-256"
435 else:
436 # TODO: vvv
437 raise Exception(
438 "TODO: REPLACEME with appropriate exception for 'what even is this key type in your known_hosts files?'" # noqa
439 )
440 other_types = [x for x in sec_opts.key_types if x != keytype]
441 sec_opts.key_types = [keytype] + other_types
443 t.start_client(timeout=timeout)
445 server_key = t.get_remote_server_key()
446 if our_server_keys is None:
447 # will raise exception if the key is rejected
448 self._policy.missing_host_key(
449 self, server_hostkey_name, server_key
450 )
451 else:
452 # TODO: this should 'just work' but dblcheck (HostKeys will be
453 # offering an ssh-rsa-via-sha2 key as "ssh-rsa" still, and the
454 # key would be RSAKey whose .get_name would still say
455 # "ssh-rsa")
456 our_key = our_server_keys.get(server_key.get_name())
457 if our_key != server_key:
458 if our_key is None:
459 our_key = list(our_server_keys.values())[0]
460 raise BadHostKeyException(hostname, server_key, our_key)
462 if username is None:
463 username = getpass.getuser()
465 # New auth flow!
466 if auth_strategy is not None:
467 return auth_strategy.authenticate(transport=t)
469 # Old auth flow!
470 if key_filename is None:
471 key_filenames = []
472 elif isinstance(key_filename, str):
473 key_filenames = [key_filename]
474 else:
475 key_filenames = key_filename
477 self._auth(
478 username,
479 password,
480 pkey,
481 key_filenames,
482 allow_agent,
483 look_for_keys,
484 passphrase,
485 )
487 def close(self):
488 """
489 Close this SSHClient and its underlying `.Transport`.
491 This should be called anytime you are done using the client object.
493 .. warning::
494 Paramiko registers garbage collection hooks that will try to
495 automatically close connections for you, but this is not presently
496 reliable. Failure to explicitly close your client after use may
497 lead to end-of-process hangs!
498 """
499 if self._transport is None:
500 return
501 self._transport.close()
502 self._transport = None
504 if self._agent is not None:
505 self._agent.close()
506 self._agent = None
508 def exec_command(
509 self,
510 command,
511 bufsize=-1,
512 timeout=None,
513 get_pty=False,
514 environment=None,
515 ):
516 """
517 Execute a command on the SSH server. A new `.Channel` is opened and
518 the requested command is executed. The command's input and output
519 streams are returned as Python ``file``-like objects representing
520 stdin, stdout, and stderr.
522 :param str command: the command to execute
523 :param int bufsize:
524 interpreted the same way as by the built-in ``file()`` function in
525 Python
526 :param int timeout:
527 set command's channel timeout. See `.Channel.settimeout`
528 :param bool get_pty:
529 Request a pseudo-terminal from the server (default ``False``).
530 See `.Channel.get_pty`
531 :param dict environment:
532 a dict of shell environment variables, to be merged into the
533 default environment that the remote command executes within.
535 .. warning::
536 Servers may silently reject some environment variables; see the
537 warning in `.Channel.set_environment_variable` for details.
539 :return:
540 the stdin, stdout, and stderr of the executing command, as a
541 3-tuple
543 :raises: `.SSHException` -- if the server fails to execute the command
545 .. versionchanged:: 1.10
546 Added the ``get_pty`` kwarg.
547 """
548 chan = self._transport.open_session(timeout=timeout)
549 if get_pty:
550 chan.get_pty()
551 chan.settimeout(timeout)
552 if environment:
553 chan.update_environment(environment)
554 chan.exec_command(command)
555 stdin = chan.makefile_stdin("wb", bufsize)
556 stdout = chan.makefile("r", bufsize)
557 stderr = chan.makefile_stderr("r", bufsize)
558 return stdin, stdout, stderr
560 def invoke_shell(
561 self,
562 term="vt100",
563 width=80,
564 height=24,
565 width_pixels=0,
566 height_pixels=0,
567 environment=None,
568 ):
569 """
570 Start an interactive shell session on the SSH server. A new `.Channel`
571 is opened and connected to a pseudo-terminal using the requested
572 terminal type and size.
574 :param str term:
575 the terminal type to emulate (for example, ``"vt100"``)
576 :param int width: the width (in characters) of the terminal window
577 :param int height: the height (in characters) of the terminal window
578 :param int width_pixels: the width (in pixels) of the terminal window
579 :param int height_pixels: the height (in pixels) of the terminal window
580 :param dict environment: the command's environment
581 :return: a new `.Channel` connected to the remote shell
583 :raises: `.SSHException` -- if the server fails to invoke a shell
584 """
585 chan = self._transport.open_session()
586 chan.get_pty(term, width, height, width_pixels, height_pixels)
587 chan.invoke_shell()
588 return chan
590 def open_sftp(self):
591 """
592 Open an SFTP session on the SSH server.
594 :return: a new `.SFTPClient` session object
595 """
596 return self._transport.open_sftp_client()
598 def get_transport(self):
599 """
600 Return the underlying `.Transport` object for this SSH connection.
601 This can be used to perform lower-level tasks, like opening specific
602 kinds of channels.
604 :return: the `.Transport` for this connection
605 """
606 return self._transport
608 def _key_from_filepath(self, filename, klass, password):
609 """
610 Attempt to derive a `.PKey` from given string path ``filename``:
612 - If ``filename`` appears to be a cert, the matching private key is
613 loaded.
614 - Otherwise, the filename is assumed to be a private key, and the
615 matching public cert will be loaded if it exists.
616 """
617 cert_suffix = "-cert.pub"
618 # Assume privkey, not cert, by default
619 if filename.endswith(cert_suffix):
620 key_path = filename[: -len(cert_suffix)]
621 cert_path = filename
622 else:
623 key_path = filename
624 cert_path = filename + cert_suffix
625 # Blindly try the key path; if no private key, nothing will work.
626 key = klass.from_private_key_file(key_path, password)
627 # TODO: change this to 'Loading' instead of 'Trying' sometime; probably
628 # when #387 is released, since this is a critical log message users are
629 # likely testing/filtering for (bah.)
630 msg = "Trying discovered key {} in {}".format(
631 hexlify(key.get_fingerprint()), key_path
632 )
633 self._log(DEBUG, msg)
634 # Attempt to load cert if it exists.
635 if os.path.isfile(cert_path):
636 key.load_certificate(cert_path)
637 self._log(DEBUG, "Adding public certificate {}".format(cert_path))
638 return key
640 def _auth(
641 self,
642 username,
643 password,
644 pkey,
645 key_filenames,
646 allow_agent,
647 look_for_keys,
648 passphrase,
649 ):
650 """
651 Try, in order:
653 - The key(s) passed in, if one was passed in.
654 - Any key we can find through an SSH agent (if allowed).
655 - Any id_* key discoverable in ~/.ssh/ (if allowed).
656 - Plain username/password auth, if a password was given.
658 (The password might be needed to unlock a private key [if 'passphrase'
659 isn't also given], or for two-factor authentication [for which it is
660 required].)
661 """
662 saved_exception = None
663 two_factor = False
664 allowed_types = set()
665 two_factor_types = {"keyboard-interactive", "password"}
666 if passphrase is None and password is not None:
667 passphrase = password
669 if pkey is not None:
670 try:
671 self._log(
672 DEBUG,
673 "Trying SSH key {}".format(
674 hexlify(pkey.get_fingerprint())
675 ),
676 )
677 allowed_types = set(
678 self._transport.auth_publickey(username, pkey)
679 )
680 two_factor = allowed_types & two_factor_types
681 if not two_factor:
682 return
683 except SSHException as e:
684 saved_exception = e
686 if not two_factor:
687 for key_filename in key_filenames:
688 # TODO (backwards incompat): leverage PKey.from_path() if we
689 # don't end up just killing SSHClient entirely
690 for pkey_class in (RSAKey, ECDSAKey, Ed25519Key):
691 try:
692 key = self._key_from_filepath(
693 key_filename, pkey_class, passphrase
694 )
695 allowed_types = set(
696 self._transport.auth_publickey(username, key)
697 )
698 two_factor = allowed_types & two_factor_types
699 if not two_factor:
700 return
701 break
702 except SSHException as e:
703 saved_exception = e
705 if not two_factor and allow_agent:
706 if self._agent is None:
707 self._agent = Agent()
709 for key in self._agent.get_keys():
710 try:
711 id_ = hexlify(key.get_fingerprint())
712 self._log(DEBUG, "Trying SSH agent key {}".format(id_))
713 # for 2-factor auth a successfully auth'd key password
714 # will return an allowed 2fac auth method
715 allowed_types = set(
716 self._transport.auth_publickey(username, key)
717 )
718 two_factor = allowed_types & two_factor_types
719 if not two_factor:
720 return
721 break
722 except SSHException as e:
723 saved_exception = e
725 if not two_factor:
726 keyfiles = []
728 for keytype, name in [
729 (RSAKey, "rsa"),
730 (ECDSAKey, "ecdsa"),
731 (Ed25519Key, "ed25519"),
732 ]:
733 # ~/ssh/ is for windows
734 for directory in [".ssh", "ssh"]:
735 full_path = os.path.expanduser(
736 "~/{}/id_{}".format(directory, name)
737 )
738 if os.path.isfile(full_path):
739 # TODO: only do this append if below did not run
740 keyfiles.append((keytype, full_path))
741 if os.path.isfile(full_path + "-cert.pub"):
742 keyfiles.append((keytype, full_path + "-cert.pub"))
744 if not look_for_keys:
745 keyfiles = []
747 for pkey_class, filename in keyfiles:
748 try:
749 key = self._key_from_filepath(
750 filename, pkey_class, passphrase
751 )
752 # for 2-factor auth a successfully auth'd key will result
753 # in ['password']
754 allowed_types = set(
755 self._transport.auth_publickey(username, key)
756 )
757 two_factor = allowed_types & two_factor_types
758 if not two_factor:
759 return
760 break
761 except (SSHException, IOError) as e:
762 saved_exception = e
764 if password is not None:
765 try:
766 self._transport.auth_password(username, password)
767 return
768 except SSHException as e:
769 saved_exception = e
770 elif two_factor:
771 try:
772 self._transport.auth_interactive_dumb(username)
773 return
774 except SSHException as e:
775 saved_exception = e
777 # if we got an auth-failed exception earlier, re-raise it
778 if saved_exception is not None:
779 raise saved_exception
780 raise SSHException("No authentication methods available")
782 def _log(self, level, msg):
783 self._transport._log(level, msg)
786class MissingHostKeyPolicy:
787 """
788 Interface for defining the policy that `.SSHClient` should use when the
789 SSH server's hostname is not in either the system host keys or the
790 application's keys. Pre-made classes implement policies for automatically
791 adding the key to the application's `.HostKeys` object (`.AutoAddPolicy`),
792 and for automatically rejecting the key (`.RejectPolicy`).
794 This function may be used to ask the user to verify the key, for example.
795 """
797 def missing_host_key(self, client, hostname, key):
798 """
799 Called when an `.SSHClient` receives a server key for a server that
800 isn't in either the system or local `.HostKeys` object. To accept
801 the key, simply return. To reject, raised an exception (which will
802 be passed to the calling application).
803 """
804 pass
807class AutoAddPolicy(MissingHostKeyPolicy):
808 """
809 Policy for automatically adding the hostname and new host key to the
810 local `.HostKeys` object, and saving it. This is used by `.SSHClient`.
811 """
813 def missing_host_key(self, client, hostname, key):
814 client._host_keys.add(hostname, key.get_name(), key)
815 if client._host_keys_filename is not None:
816 client.save_host_keys(client._host_keys_filename)
817 client._log(
818 DEBUG,
819 "Adding {} host key for {}: {}".format(
820 key.get_name(), hostname, hexlify(key.get_fingerprint())
821 ),
822 )
825class RejectPolicy(MissingHostKeyPolicy):
826 """
827 Policy for automatically rejecting the unknown hostname & key. This is
828 used by `.SSHClient`.
829 """
831 def missing_host_key(self, client, hostname, key):
832 client._log(
833 DEBUG,
834 "Rejecting {} host key for {}: {}".format(
835 key.get_name(), hostname, hexlify(key.get_fingerprint())
836 ),
837 )
838 raise SSHException(
839 "Server {!r} not found in known_hosts".format(hostname)
840 )
843class WarningPolicy(MissingHostKeyPolicy):
844 """
845 Policy for logging a Python-style warning for an unknown host key, but
846 accepting it. This is used by `.SSHClient`.
847 """
849 def missing_host_key(self, client, hostname, key):
850 warnings.warn(
851 "Unknown {} host key for {}: {}".format(
852 key.get_name(), hostname, hexlify(key.get_fingerprint())
853 )
854 )