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"""
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 auth_strategy=None,
242 ):
243 """
244 Connect to an SSH server and authenticate to it. The server's host key
245 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 hostname
247 is not found in either set of host keys, the missing host key policy
248 is used (see `set_missing_host_key_policy`). The default policy is
249 to reject the key and raise an `.SSHException`.
251 Authentication is attempted in the following order of priority:
253 - The ``pkey`` or ``key_filename`` passed in (if any)
255 - ``key_filename`` may contain OpenSSH public certificate paths
256 as well as regular private-key paths; when files ending in
257 ``-cert.pub`` are found, they are assumed to match a private
258 key, and both components will be loaded. (The private key
259 itself does *not* need to be listed in ``key_filename`` for
260 this to occur - *just* the certificate.)
262 - Any key we can find through an SSH agent
263 - Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in
264 ``~/.ssh/``
266 - When OpenSSH-style public certificates exist that match an
267 existing such private key (so e.g. one has ``id_rsa`` and
268 ``id_rsa-cert.pub``) the certificate will be loaded alongside
269 the private key and used for authentication.
271 - Plain username/password auth, if a password was given
273 If a private key requires a password to unlock it, and a password is
274 passed in, that password will be used to attempt to unlock the key.
276 :param str hostname: the server to connect to
277 :param int port: the server port to connect to
278 :param str username:
279 the username to authenticate as (defaults to the current local
280 username)
281 :param str password:
282 Used for password authentication; is also used for private key
283 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 authentication
287 :param str key_filename:
288 the filename, or list of filenames, of optional private key(s)
289 and/or certs to try for authentication
290 :param float timeout:
291 an optional timeout (in seconds) for the TCP connect
292 :param bool allow_agent:
293 set to False to disable connecting to the SSH agent
294 :param bool look_for_keys:
295 set to False to disable searching for discoverable private key
296 files in ``~/.ssh/``
297 :param bool compress: set to True to turn on compression
298 :param socket sock:
299 an open socket or socket-like object (such as a `.Channel`) to use
300 for communication to the target host
301 :param bool gss_auth:
302 ``True`` if you want to use GSS-API authentication
303 :param bool gss_kex:
304 Perform GSS-API Key Exchange and user authentication
305 :param bool gss_deleg_creds: Delegate GSS-API client credentials or not
306 :param str gss_host:
307 The targets name in the kerberos database. default: hostname
308 :param bool gss_trust_dns:
309 Indicates whether or not the DNS is trusted to securely
310 canonicalize the name of the host being connected to (default
311 ``True``).
312 :param float banner_timeout: an optional timeout (in seconds) to wait
313 for the SSH banner to be presented.
314 :param float auth_timeout: an optional timeout (in seconds) to wait for
315 an authentication response.
316 :param float channel_timeout: an optional timeout (in seconds) to wait
317 for a channel open response.
318 :param dict disabled_algorithms:
319 an optional dict passed directly to `.Transport` and its keyword
320 argument of the same name.
321 :param transport_factory:
322 an optional callable which is handed a subset of the constructor
323 arguments (primarily those related to the socket, GSS
324 functionality, and algorithm selection) and generates a
325 `.Transport` instance to be used by this client. Defaults to
326 `.Transport.__init__`.
327 :param auth_strategy:
328 an optional instance of `.AuthStrategy`, triggering use of this
329 newer authentication mechanism instead of SSHClient's legacy auth
330 method.
332 .. warning::
333 This parameter is **incompatible** with all other
334 authentication-related parameters (such as, but not limited to,
335 ``password``, ``key_filename`` and ``allow_agent``) and will
336 trigger an exception if given alongside them.
338 :returns:
339 `.AuthResult` if ``auth_strategy`` is non-``None``; otherwise,
340 returns ``None``.
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 or
351 host-unreachable) occurred while connecting.
352 :raises NoValidConnectionsError:
353 if all valid connection targets for the requested hostname (eg IPv4
354 and IPv6) yielded connection-refused or host-unreachable socket
355 errors.
356 :raises SSHException:
357 if there was any other error connecting or establishing an SSH
358 session.
360 .. versionchanged:: 1.15
361 Added the ``banner_timeout``, ``gss_auth``, ``gss_kex``,
362 ``gss_deleg_creds`` and ``gss_host`` arguments.
363 .. versionchanged:: 2.3
364 Added the ``gss_trust_dns`` argument.
365 .. versionchanged:: 2.4
366 Added the ``passphrase`` argument.
367 .. versionchanged:: 2.6
368 Added the ``disabled_algorithms`` argument.
369 .. versionchanged:: 2.12
370 Added the ``transport_factory`` argument.
371 .. versionchanged:: 3.2
372 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 pass
386 sock.connect(addr)
387 # Break out of the loop on success
388 break
389 except socket.error as e:
390 # As mentioned in socket docs it is better
391 # to close sockets explicitly
392 if sock:
393 sock.close()
394 # Raise anything that isn't a straight up connection error
395 # (such as a resolution error)
396 if e.errno not in (ECONNREFUSED, EHOSTUNREACH):
397 raise
398 # Capture anything else so we know how the run looks once
399 # iteration is complete. Retain info about which attempt
400 # this was.
401 errors[addr] = e
403 # Make sure we explode usefully if no address family attempts
404 # succeeded. We've no way of knowing which error is the "right"
405 # one, so we construct a hybrid exception containing all the real
406 # ones, of a subclass that client code should still be watching for
407 # (socket.error)
408 if len(errors) == len(to_try):
409 raise NoValidConnectionsError(errors)
411 if transport_factory is None:
412 transport_factory = Transport
413 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_timeout
431 if auth_timeout is not None:
432 t.auth_timeout = auth_timeout
433 if channel_timeout is not None:
434 t.channel_timeout = channel_timeout
436 if port == SSH_PORT:
437 server_hostkey_name = hostname
438 else:
439 server_hostkey_name = "[{}]:{}".format(hostname, port)
440 our_server_keys = None
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_types
451 t.start_client(timeout=timeout)
453 # If GSS-API Key Exchange is performed we are not required to check the
454 # host key, because the host is authenticated via GSS-API / SSPI as
455 # 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 rejected
460 self._policy.missing_host_key(
461 self, server_hostkey_name, server_key
462 )
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)
470 if username is None:
471 username = getpass.getuser()
473 # New auth flow!
474 if auth_strategy is not None:
475 return auth_strategy.authenticate(transport=t)
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_filename
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 )
499 def close(self):
500 """
501 Close this SSHClient and its underlying `.Transport`.
503 This should be called anytime you are done using the client object.
505 .. warning::
506 Paramiko registers garbage collection hooks that will try to
507 automatically close connections for you, but this is not presently
508 reliable. Failure to explicitly close your client after use may
509 lead to end-of-process hangs!
510 """
511 if self._transport is None:
512 return
513 self._transport.close()
514 self._transport = None
516 if self._agent is not None:
517 self._agent.close()
518 self._agent = None
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 and
530 the requested command is executed. The command's input and output
531 streams are returned as Python ``file``-like objects representing
532 stdin, stdout, and stderr.
534 :param str command: the command to execute
535 :param int bufsize:
536 interpreted the same way as by the built-in ``file()`` function in
537 Python
538 :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 the
545 default environment that the remote command executes within.
547 .. warning::
548 Servers may silently reject some environment variables; see the
549 warning in `.Channel.set_environment_variable` for details.
551 :return:
552 the stdin, stdout, and stderr of the executing command, as a
553 3-tuple
555 :raises: `.SSHException` -- if the server fails to execute the command
557 .. versionchanged:: 1.10
558 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, stderr
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 requested
584 terminal type and size.
586 :param str term:
587 the terminal type to emulate (for example, ``"vt100"``)
588 :param int width: the width (in characters) of the terminal window
589 :param int height: the height (in characters) of the terminal window
590 :param int width_pixels: the width (in pixels) of the terminal window
591 :param int height_pixels: the height (in pixels) of the terminal window
592 :param dict environment: the command's environment
593 :return: a new `.Channel` connected to the remote shell
595 :raises: `.SSHException` -- if the server fails to invoke a shell
596 """
597 chan = self._transport.open_session()
598 chan.get_pty(term, width, height, width_pixels, height_pixels)
599 chan.invoke_shell()
600 return chan
602 def open_sftp(self):
603 """
604 Open an SFTP session on the SSH server.
606 :return: a new `.SFTPClient` session object
607 """
608 return self._transport.open_sftp_client()
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 specific
614 kinds of channels.
616 :return: the `.Transport` for this connection
617 """
618 return self._transport
620 def _key_from_filepath(self, filename, klass, password):
621 """
622 Attempt to derive a `.PKey` from given string path ``filename``:
624 - If ``filename`` appears to be a cert, the matching private key is
625 loaded.
626 - Otherwise, the filename is assumed to be a private key, and the
627 matching public cert will be loaded if it exists.
628 """
629 cert_suffix = "-cert.pub"
630 # Assume privkey, not cert, by default
631 if filename.endswith(cert_suffix):
632 key_path = filename[: -len(cert_suffix)]
633 cert_path = filename
634 else:
635 key_path = filename
636 cert_path = filename + cert_suffix
637 # 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; probably
640 # when #387 is released, since this is a critical log message users are
641 # likely testing/filtering for (bah.)
642 msg = "Trying discovered key {} in {}".format(
643 hexlify(key.get_fingerprint()), key_path
644 )
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 key
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:
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.
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 is
677 required].)
678 """
679 saved_exception = None
680 two_factor = False
681 allowed_types = set()
682 two_factor_types = {"keyboard-interactive", "password"}
683 if passphrase is None and password is not None:
684 passphrase = password
686 # If GSS-API support and GSS-PI Key Exchange was performed, we attempt
687 # authentication with gssapi-keyex.
688 if gss_kex and self._transport.gss_kex_used:
689 try:
690 self._transport.auth_gssapi_keyex(username)
691 return
692 except Exception as e:
693 saved_exception = e
695 # Try GSS-API authentication (gssapi-with-mic) only if GSS-API Key
696 # Exchange is not performed, because if we use GSS-API for the key
697 # exchange, there is already a fully established GSS-API context, so
698 # 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_creds
703 )
704 except Exception as e:
705 saved_exception = e
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_types
719 if not two_factor:
720 return
721 except SSHException as e:
722 saved_exception = e
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 just
727 # killing SSHClient entirely
728 for pkey_class in (RSAKey, DSSKey, ECDSAKey, Ed25519Key):
729 try:
730 key = self._key_from_filepath(
731 key_filename, pkey_class, passphrase
732 )
733 allowed_types = set(
734 self._transport.auth_publickey(username, key)
735 )
736 two_factor = allowed_types & two_factor_types
737 if not two_factor:
738 return
739 break
740 except SSHException as e:
741 saved_exception = e
743 if not two_factor and allow_agent:
744 if self._agent is None:
745 self._agent = Agent()
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 password
752 # will return an allowed 2fac auth method
753 allowed_types = set(
754 self._transport.auth_publickey(username, key)
755 )
756 two_factor = allowed_types & two_factor_types
757 if not two_factor:
758 return
759 break
760 except SSHException as e:
761 saved_exception = e
763 if not two_factor:
764 keyfiles = []
766 for keytype, name in [
767 (RSAKey, "rsa"),
768 (DSSKey, "dsa"),
769 (ECDSAKey, "ecdsa"),
770 (Ed25519Key, "ed25519"),
771 ]:
772 # ~/ssh/ is for windows
773 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 run
779 keyfiles.append((keytype, full_path))
780 if os.path.isfile(full_path + "-cert.pub"):
781 keyfiles.append((keytype, full_path + "-cert.pub"))
783 if not look_for_keys:
784 keyfiles = []
786 for pkey_class, filename in keyfiles:
787 try:
788 key = self._key_from_filepath(
789 filename, pkey_class, passphrase
790 )
791 # for 2-factor auth a successfully auth'd key will result
792 # in ['password']
793 allowed_types = set(
794 self._transport.auth_publickey(username, key)
795 )
796 two_factor = allowed_types & two_factor_types
797 if not two_factor:
798 return
799 break
800 except (SSHException, IOError) as e:
801 saved_exception = e
803 if password is not None:
804 try:
805 self._transport.auth_password(username, password)
806 return
807 except SSHException as e:
808 saved_exception = e
809 elif two_factor:
810 try:
811 self._transport.auth_interactive_dumb(username)
812 return
813 except SSHException as e:
814 saved_exception = e
816 # if we got an auth-failed exception earlier, re-raise it
817 if saved_exception is not None:
818 raise saved_exception
819 raise SSHException("No authentication methods available")
821 def _log(self, level, msg):
822 self._transport._log(level, msg)
825class MissingHostKeyPolicy:
826 """
827 Interface for defining the policy that `.SSHClient` should use when the
828 SSH server's hostname is not in either the system host keys or the
829 application's keys. Pre-made classes implement policies for automatically
830 adding the key to the application's `.HostKeys` object (`.AutoAddPolicy`),
831 and for automatically rejecting the key (`.RejectPolicy`).
833 This function may be used to ask the user to verify the key, for example.
834 """
836 def missing_host_key(self, client, hostname, key):
837 """
838 Called when an `.SSHClient` receives a server key for a server that
839 isn't in either the system or local `.HostKeys` object. To accept
840 the key, simply return. To reject, raised an exception (which will
841 be passed to the calling application).
842 """
843 pass
846class AutoAddPolicy(MissingHostKeyPolicy):
847 """
848 Policy for automatically adding the hostname and new host key to the
849 local `.HostKeys` object, and saving it. This is used by `.SSHClient`.
850 """
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 )
864class RejectPolicy(MissingHostKeyPolicy):
865 """
866 Policy for automatically rejecting the unknown hostname & key. This is
867 used by `.SSHClient`.
868 """
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 )
882class WarningPolicy(MissingHostKeyPolicy):
883 """
884 Policy for logging a Python-style warning for an unknown host key, but
885 accepting it. This is used by `.SSHClient`.
886 """
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 )