Source code for asyncssh.client

# Copyright (c) 2013-2024 by Ron Frederick <ronf@timeheart.net> and others.
#
# This program and the accompanying materials are made available under
# the terms of the Eclipse Public License v2.0 which accompanies this
# distribution and is available at:
#
#     http://www.eclipse.org/legal/epl-2.0/
#
# This program may also be made available under the following secondary
# licenses when the conditions for such availability set forth in the
# Eclipse Public License v2.0 are satisfied:
#
#    GNU General Public License, Version 2.0, or any later versions of
#    that license
#
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
#
# Contributors:
#     Ron Frederick - initial implementation, API, and documentation

"""SSH client protocol handler"""

from typing import TYPE_CHECKING, Optional

from .auth import KbdIntPrompts, KbdIntResponse, PasswordChangeResponse
from .misc import MaybeAwait
from .public_key import KeyPairListArg, SSHKey


if TYPE_CHECKING:
    # pylint: disable=cyclic-import
    from .connection import SSHClientConnection


[docs] class SSHClient: """SSH client protocol handler Applications may subclass this when implementing an SSH client to receive callbacks when certain events occur on the SSH connection. Whenever a new SSH client connection is opened, a corresponding SSHClient object is created and the method :meth:`connection_made` is called, passing in the :class:`SSHClientConnection` object. When the connection is closed, the method :meth:`connection_lost` is called with an exception representing the reason for the disconnect, or `None` if the connection was closed cleanly. For simple password or public key based authentication, nothing needs to be defined here if the password or client keys are passed in when the connection is created. However, to prompt interactively or otherwise dynamically select these values, the methods :meth:`password_auth_requested` and/or :meth:`public_key_auth_requested` can be defined. Keyboard-interactive authentication is also supported via :meth:`kbdint_auth_requested` and :meth:`kbdint_challenge_received`. If the server sends an authentication banner, the method :meth:`auth_banner_received` will be called. If the server requires a password change, the method :meth:`password_change_requested` will be called, followed by either :meth:`password_changed` or :meth:`password_change_failed` depending on whether the password change is successful. .. note:: The authentication callbacks described here can be defined as coroutines. However, they may be cancelled if they are running when the SSH connection is closed by the server. If they attempt to catch the CancelledError exception to perform cleanup, they should make sure to re-raise it to allow AsyncSSH to finish its own cleanup. """ # pylint: disable=no-self-use,unused-argument
[docs] def connection_made(self, conn: 'SSHClientConnection') -> None: """Called when a connection is made This method is called as soon as the TCP connection completes. The `conn` parameter should be stored if needed for later use. :param conn: The connection which was successfully opened :type conn: :class:`SSHClientConnection` """
[docs] def connection_lost(self, exc: Optional[Exception]) -> None: """Called when a connection is lost or closed This method is called when a connection is closed. If the connection is shut down cleanly, *exc* will be `None`. Otherwise, it will be an exception explaining the reason for the disconnect. :param exc: The exception which caused the connection to close, or `None` if the connection closed cleanly :type exc: :class:`Exception` """
[docs] def debug_msg_received(self, msg: str, lang: str, always_display: bool) -> None: """A debug message was received on this connection This method is called when the other end of the connection sends a debug message. Applications should implement this method if they wish to process these debug messages. :param msg: The debug message sent :param lang: The language the message is in :param always_display: Whether or not to display the message :type msg: `str` :type lang: `str` :type always_display: `bool` """
[docs] def validate_host_public_key(self, host: str, addr: str, port: int, key: SSHKey) -> bool: """Return whether key is an authorized key for this host Server host key validation can be supported by passing known host keys in the `known_hosts` argument of :func:`create_connection`. However, for more flexibility in matching on the allowed set of keys, this method can be implemented by the application to do the matching itself. It should return `True` if the specified key is a valid host key for the server being connected to. By default, this method returns `False` for all host keys. .. note:: This function only needs to report whether the public key provided is a valid key for this host. If it is, AsyncSSH will verify that the server possesses the corresponding private key before allowing the validation to succeed. :param host: The hostname of the target host :param addr: The IP address of the target host :param port: The port number on the target host :param key: The public key sent by the server :type host: `str` :type addr: `str` :type port: `int` :type key: :class:`SSHKey` *public key* :returns: A `bool` indicating if the specified key is a valid key for the target host """ return False # pragma: no cover
[docs] def validate_host_ca_key(self, host: str, addr: str, port: int, key: SSHKey) -> bool: """Return whether key is an authorized CA key for this host Server host certificate validation can be supported by passing known host CA keys in the `known_hosts` argument of :func:`create_connection`. However, for more flexibility in matching on the allowed set of keys, this method can be implemented by the application to do the matching itself. It should return `True` if the specified key is a valid certificate authority key for the server being connected to. By default, this method returns `False` for all CA keys. .. note:: This function only needs to report whether the public key provided is a valid CA key for this host. If it is, AsyncSSH will verify that the certificate is valid, that the host is one of the valid principals for the certificate, and that the server possesses the private key corresponding to the public key in the certificate before allowing the validation to succeed. :param host: The hostname of the target host :param addr: The IP address of the target host :param port: The port number on the target host :param key: The public key which signed the certificate sent by the server :type host: `str` :type addr: `str` :type port: `int` :type key: :class:`SSHKey` *public key* :returns: A `bool` indicating if the specified key is a valid CA key for the target host """ return False # pragma: no cover
[docs] def auth_banner_received(self, msg: str, lang: str) -> None: """An incoming authentication banner was received This method is called when the server sends a banner to display during authentication. Applications should implement this method if they wish to do something with the banner. :param msg: The message the server wanted to display :param lang: The language the message is in :type msg: `str` :type lang: `str` """
[docs] def auth_completed(self) -> None: """Authentication was completed successfully This method is called when authentication has completed successfully. Applications may use this method to create whatever client sessions and direct TCP/IP or UNIX domain connections are needed and/or set up listeners for incoming TCP/IP or UNIX domain connections coming from the server. However, :func:`create_connection` now blocks until authentication is complete, so any code which wishes to use the SSH connection can simply follow that call and doesn't need to be performed in a callback. """
[docs] def public_key_auth_requested(self) -> \ MaybeAwait[Optional[KeyPairListArg]]: """Public key authentication has been requested This method should return a private key corresponding to the user that authentication is being attempted for. This method may be called multiple times and can return a different key to try each time it is called. When there are no keys left to try, it should return `None` to indicate that some other authentication method should be tried. If client keys were provided when the connection was opened, they will be tried before this method is called. If blocking operations need to be performed to determine the key to authenticate with, this method may be defined as a coroutine. :returns: A key as described in :ref:`SpecifyingPrivateKeys` or `None` to move on to another authentication method """ return None # pragma: no cover
[docs] def password_auth_requested(self) -> MaybeAwait[Optional[str]]: """Password authentication has been requested This method should return a string containing the password corresponding to the user that authentication is being attempted for. It may be called multiple times and can return a different password to try each time, but most servers have a limit on the number of attempts allowed. When there's no password left to try, this method should return `None` to indicate that some other authentication method should be tried. If a password was provided when the connection was opened, it will be tried before this method is called. If blocking operations need to be performed to determine the password to authenticate with, this method may be defined as a coroutine. :returns: A string containing the password to authenticate with or `None` to move on to another authentication method """ return None # pragma: no cover
[docs] def password_change_requested(self, prompt: str, lang: str) -> \ MaybeAwait[PasswordChangeResponse]: """A password change has been requested This method is called when password authentication was attempted and the user's password was expired on the server. To request a password change, this method should return a tuple or two strings containing the old and new passwords. Otherwise, it should return `NotImplemented`. If blocking operations need to be performed to determine the passwords to authenticate with, this method may be defined as a coroutine. By default, this method returns `NotImplemented`. :param prompt: The prompt requesting that the user enter a new password :param lang: The language that the prompt is in :type prompt: `str` :type lang: `str` :returns: A tuple of two strings containing the old and new passwords or `NotImplemented` if password changes aren't supported """ return NotImplemented # pragma: no cover
[docs] def password_changed(self) -> None: """The requested password change was successful This method is called to indicate that a requested password change was successful. It is generally followed by a call to :meth:`auth_completed` since this means authentication was also successful. """
[docs] def password_change_failed(self) -> None: """The requested password change has failed This method is called to indicate that a requested password change failed, generally because the requested new password doesn't meet the password criteria on the remote system. After this method is called, other forms of authentication will automatically be attempted. """
[docs] def kbdint_auth_requested(self) -> MaybeAwait[Optional[str]]: """Keyboard-interactive authentication has been requested This method should return a string containing a comma-separated list of submethods that the server should use for keyboard-interactive authentication. An empty string can be returned to let the server pick the type of keyboard-interactive authentication to perform. If keyboard-interactive authentication is not supported, `None` should be returned. By default, keyboard-interactive authentication is supported if a password was provided when the :class:`SSHClient` was created and it hasn't been sent yet. If the challenge is not a password challenge, this authentication will fail. This method and the :meth:`kbdint_challenge_received` method can be overridden if other forms of challenge should be supported. If blocking operations need to be performed to determine the submethods to request, this method may be defined as a coroutine. :returns: A string containing the submethods the server should use for authentication or `None` to move on to another authentication method """ return NotImplemented # pragma: no cover
[docs] def kbdint_challenge_received(self, name: str, instructions: str, lang: str, prompts: KbdIntPrompts) -> \ MaybeAwait[Optional[KbdIntResponse]]: """A keyboard-interactive auth challenge has been received This method is called when the server sends a keyboard-interactive authentication challenge. The return value should be a list of strings of the same length as the number of prompts provided if the challenge can be answered, or `None` to indicate that some other form of authentication should be attempted. If blocking operations need to be performed to determine the responses to authenticate with, this method may be defined as a coroutine. By default, this method will look for a challenge consisting of a single 'Password:' prompt, and call the method :meth:`password_auth_requested` to provide the response. It will also ignore challenges with no prompts (generally used to provide instructions). Any other form of challenge will cause this method to return `None` to move on to another authentication method. :param name: The name of the challenge :param instructions: Instructions to the user about how to respond to the challenge :param lang: The language the challenge is in :param prompts: The challenges the user should respond to and whether or not the responses should be echoed when they are entered :type name: `str` :type instructions: `str` :type lang: `str` :type prompts: `list` of tuples of `str` and `bool` :returns: List of string responses to the challenge or `None` to move on to another authentication method """ return None # pragma: no cover