codekingpro/portable-devtools
114k
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 the6# terms of the GNU Lesser General Public License as published by the Free7# 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 ANY11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR12# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more13# details.14#15# You should have received a copy of the GNU Lesser General Public License16# along with Paramiko; if not, write to the Free Software Foundation, Inc.,17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.18 19"""20SSH client & key policies21"""22 23from binascii import hexlify24import getpass25import inspect26import os27import socket28import warnings29from errno import ECONNREFUSED, EHOSTUNREACH30 31from paramiko.agent import Agent32from paramiko.common import DEBUG33from paramiko.config import SSH_PORT34from paramiko.dsskey import DSSKey35from paramiko.ecdsakey import ECDSAKey36from paramiko.ed25519key import Ed25519Key37from paramiko.hostkeys import HostKeys38from paramiko.rsakey import RSAKey39from paramiko.ssh_exception import (40 SSHException,41 BadHostKeyException,42 NoValidConnectionsError,43)44from paramiko.transport import Transport45from paramiko.util import ClosingContextManager46 47 48class SSHClient(ClosingContextManager):49 """50 A high-level representation of a session with an SSH server. This class51 wraps `.Transport`, `.Channel`, and `.SFTPClient` to take care of most52 aspects of authenticating and opening channels. A typical use case is::53 54 client = SSHClient()55 client.load_system_host_keys()56 client.connect('ssh.example.com')57 stdin, stdout, stderr = client.exec_command('ls -l')58 59 You may pass in explicit overrides for authentication and server host key60 checking. The default mechanism is to try to use local key files or an61 SSH agent (if one is running).62 63 Instances of this class may be used as context managers.64 65 .. versionadded:: 1.666 """67 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 = None75 self._log_channel = None76 self._policy = RejectPolicy()77 self._transport = None78 self._agent = None79 80 def load_system_host_keys(self, filename=None):81 """82 Load host keys from a system (read-only) file. Host keys read with83 this method will not be saved back by `save_host_keys`.84 85 This method can be called multiple times. Each new set of host keys86 will be merged with the existing set (new replacing old if there are87 conflicts).88 89 If ``filename`` is left as ``None``, an attempt will be made to read90 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 is92 probably only useful on posix.93 94 :param str filename: the filename to read, or ``None``95 96 :raises: ``IOError`` --97 if a filename was provided and the file could not be read98 """99 if filename is None:100 # try the user's .ssh key file, and mask exceptions101 filename = os.path.expanduser("~/.ssh/known_hosts")102 try:103 self._system_host_keys.load(filename)104 except IOError:105 pass106 return107 self._system_host_keys.load(filename)108 109 def load_host_keys(self, filename):110 """111 Load host keys from a local host-key file. Host keys read with this112 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 and115 saves them, when connecting to a previously-unknown server.116 117 This method can be called multiple times. Each new set of host keys118 will be merged with the existing set (new replacing old if there are119 conflicts). When automatically saving, the last hostname is used.120 121 :param str filename: the filename to read122 123 :raises: ``IOError`` -- if the filename could not be read124 """125 self._host_keys_filename = filename126 self._host_keys.load(filename)127 128 def save_host_keys(self, filename):129 """130 Save the host keys back to a file. Only the host keys loaded with131 `load_host_keys` (plus any added directly) will be saved -- not any132 host keys loaded with `load_system_host_keys`.133 134 :param str filename: the filename to save to135 136 :raises: ``IOError`` -- if the file could not be written137 """138 139 # update local host keys from file (in case other SSH clients140 # 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)143 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 )152 153 def get_host_keys(self):154 """155 Get the local `.HostKeys` object. This can be used to examine the156 local host keys or change them.157 158 :return: the local host keys as a `.HostKeys` object.159 """160 return self._host_keys161 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.166 167 :param str name: new channel name for logging168 """169 self._log_channel = name170 171 def set_missing_host_key_policy(self, policy):172 """173 Set policy to use when connecting to servers without a known host key.174 175 Specifically:176 177 * A **policy** is a "policy class" (or instance thereof), namely some178 subclass of `.MissingHostKeyPolicy` such as `.RejectPolicy` (the179 default), `.AutoAddPolicy`, `.WarningPolicy`, or a user-created180 subclass.181 * A host key is **known** when it appears in the client object's cached182 host keys structures (those manipulated by `load_system_host_keys`183 and/or `load_host_keys`).184 185 :param .MissingHostKeyPolicy policy:186 the policy to use when receiving a host key from a187 previously-unknown server188 """189 if inspect.isclass(policy):190 policy = policy()191 self._policy = policy192 193 def _families_and_addresses(self, hostname, port):194 """195 Yield pairs of address families and addresses to try for connecting.196 197 :param str hostname: the server to connect to198 :param int port: the server port to connect to199 :returns: Yields an iterable of ``(family, address)`` tuples200 """201 guess = True202 addrinfos = socket.getaddrinfo(203 hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM204 )205 for (family, socktype, proto, canonname, sockaddr) in addrinfos:206 if socktype == socket.SOCK_STREAM:207 yield family, sockaddr208 guess = False209 210 # some OS like AIX don't indicate SOCK_STREAM support, so just211 # guess. :( We only do this if we did not get a single result marked212 # as socktype == SOCK_STREAM.213 if guess:214 for family, _, _, _, sockaddr in addrinfos:215 yield family, sockaddr216 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 auth_strategy=None,242 ):243 """244 Connect to an SSH server and authenticate to it. The server's host key245 is checked against the system host keys (see `load_system_host_keys`)246 and any local host keys (`load_host_keys`). If the server's hostname247 is not found in either set of host keys, the missing host key policy248 is used (see `set_missing_host_key_policy`). The default policy is249 to reject the key and raise an `.SSHException`.250 251 Authentication is attempted in the following order of priority:252 253 - The ``pkey`` or ``key_filename`` passed in (if any)254 255 - ``key_filename`` may contain OpenSSH public certificate paths256 as well as regular private-key paths; when files ending in257 ``-cert.pub`` are found, they are assumed to match a private258 key, and both components will be loaded. (The private key259 itself does *not* need to be listed in ``key_filename`` for260 this to occur - *just* the certificate.)261 262 - Any key we can find through an SSH agent263 - Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in264 ``~/.ssh/``265 266 - When OpenSSH-style public certificates exist that match an267 existing such private key (so e.g. one has ``id_rsa`` and268 ``id_rsa-cert.pub``) the certificate will be loaded alongside269 the private key and used for authentication.270 271 - Plain username/password auth, if a password was given272 273 If a private key requires a password to unlock it, and a password is274 passed in, that password will be used to attempt to unlock the key.275 276 :param str hostname: the server to connect to277 :param int port: the server port to connect to278 :param str username:279 the username to authenticate as (defaults to the current local280 username)281 :param str password:282 Used for password authentication; is also used for private key283 decryption if ``passphrase`` is not given.284 :param str passphrase:285 Used for decrypting private keys.286 :param .PKey pkey: an optional private key to use for authentication287 :param str key_filename:288 the filename, or list of filenames, of optional private key(s)289 and/or certs to try for authentication290 :param float timeout:291 an optional timeout (in seconds) for the TCP connect292 :param bool allow_agent:293 set to False to disable connecting to the SSH agent294 :param bool look_for_keys:295 set to False to disable searching for discoverable private key296 files in ``~/.ssh/``297 :param bool compress: set to True to turn on compression298 :param socket sock:299 an open socket or socket-like object (such as a `.Channel`) to use300 for communication to the target host301 :param bool gss_auth:302 ``True`` if you want to use GSS-API authentication303 :param bool gss_kex:304 Perform GSS-API Key Exchange and user authentication305 :param bool gss_deleg_creds: Delegate GSS-API client credentials or not306 :param str gss_host:307 The targets name in the kerberos database. default: hostname308 :param bool gss_trust_dns:309 Indicates whether or not the DNS is trusted to securely310 canonicalize the name of the host being connected to (default311 ``True``).312 :param float banner_timeout: an optional timeout (in seconds) to wait313 for the SSH banner to be presented.314 :param float auth_timeout: an optional timeout (in seconds) to wait for315 an authentication response.316 :param float channel_timeout: an optional timeout (in seconds) to wait317 for a channel open response.318 :param dict disabled_algorithms:319 an optional dict passed directly to `.Transport` and its keyword320 argument of the same name.321 :param transport_factory:322 an optional callable which is handed a subset of the constructor323 arguments (primarily those related to the socket, GSS324 functionality, and algorithm selection) and generates a325 `.Transport` instance to be used by this client. Defaults to326 `.Transport.__init__`.327 :param auth_strategy:328 an optional instance of `.AuthStrategy`, triggering use of this329 newer authentication mechanism instead of SSHClient's legacy auth330 method.331 332 .. warning::333 This parameter is **incompatible** with all other334 authentication-related parameters (such as, but not limited to,335 ``password``, ``key_filename`` and ``allow_agent``) and will336 trigger an exception if given alongside them.337 338 :returns:339 `.AuthResult` if ``auth_strategy`` is non-``None``; otherwise,340 returns ``None``.341 342 :raises BadHostKeyException:343 if the server's host key could not be verified.344 :raises AuthenticationException:345 if authentication failed.346 :raises UnableToAuthenticate:347 if authentication failed (when ``auth_strategy`` is non-``None``;348 and note that this is a subclass of ``AuthenticationException``).349 :raises socket.error:350 if a socket error (other than connection-refused or351 host-unreachable) occurred while connecting.352 :raises NoValidConnectionsError:353 if all valid connection targets for the requested hostname (eg IPv4354 and IPv6) yielded connection-refused or host-unreachable socket355 errors.356 :raises SSHException:357 if there was any other error connecting or establishing an SSH358 session.359 360 .. versionchanged:: 1.15361 Added the ``banner_timeout``, ``gss_auth``, ``gss_kex``,362 ``gss_deleg_creds`` and ``gss_host`` arguments.363 .. versionchanged:: 2.3364 Added the ``gss_trust_dns`` argument.365 .. versionchanged:: 2.4366 Added the ``passphrase`` argument.367 .. versionchanged:: 2.6368 Added the ``disabled_algorithms`` argument.369 .. versionchanged:: 2.12370 Added the ``transport_factory`` argument.371 .. versionchanged:: 3.2372 Added the ``auth_strategy`` argument.373 """374 if not sock:375 errors = {}376 # Try multiple possible address families (e.g. IPv4 vs IPv6)377 to_try = list(self._families_and_addresses(hostname, port))378 for af, addr in to_try:379 try:380 sock = socket.socket(af, socket.SOCK_STREAM)381 if timeout is not None:382 try:383 sock.settimeout(timeout)384 except:385 pass386 sock.connect(addr)387 # Break out of the loop on success388 break389 except socket.error as e:390 # As mentioned in socket docs it is better391 # to close sockets explicitly392 if sock:393 sock.close()394 # Raise anything that isn't a straight up connection error395 # (such as a resolution error)396 if e.errno not in (ECONNREFUSED, EHOSTUNREACH):397 raise398 # Capture anything else so we know how the run looks once399 # iteration is complete. Retain info about which attempt400 # this was.401 errors[addr] = e402 403 # Make sure we explode usefully if no address family attempts404 # succeeded. We've no way of knowing which error is the "right"405 # one, so we construct a hybrid exception containing all the real406 # ones, of a subclass that client code should still be watching for407 # (socket.error)408 if len(errors) == len(to_try):409 raise NoValidConnectionsError(errors)410 411 if transport_factory is None:412 transport_factory = Transport413 t = self._transport = transport_factory(414 sock,415 gss_kex=gss_kex,416 gss_deleg_creds=gss_deleg_creds,417 disabled_algorithms=disabled_algorithms,418 )419 t.use_compression(compress=compress)420 t.set_gss_host(421 # t.hostname may be None, but GSS-API requires a target name.422 # Therefore use hostname as fallback.423 gss_host=gss_host or hostname,424 trust_dns=gss_trust_dns,425 gssapi_requested=gss_auth or gss_kex,426 )427 if self._log_channel is not None:428 t.set_log_channel(self._log_channel)429 if banner_timeout is not None:430 t.banner_timeout = banner_timeout431 if auth_timeout is not None:432 t.auth_timeout = auth_timeout433 if channel_timeout is not None:434 t.channel_timeout = channel_timeout435 436 if port == SSH_PORT:437 server_hostkey_name = hostname438 else:439 server_hostkey_name = "[{}]:{}".format(hostname, port)440 our_server_keys = None441 442 our_server_keys = self._system_host_keys.get(server_hostkey_name)443 if our_server_keys is None:444 our_server_keys = self._host_keys.get(server_hostkey_name)445 if our_server_keys is not None:446 keytype = our_server_keys.keys()[0]447 sec_opts = t.get_security_options()448 other_types = [x for x in sec_opts.key_types if x != keytype]449 sec_opts.key_types = [keytype] + other_types450 451 t.start_client(timeout=timeout)452 453 # If GSS-API Key Exchange is performed we are not required to check the454 # host key, because the host is authenticated via GSS-API / SSPI as455 # well as our client.456 if not self._transport.gss_kex_used:457 server_key = t.get_remote_server_key()458 if our_server_keys is None:459 # will raise exception if the key is rejected460 self._policy.missing_host_key(461 self, server_hostkey_name, server_key462 )463 else:464 our_key = our_server_keys.get(server_key.get_name())465 if our_key != server_key:466 if our_key is None:467 our_key = list(our_server_keys.values())[0]468 raise BadHostKeyException(hostname, server_key, our_key)469 470 if username is None:471 username = getpass.getuser()472 473 # New auth flow!474 if auth_strategy is not None:475 return auth_strategy.authenticate(transport=t)476 477 # Old auth flow!478 if key_filename is None:479 key_filenames = []480 elif isinstance(key_filename, str):481 key_filenames = [key_filename]482 else:483 key_filenames = key_filename484 485 self._auth(486 username,487 password,488 pkey,489 key_filenames,490 allow_agent,491 look_for_keys,492 gss_auth,493 gss_kex,494 gss_deleg_creds,495 t.gss_host,496 passphrase,497 )498 499 def close(self):500 """501 Close this SSHClient and its underlying `.Transport`.502 503 This should be called anytime you are done using the client object.504 505 .. warning::506 Paramiko registers garbage collection hooks that will try to507 automatically close connections for you, but this is not presently508 reliable. Failure to explicitly close your client after use may509 lead to end-of-process hangs!510 """511 if self._transport is None:512 return513 self._transport.close()514 self._transport = None515 516 if self._agent is not None:517 self._agent.close()518 self._agent = None519 520 def exec_command(521 self,522 command,523 bufsize=-1,524 timeout=None,525 get_pty=False,526 environment=None,527 ):528 """529 Execute a command on the SSH server. A new `.Channel` is opened and530 the requested command is executed. The command's input and output531 streams are returned as Python ``file``-like objects representing532 stdin, stdout, and stderr.533 534 :param str command: the command to execute535 :param int bufsize:536 interpreted the same way as by the built-in ``file()`` function in537 Python538 :param int timeout:539 set command's channel timeout. See `.Channel.settimeout`540 :param bool get_pty:541 Request a pseudo-terminal from the server (default ``False``).542 See `.Channel.get_pty`543 :param dict environment:544 a dict of shell environment variables, to be merged into the545 default environment that the remote command executes within.546 547 .. warning::548 Servers may silently reject some environment variables; see the549 warning in `.Channel.set_environment_variable` for details.550 551 :return:552 the stdin, stdout, and stderr of the executing command, as a553 3-tuple554 555 :raises: `.SSHException` -- if the server fails to execute the command556 557 .. versionchanged:: 1.10558 Added the ``get_pty`` kwarg.559 """560 chan = self._transport.open_session(timeout=timeout)561 if get_pty:562 chan.get_pty()563 chan.settimeout(timeout)564 if environment:565 chan.update_environment(environment)566 chan.exec_command(command)567 stdin = chan.makefile_stdin("wb", bufsize)568 stdout = chan.makefile("r", bufsize)569 stderr = chan.makefile_stderr("r", bufsize)570 return stdin, stdout, stderr571 572 def invoke_shell(573 self,574 term="vt100",575 width=80,576 height=24,577 width_pixels=0,578 height_pixels=0,579 environment=None,580 ):581 """582 Start an interactive shell session on the SSH server. A new `.Channel`583 is opened and connected to a pseudo-terminal using the requested584 terminal type and size.585 586 :param str term:587 the terminal type to emulate (for example, ``"vt100"``)588 :param int width: the width (in characters) of the terminal window589 :param int height: the height (in characters) of the terminal window590 :param int width_pixels: the width (in pixels) of the terminal window591 :param int height_pixels: the height (in pixels) of the terminal window592 :param dict environment: the command's environment593 :return: a new `.Channel` connected to the remote shell594 595 :raises: `.SSHException` -- if the server fails to invoke a shell596 """597 chan = self._transport.open_session()598 chan.get_pty(term, width, height, width_pixels, height_pixels)599 chan.invoke_shell()600 return chan601 602 def open_sftp(self):603 """604 Open an SFTP session on the SSH server.605 606 :return: a new `.SFTPClient` session object607 """608 return self._transport.open_sftp_client()609 610 def get_transport(self):611 """612 Return the underlying `.Transport` object for this SSH connection.613 This can be used to perform lower-level tasks, like opening specific614 kinds of channels.615 616 :return: the `.Transport` for this connection617 """618 return self._transport619 620 def _key_from_filepath(self, filename, klass, password):621 """622 Attempt to derive a `.PKey` from given string path ``filename``:623 624 - If ``filename`` appears to be a cert, the matching private key is625 loaded.626 - Otherwise, the filename is assumed to be a private key, and the627 matching public cert will be loaded if it exists.628 """629 cert_suffix = "-cert.pub"630 # Assume privkey, not cert, by default631 if filename.endswith(cert_suffix):632 key_path = filename[: -len(cert_suffix)]633 cert_path = filename634 else:635 key_path = filename636 cert_path = filename + cert_suffix637 # Blindly try the key path; if no private key, nothing will work.638 key = klass.from_private_key_file(key_path, password)639 # TODO: change this to 'Loading' instead of 'Trying' sometime; probably640 # when #387 is released, since this is a critical log message users are641 # likely testing/filtering for (bah.)642 msg = "Trying discovered key {} in {}".format(643 hexlify(key.get_fingerprint()), key_path644 )645 self._log(DEBUG, msg)646 # Attempt to load cert if it exists.647 if os.path.isfile(cert_path):648 key.load_certificate(cert_path)649 self._log(DEBUG, "Adding public certificate {}".format(cert_path))650 return key651 652 def _auth(653 self,654 username,655 password,656 pkey,657 key_filenames,658 allow_agent,659 look_for_keys,660 gss_auth,661 gss_kex,662 gss_deleg_creds,663 gss_host,664 passphrase,665 ):666 """667 Try, in order:668 669 - The key(s) passed in, if one was passed in.670 - Any key we can find through an SSH agent (if allowed).671 - Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in ~/.ssh/672 (if allowed).673 - Plain username/password auth, if a password was given.674 675 (The password might be needed to unlock a private key [if 'passphrase'676 isn't also given], or for two-factor authentication [for which it is677 required].)678 """679 saved_exception = None680 two_factor = False681 allowed_types = set()682 two_factor_types = {"keyboard-interactive", "password"}683 if passphrase is None and password is not None:684 passphrase = password685 686 # If GSS-API support and GSS-PI Key Exchange was performed, we attempt687 # authentication with gssapi-keyex.688 if gss_kex and self._transport.gss_kex_used:689 try:690 self._transport.auth_gssapi_keyex(username)691 return692 except Exception as e:693 saved_exception = e694 695 # Try GSS-API authentication (gssapi-with-mic) only if GSS-API Key696 # Exchange is not performed, because if we use GSS-API for the key697 # exchange, there is already a fully established GSS-API context, so698 # why should we do that again?699 if gss_auth:700 try:701 return self._transport.auth_gssapi_with_mic(702 username, gss_host, gss_deleg_creds703 )704 except Exception as e:705 saved_exception = e706 707 if pkey is not None:708 try:709 self._log(710 DEBUG,711 "Trying SSH key {}".format(712 hexlify(pkey.get_fingerprint())713 ),714 )715 allowed_types = set(716 self._transport.auth_publickey(username, pkey)717 )718 two_factor = allowed_types & two_factor_types719 if not two_factor:720 return721 except SSHException as e:722 saved_exception = e723 724 if not two_factor:725 for key_filename in key_filenames:726 # TODO 4.0: leverage PKey.from_path() if we don't end up just727 # killing SSHClient entirely728 for pkey_class in (RSAKey, DSSKey, ECDSAKey, Ed25519Key):729 try:730 key = self._key_from_filepath(731 key_filename, pkey_class, passphrase732 )733 allowed_types = set(734 self._transport.auth_publickey(username, key)735 )736 two_factor = allowed_types & two_factor_types737 if not two_factor:738 return739 break740 except SSHException as e:741 saved_exception = e742 743 if not two_factor and allow_agent:744 if self._agent is None:745 self._agent = Agent()746 747 for key in self._agent.get_keys():748 try:749 id_ = hexlify(key.get_fingerprint())750 self._log(DEBUG, "Trying SSH agent key {}".format(id_))751 # for 2-factor auth a successfully auth'd key password752 # will return an allowed 2fac auth method753 allowed_types = set(754 self._transport.auth_publickey(username, key)755 )756 two_factor = allowed_types & two_factor_types757 if not two_factor:758 return759 break760 except SSHException as e:761 saved_exception = e762 763 if not two_factor:764 keyfiles = []765 766 for keytype, name in [767 (RSAKey, "rsa"),768 (DSSKey, "dsa"),769 (ECDSAKey, "ecdsa"),770 (Ed25519Key, "ed25519"),771 ]:772 # ~/ssh/ is for windows773 for directory in [".ssh", "ssh"]:774 full_path = os.path.expanduser(775 "~/{}/id_{}".format(directory, name)776 )777 if os.path.isfile(full_path):778 # TODO: only do this append if below did not run779 keyfiles.append((keytype, full_path))780 if os.path.isfile(full_path + "-cert.pub"):781 keyfiles.append((keytype, full_path + "-cert.pub"))782 783 if not look_for_keys:784 keyfiles = []785 786 for pkey_class, filename in keyfiles:787 try:788 key = self._key_from_filepath(789 filename, pkey_class, passphrase790 )791 # for 2-factor auth a successfully auth'd key will result792 # in ['password']793 allowed_types = set(794 self._transport.auth_publickey(username, key)795 )796 two_factor = allowed_types & two_factor_types797 if not two_factor:798 return799 break800 except (SSHException, IOError) as e:801 saved_exception = e802 803 if password is not None:804 try:805 self._transport.auth_password(username, password)806 return807 except SSHException as e:808 saved_exception = e809 elif two_factor:810 try:811 self._transport.auth_interactive_dumb(username)812 return813 except SSHException as e:814 saved_exception = e815 816 # if we got an auth-failed exception earlier, re-raise it817 if saved_exception is not None:818 raise saved_exception819 raise SSHException("No authentication methods available")820 821 def _log(self, level, msg):822 self._transport._log(level, msg)823 824 825class MissingHostKeyPolicy:826 """827 Interface for defining the policy that `.SSHClient` should use when the828 SSH server's hostname is not in either the system host keys or the829 application's keys. Pre-made classes implement policies for automatically830 adding the key to the application's `.HostKeys` object (`.AutoAddPolicy`),831 and for automatically rejecting the key (`.RejectPolicy`).832 833 This function may be used to ask the user to verify the key, for example.834 """835 836 def missing_host_key(self, client, hostname, key):837 """838 Called when an `.SSHClient` receives a server key for a server that839 isn't in either the system or local `.HostKeys` object. To accept840 the key, simply return. To reject, raised an exception (which will841 be passed to the calling application).842 """843 pass844 845 846class AutoAddPolicy(MissingHostKeyPolicy):847 """848 Policy for automatically adding the hostname and new host key to the849 local `.HostKeys` object, and saving it. This is used by `.SSHClient`.850 """851 852 def missing_host_key(self, client, hostname, key):853 client._host_keys.add(hostname, key.get_name(), key)854 if client._host_keys_filename is not None:855 client.save_host_keys(client._host_keys_filename)856 client._log(857 DEBUG,858 "Adding {} host key for {}: {}".format(859 key.get_name(), hostname, hexlify(key.get_fingerprint())860 ),861 )862 863 864class RejectPolicy(MissingHostKeyPolicy):865 """866 Policy for automatically rejecting the unknown hostname & key. This is867 used by `.SSHClient`.868 """869 870 def missing_host_key(self, client, hostname, key):871 client._log(872 DEBUG,873 "Rejecting {} host key for {}: {}".format(874 key.get_name(), hostname, hexlify(key.get_fingerprint())875 ),876 )877 raise SSHException(878 "Server {!r} not found in known_hosts".format(hostname)879 )880 881 882class WarningPolicy(MissingHostKeyPolicy):883 """884 Policy for logging a Python-style warning for an unknown host key, but885 accepting it. This is used by `.SSHClient`.886 """887 888 def missing_host_key(self, client, hostname, key):889 warnings.warn(890 "Unknown {} host key for {}: {}".format(891 key.get_name(), hostname, hexlify(key.get_fingerprint())892 )893 )894 