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.ecdsakey import ECDSAKey
35from paramiko.ed25519key import Ed25519Key
36from paramiko.hostkeys import HostKeys
37from paramiko.rsakey import RSAKey
38from paramiko.ssh_exception import (
39 SSHException,
40 BadHostKeyException,
41 NoValidConnectionsError,
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 gss_auth=False,
230 gss_kex=False,
231 gss_deleg_creds=True,
232 gss_host=None,
233 banner_timeout=None,
234 auth_timeout=None,
235 channel_timeout=None,
236 gss_trust_dns=True,
237 passphrase=None,
238 disabled_algorithms=None,
239 transport_factory=None,
240 auth_strategy=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_*`` keys discoverable in ``~/.ssh/``
264 - When OpenSSH-style public certificates exist that match an
265 existing such private key (so e.g. one has ``id_rsa`` and
266 ``id_rsa-cert.pub``) the certificate will be loaded alongside
267 the private key and used for authentication.
269 - Plain username/password auth, if a password was given
271 If a private key requires a password to unlock it, and a password is
272 passed in, that password will be used to attempt to unlock the key.
274 :param str hostname: the server to connect to
275 :param int port: the server port to connect to
276 :param str username:
277 the username to authenticate as (defaults to the current local
278 username)
279 :param str password:
280 Used for password authentication; is also used for private key
281 decryption if ``passphrase`` is not given.
282 :param str passphrase:
283 Used for decrypting private keys.
284 :param .PKey pkey: an optional private key to use for authentication
285 :param str key_filename:
286 the filename, or list of filenames, of optional private key(s)
287 and/or certs to try for authentication
288 :param float timeout:
289 an optional timeout (in seconds) for the TCP connect
290 :param bool allow_agent:
291 set to False to disable connecting to the SSH agent
292 :param bool look_for_keys:
293 set to False to disable searching for discoverable private key
294 files in ``~/.ssh/``
295 :param bool compress: set to True to turn on compression
296 :param socket sock:
297 an open socket or socket-like object (such as a `.Channel`) to use
298 for communication to the target host
299 :param bool gss_auth:
300 ``True`` if you want to use GSS-API authentication
301 :param bool gss_kex:
302 Perform GSS-API Key Exchange and user authentication
303 :param bool gss_deleg_creds: Delegate GSS-API client credentials or not
304 :param str gss_host:
305 The targets name in the kerberos database. default: hostname
306 :param bool gss_trust_dns:
307 Indicates whether or not the DNS is trusted to securely
308 canonicalize the name of the host being connected to (default
309 ``True``).
310 :param float banner_timeout: an optional timeout (in seconds) to wait
311 for the SSH banner to be presented.
312 :param float auth_timeout: an optional timeout (in seconds) to wait for
313 an authentication response.
314 :param float channel_timeout: an optional timeout (in seconds) to wait
315 for a channel open response.
316 :param dict disabled_algorithms:
317 an optional dict passed directly to `.Transport` and its keyword
318 argument of the same name.
319 :param transport_factory:
320 an optional callable which is handed a subset of the constructor
321 arguments (primarily those related to the socket, GSS
322 functionality, and algorithm selection) and generates a
323 `.Transport` instance to be used by this client. Defaults to
324 `.Transport.__init__`.
325 :param auth_strategy:
326 an optional instance of `.AuthStrategy`, triggering use of this
327 newer authentication mechanism instead of SSHClient's legacy auth
328 method.
330 .. warning::
331 This parameter is **incompatible** with all other
332 authentication-related parameters (such as, but not limited to,
333 ``password``, ``key_filename`` and ``allow_agent``) and will
334 trigger an exception if given alongside them.
336 :returns:
337 `.AuthResult` if ``auth_strategy`` is non-``None``; otherwise,
338 returns ``None``.
340 :raises BadHostKeyException:
341 if the server's host key could not be verified.
342 :raises AuthenticationException:
343 if authentication failed.
344 :raises UnableToAuthenticate:
345 if authentication failed (when ``auth_strategy`` is non-``None``;
346 and note that this is a subclass of ``AuthenticationException``).
347 :raises socket.error:
348 if a socket error (other than connection-refused or
349 host-unreachable) occurred while connecting.
350 :raises NoValidConnectionsError:
351 if all valid connection targets for the requested hostname (eg IPv4
352 and IPv6) yielded connection-refused or host-unreachable socket
353 errors.
354 :raises SSHException:
355 if there was any other error connecting or establishing an SSH
356 session.
358 .. versionchanged:: 1.15
359 Added the ``banner_timeout``, ``gss_auth``, ``gss_kex``,
360 ``gss_deleg_creds`` and ``gss_host`` arguments.
361 .. versionchanged:: 2.3
362 Added the ``gss_trust_dns`` argument.
363 .. versionchanged:: 2.4
364 Added the ``passphrase`` argument.
365 .. versionchanged:: 2.6
366 Added the ``disabled_algorithms`` argument.
367 .. versionchanged:: 2.12
368 Added the ``transport_factory`` argument.
369 .. versionchanged:: 3.2
370 Added the ``auth_strategy`` argument.
371 """
372 if not sock:
373 errors = {}
374 # Try multiple possible address families (e.g. IPv4 vs IPv6)
375 to_try = list(self._families_and_addresses(hostname, port))
376 for af, addr in to_try:
377 try:
378 sock = socket.socket(af, socket.SOCK_STREAM)
379 if timeout is not None:
380 try:
381 sock.settimeout(timeout)
382 except:
383 pass
384 sock.connect(addr)
385 # Break out of the loop on success
386 break
387 except socket.error as e:
388 # As mentioned in socket docs it is better
389 # to close sockets explicitly
390 if sock:
391 sock.close()
392 # Raise anything that isn't a straight up connection error
393 # (such as a resolution error)
394 if e.errno not in (ECONNREFUSED, EHOSTUNREACH):
395 raise
396 # Capture anything else so we know how the run looks once
397 # iteration is complete. Retain info about which attempt
398 # this was.
399 errors[addr] = e
401 # Make sure we explode usefully if no address family attempts
402 # succeeded. We've no way of knowing which error is the "right"
403 # one, so we construct a hybrid exception containing all the real
404 # ones, of a subclass that client code should still be watching for
405 # (socket.error)
406 if len(errors) == len(to_try):
407 raise NoValidConnectionsError(errors)
409 if transport_factory is None:
410 transport_factory = Transport
411 t = self._transport = transport_factory(
412 sock,
413 gss_kex=gss_kex,
414 gss_deleg_creds=gss_deleg_creds,
415 disabled_algorithms=disabled_algorithms,
416 )
417 t.use_compression(compress=compress)
418 t.set_gss_host(
419 # t.hostname may be None, but GSS-API requires a target name.
420 # Therefore use hostname as fallback.
421 gss_host=gss_host or hostname,
422 trust_dns=gss_trust_dns,
423 gssapi_requested=gss_auth or gss_kex,
424 )
425 if self._log_channel is not None:
426 t.set_log_channel(self._log_channel)
427 if banner_timeout is not None:
428 t.banner_timeout = banner_timeout
429 if auth_timeout is not None:
430 t.auth_timeout = auth_timeout
431 if channel_timeout is not None:
432 t.channel_timeout = channel_timeout
434 if port == SSH_PORT:
435 server_hostkey_name = hostname
436 else:
437 server_hostkey_name = "[{}]:{}".format(hostname, port)
438 our_server_keys = None
440 our_server_keys = self._system_host_keys.get(server_hostkey_name)
441 if our_server_keys is None:
442 our_server_keys = self._host_keys.get(server_hostkey_name)
443 if our_server_keys is not None:
444 keytype = our_server_keys.keys()[0]
445 sec_opts = t.get_security_options()
446 other_types = [x for x in sec_opts.key_types if x != keytype]
447 sec_opts.key_types = [keytype] + other_types
449 t.start_client(timeout=timeout)
451 # If GSS-API Key Exchange is performed we are not required to check the
452 # host key, because the host is authenticated via GSS-API / SSPI as
453 # well as our client.
454 if not self._transport.gss_kex_used:
455 server_key = t.get_remote_server_key()
456 if our_server_keys is None:
457 # will raise exception if the key is rejected
458 self._policy.missing_host_key(
459 self, server_hostkey_name, server_key
460 )
461 else:
462 our_key = our_server_keys.get(server_key.get_name())
463 if our_key != server_key:
464 if our_key is None:
465 our_key = list(our_server_keys.values())[0]
466 raise BadHostKeyException(hostname, server_key, our_key)
468 if username is None:
469 username = getpass.getuser()
471 # New auth flow!
472 if auth_strategy is not None:
473 return auth_strategy.authenticate(transport=t)
475 # Old auth flow!
476 if key_filename is None:
477 key_filenames = []
478 elif isinstance(key_filename, str):
479 key_filenames = [key_filename]
480 else:
481 key_filenames = key_filename
483 self._auth(
484 username,
485 password,
486 pkey,
487 key_filenames,
488 allow_agent,
489 look_for_keys,
490 gss_auth,
491 gss_kex,
492 gss_deleg_creds,
493 t.gss_host,
494 passphrase,
495 )
497 def close(self):
498 """
499 Close this SSHClient and its underlying `.Transport`.
501 This should be called anytime you are done using the client object.
503 .. warning::
504 Paramiko registers garbage collection hooks that will try to
505 automatically close connections for you, but this is not presently
506 reliable. Failure to explicitly close your client after use may
507 lead to end-of-process hangs!
508 """
509 if self._transport is None:
510 return
511 self._transport.close()
512 self._transport = None
514 if self._agent is not None:
515 self._agent.close()
516 self._agent = None
518 def exec_command(
519 self,
520 command,
521 bufsize=-1,
522 timeout=None,
523 get_pty=False,
524 environment=None,
525 ):
526 """
527 Execute a command on the SSH server. A new `.Channel` is opened and
528 the requested command is executed. The command's input and output
529 streams are returned as Python ``file``-like objects representing
530 stdin, stdout, and stderr.
532 :param str command: the command to execute
533 :param int bufsize:
534 interpreted the same way as by the built-in ``file()`` function in
535 Python
536 :param int timeout:
537 set command's channel timeout. See `.Channel.settimeout`
538 :param bool get_pty:
539 Request a pseudo-terminal from the server (default ``False``).
540 See `.Channel.get_pty`
541 :param dict environment:
542 a dict of shell environment variables, to be merged into the
543 default environment that the remote command executes within.
545 .. warning::
546 Servers may silently reject some environment variables; see the
547 warning in `.Channel.set_environment_variable` for details.
549 :return:
550 the stdin, stdout, and stderr of the executing command, as a
551 3-tuple
553 :raises: `.SSHException` -- if the server fails to execute the command
555 .. versionchanged:: 1.10
556 Added the ``get_pty`` kwarg.
557 """
558 chan = self._transport.open_session(timeout=timeout)
559 if get_pty:
560 chan.get_pty()
561 chan.settimeout(timeout)
562 if environment:
563 chan.update_environment(environment)
564 chan.exec_command(command)
565 stdin = chan.makefile_stdin("wb", bufsize)
566 stdout = chan.makefile("r", bufsize)
567 stderr = chan.makefile_stderr("r", bufsize)
568 return stdin, stdout, stderr
570 def invoke_shell(
571 self,
572 term="vt100",
573 width=80,
574 height=24,
575 width_pixels=0,
576 height_pixels=0,
577 environment=None,
578 ):
579 """
580 Start an interactive shell session on the SSH server. A new `.Channel`
581 is opened and connected to a pseudo-terminal using the requested
582 terminal type and size.
584 :param str term:
585 the terminal type to emulate (for example, ``"vt100"``)
586 :param int width: the width (in characters) of the terminal window
587 :param int height: the height (in characters) of the terminal window
588 :param int width_pixels: the width (in pixels) of the terminal window
589 :param int height_pixels: the height (in pixels) of the terminal window
590 :param dict environment: the command's environment
591 :return: a new `.Channel` connected to the remote shell
593 :raises: `.SSHException` -- if the server fails to invoke a shell
594 """
595 chan = self._transport.open_session()
596 chan.get_pty(term, width, height, width_pixels, height_pixels)
597 chan.invoke_shell()
598 return chan
600 def open_sftp(self):
601 """
602 Open an SFTP session on the SSH server.
604 :return: a new `.SFTPClient` session object
605 """
606 return self._transport.open_sftp_client()
608 def get_transport(self):
609 """
610 Return the underlying `.Transport` object for this SSH connection.
611 This can be used to perform lower-level tasks, like opening specific
612 kinds of channels.
614 :return: the `.Transport` for this connection
615 """
616 return self._transport
618 def _key_from_filepath(self, filename, klass, password):
619 """
620 Attempt to derive a `.PKey` from given string path ``filename``:
622 - If ``filename`` appears to be a cert, the matching private key is
623 loaded.
624 - Otherwise, the filename is assumed to be a private key, and the
625 matching public cert will be loaded if it exists.
626 """
627 cert_suffix = "-cert.pub"
628 # Assume privkey, not cert, by default
629 if filename.endswith(cert_suffix):
630 key_path = filename[: -len(cert_suffix)]
631 cert_path = filename
632 else:
633 key_path = filename
634 cert_path = filename + cert_suffix
635 # Blindly try the key path; if no private key, nothing will work.
636 key = klass.from_private_key_file(key_path, password)
637 # TODO: change this to 'Loading' instead of 'Trying' sometime; probably
638 # when #387 is released, since this is a critical log message users are
639 # likely testing/filtering for (bah.)
640 msg = "Trying discovered key {} in {}".format(
641 hexlify(key.get_fingerprint()), key_path
642 )
643 self._log(DEBUG, msg)
644 # Attempt to load cert if it exists.
645 if os.path.isfile(cert_path):
646 key.load_certificate(cert_path)
647 self._log(DEBUG, "Adding public certificate {}".format(cert_path))
648 return key
650 def _auth(
651 self,
652 username,
653 password,
654 pkey,
655 key_filenames,
656 allow_agent,
657 look_for_keys,
658 gss_auth,
659 gss_kex,
660 gss_deleg_creds,
661 gss_host,
662 passphrase,
663 ):
664 """
665 Try, in order:
667 - The key(s) passed in, if one was passed in.
668 - Any key we can find through an SSH agent (if allowed).
669 - Any id_* key discoverable in ~/.ssh/ (if allowed).
670 - Plain username/password auth, if a password was given.
672 (The password might be needed to unlock a private key [if 'passphrase'
673 isn't also given], or for two-factor authentication [for which it is
674 required].)
675 """
676 saved_exception = None
677 two_factor = False
678 allowed_types = set()
679 two_factor_types = {"keyboard-interactive", "password"}
680 if passphrase is None and password is not None:
681 passphrase = password
683 # If GSS-API support and GSS-PI Key Exchange was performed, we attempt
684 # authentication with gssapi-keyex.
685 if gss_kex and self._transport.gss_kex_used:
686 try:
687 self._transport.auth_gssapi_keyex(username)
688 return
689 except Exception as e:
690 saved_exception = e
692 # Try GSS-API authentication (gssapi-with-mic) only if GSS-API Key
693 # Exchange is not performed, because if we use GSS-API for the key
694 # exchange, there is already a fully established GSS-API context, so
695 # why should we do that again?
696 if gss_auth:
697 try:
698 return self._transport.auth_gssapi_with_mic(
699 username, gss_host, gss_deleg_creds
700 )
701 except Exception as e:
702 saved_exception = e
704 if pkey is not None:
705 try:
706 self._log(
707 DEBUG,
708 "Trying SSH key {}".format(
709 hexlify(pkey.get_fingerprint())
710 ),
711 )
712 allowed_types = set(
713 self._transport.auth_publickey(username, pkey)
714 )
715 two_factor = allowed_types & two_factor_types
716 if not two_factor:
717 return
718 except SSHException as e:
719 saved_exception = e
721 if not two_factor:
722 for key_filename in key_filenames:
723 # TODO 4.0: leverage PKey.from_path() if we don't end up just
724 # killing SSHClient entirely
725 for pkey_class in (RSAKey, ECDSAKey, Ed25519Key):
726 try:
727 key = self._key_from_filepath(
728 key_filename, pkey_class, passphrase
729 )
730 allowed_types = set(
731 self._transport.auth_publickey(username, key)
732 )
733 two_factor = allowed_types & two_factor_types
734 if not two_factor:
735 return
736 break
737 except SSHException as e:
738 saved_exception = e
740 if not two_factor and allow_agent:
741 if self._agent is None:
742 self._agent = Agent()
744 for key in self._agent.get_keys():
745 try:
746 id_ = hexlify(key.get_fingerprint())
747 self._log(DEBUG, "Trying SSH agent key {}".format(id_))
748 # for 2-factor auth a successfully auth'd key password
749 # will return an allowed 2fac auth method
750 allowed_types = set(
751 self._transport.auth_publickey(username, key)
752 )
753 two_factor = allowed_types & two_factor_types
754 if not two_factor:
755 return
756 break
757 except SSHException as e:
758 saved_exception = e
760 if not two_factor:
761 keyfiles = []
763 for keytype, name in [
764 (RSAKey, "rsa"),
765 (ECDSAKey, "ecdsa"),
766 (Ed25519Key, "ed25519"),
767 ]:
768 # ~/ssh/ is for windows
769 for directory in [".ssh", "ssh"]:
770 full_path = os.path.expanduser(
771 "~/{}/id_{}".format(directory, name)
772 )
773 if os.path.isfile(full_path):
774 # TODO: only do this append if below did not run
775 keyfiles.append((keytype, full_path))
776 if os.path.isfile(full_path + "-cert.pub"):
777 keyfiles.append((keytype, full_path + "-cert.pub"))
779 if not look_for_keys:
780 keyfiles = []
782 for pkey_class, filename in keyfiles:
783 try:
784 key = self._key_from_filepath(
785 filename, pkey_class, passphrase
786 )
787 # for 2-factor auth a successfully auth'd key will result
788 # in ['password']
789 allowed_types = set(
790 self._transport.auth_publickey(username, key)
791 )
792 two_factor = allowed_types & two_factor_types
793 if not two_factor:
794 return
795 break
796 except (SSHException, IOError) as e:
797 saved_exception = e
799 if password is not None:
800 try:
801 self._transport.auth_password(username, password)
802 return
803 except SSHException as e:
804 saved_exception = e
805 elif two_factor:
806 try:
807 self._transport.auth_interactive_dumb(username)
808 return
809 except SSHException as e:
810 saved_exception = e
812 # if we got an auth-failed exception earlier, re-raise it
813 if saved_exception is not None:
814 raise saved_exception
815 raise SSHException("No authentication methods available")
817 def _log(self, level, msg):
818 self._transport._log(level, msg)
821class MissingHostKeyPolicy:
822 """
823 Interface for defining the policy that `.SSHClient` should use when the
824 SSH server's hostname is not in either the system host keys or the
825 application's keys. Pre-made classes implement policies for automatically
826 adding the key to the application's `.HostKeys` object (`.AutoAddPolicy`),
827 and for automatically rejecting the key (`.RejectPolicy`).
829 This function may be used to ask the user to verify the key, for example.
830 """
832 def missing_host_key(self, client, hostname, key):
833 """
834 Called when an `.SSHClient` receives a server key for a server that
835 isn't in either the system or local `.HostKeys` object. To accept
836 the key, simply return. To reject, raised an exception (which will
837 be passed to the calling application).
838 """
839 pass
842class AutoAddPolicy(MissingHostKeyPolicy):
843 """
844 Policy for automatically adding the hostname and new host key to the
845 local `.HostKeys` object, and saving it. This is used by `.SSHClient`.
846 """
848 def missing_host_key(self, client, hostname, key):
849 client._host_keys.add(hostname, key.get_name(), key)
850 if client._host_keys_filename is not None:
851 client.save_host_keys(client._host_keys_filename)
852 client._log(
853 DEBUG,
854 "Adding {} host key for {}: {}".format(
855 key.get_name(), hostname, hexlify(key.get_fingerprint())
856 ),
857 )
860class RejectPolicy(MissingHostKeyPolicy):
861 """
862 Policy for automatically rejecting the unknown hostname & key. This is
863 used by `.SSHClient`.
864 """
866 def missing_host_key(self, client, hostname, key):
867 client._log(
868 DEBUG,
869 "Rejecting {} host key for {}: {}".format(
870 key.get_name(), hostname, hexlify(key.get_fingerprint())
871 ),
872 )
873 raise SSHException(
874 "Server {!r} not found in known_hosts".format(hostname)
875 )
878class WarningPolicy(MissingHostKeyPolicy):
879 """
880 Policy for logging a Python-style warning for an unknown host key, but
881 accepting it. This is used by `.SSHClient`.
882 """
884 def missing_host_key(self, client, hostname, key):
885 warnings.warn(
886 "Unknown {} host key for {}: {}".format(
887 key.get_name(), hostname, hexlify(key.get_fingerprint())
888 )
889 )