/create_conn.py
Python | 329 lines | 271 code | 36 blank | 22 comment | 12 complexity | 0d4b565ad9b251545a98485ea5cfa15c MD5 | raw file
- import asyncio
- import getpass
- import io
- import os
- import socket
- import sys
- import time
- from collections import OrderedDict
- from .agent import connect_agent
- from .auth import lookup_client_auth
- from .auth import get_server_auth_methods, lookup_server_auth
- from .auth_keys import read_authorized_keys
- from .channel import SSHClientChannel, SSHServerChannel
- from .channel import SSHTCPChannel, SSHUNIXChannel, SSHAgentChannel
- from .cipher import get_encryption_algs, get_encryption_params, get_cipher
- from .client import SSHClient
- from .compression import get_compression_algs, get_compression_params
- from .compression import get_compressor, get_decompressor
- from .constants import DEFAULT_LANG
- from .constants import DISC_BY_APPLICATION, DISC_CONNECTION_LOST
- from .constants import DISC_KEY_EXCHANGE_FAILED, DISC_HOST_KEY_NOT_VERIFYABLE
- from .constants import DISC_MAC_ERROR, DISC_NO_MORE_AUTH_METHODS_AVAILABLE
- from .constants import DISC_PROTOCOL_ERROR, DISC_SERVICE_NOT_AVAILABLE
- from .constants import EXTENDED_DATA_STDERR
- from .constants import MSG_DISCONNECT, MSG_IGNORE, MSG_UNIMPLEMENTED, MSG_DEBUG
- from .constants import MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT, MSG_EXT_INFO
- from .constants import MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_CONFIRMATION
- from .constants import MSG_CHANNEL_OPEN_FAILURE, MSG_CHANNEL_WINDOW_ADJUST
- from .constants import MSG_CHANNEL_DATA, MSG_CHANNEL_EXTENDED_DATA
- from .constants import MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE, MSG_CHANNEL_REQUEST
- from .constants import MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE
- from .constants import MSG_KEXINIT, MSG_NEWKEYS, MSG_KEX_FIRST, MSG_KEX_LAST
- from .constants import MSG_USERAUTH_REQUEST, MSG_USERAUTH_FAILURE
- from .constants import MSG_USERAUTH_SUCCESS, MSG_USERAUTH_BANNER
- from .constants import MSG_USERAUTH_FIRST, MSG_USERAUTH_LAST
- from .constants import MSG_GLOBAL_REQUEST, MSG_REQUEST_SUCCESS
- from .constants import MSG_REQUEST_FAILURE
- from .constants import OPEN_ADMINISTRATIVELY_PROHIBITED, OPEN_CONNECT_FAILED
- from .constants import OPEN_UNKNOWN_CHANNEL_TYPE
- from .forward import SSHForwarder
- from .kex import get_kex_algs, get_kex
- from .known_hosts import match_known_hosts
- from .listener import SSHTCPClientListener, create_tcp_forward_listener
- from .listener import SSHUNIXClientListener, create_unix_forward_listener
- from .logging import logger
- from .mac import get_mac_algs, get_mac_params, get_mac
- from .misc import ChannelOpenError, DisconnectError, PasswordChangeRequired
- from .misc import async_context_manager, ensure_future, ip_address
- from .misc import load_default_keypairs, map_handler_name
- from .packet import Boolean, Byte, NameList, String, UInt32, UInt64
- from .packet import PacketDecodeError, SSHPacket, SSHPacketHandler
- from .process import PIPE, SSHClientProcess
- from .public_key import CERT_TYPE_HOST, CERT_TYPE_USER, KeyImportError
- from .public_key import get_public_key_algs, get_certificate_algs
- from .public_key import decode_ssh_public_key, decode_ssh_certificate
- from .public_key import load_keypairs, load_public_keys
- from .saslprep import saslprep, SASLPrepError
- from .server import SSHServer
- from .sftp import SFTPClient, SFTPServer, SFTPClientHandler
- from .stream import SSHClientStreamSession, SSHServerStreamSession
- from .stream import SSHTCPStreamSession, SSHUNIXStreamSession
- from .stream import SSHReader, SSHWriter
- # SSH default port
- _DEFAULT_PORT = 22
- # SSH service names
- _USERAUTH_SERVICE = b'ssh-userauth'
- _CONNECTION_SERVICE = b'ssh-connection'
- # Default rekey parameters
- _DEFAULT_REKEY_BYTES = 1 << 30 # 1 GiB
- _DEFAULT_REKEY_SECONDS = 3600 # 1 hour
- # Default login timeout
- _DEFAULT_LOGIN_TIMEOUT = 120 # 2 minutes
- # Default channel parameters
- _DEFAULT_WINDOW = 2 * 1024 * 1024 # 2 MiB
- _DEFAULT_MAX_PKTSIZE = 32768 # 32 kiB
- # Default line editor parameters
- _DEFAULT_LINE_HISTORY = 1000 # 1000 lines
- async def create_connection(client_factory, host, port=_DEFAULT_PORT, *,
- loop=None, tunnel=None, family=0, flags=0,
- local_addr=None, known_hosts=(), username=None,
- password=None, client_keys=(), passphrase=None,
- agent_path=(), agent_forwarding=False,
- client_version=(), kex_algs=(), encryption_algs=(),
- mac_algs=(), compression_algs=(), signature_algs=(),
- rekey_bytes=_DEFAULT_REKEY_BYTES,
- rekey_seconds=_DEFAULT_REKEY_SECONDS):
- """Create an SSH client connection
- This function is a coroutine which can be run to create an outbound SSH
- client connection to the specified host and port.
- When successful, the following steps occur:
- 1. The connection is established and an :class:`SSHClientConnection`
- object is created to represent it.
- 2. The ``client_factory`` is called without arguments and should
- return an :class:`SSHClient` object.
- 3. The client object is tied to the connection and its
- :meth:`connection_made() <SSHClient.connection_made>` method
- is called.
- 4. The SSH handshake and authentication process is initiated,
- calling methods on the client object if needed.
- 5. When authentication completes successfully, the client's
- :meth:`auth_completed() <SSHClient.auth_completed>` method is
- called.
- 6. The coroutine returns the ``(connection, client)`` pair. At
- this point, the connection is ready for sessions to be opened
- or port forwarding to be set up.
- If an error occurs, it will be raised as an exception and the partially
- open connection and client objects will be cleaned up.
- .. note:: Unlike :func:`socket.create_connection`, asyncio calls
- to create a connection do not support a ``timeout``
- parameter. However, asyncio calls can be wrapped in a
- call to :func:`asyncio.wait_for` or :func:`asyncio.wait`
- which takes a timeout and provides equivalent functionality.
- :param callable client_factory:
- A callable which returns an :class:`SSHClient` object that will
- be tied to the connection
- :param str host:
- The hostname or address to connect to
- :param int port: (optional)
- The port number to connect to. If not specified, the default
- SSH port is used.
- :param loop: (optional)
- The event loop to use when creating the connection. If not
- specified, the default event loop is used.
- :param tunnel: (optional)
- An existing SSH client connection that this new connection should
- be tunneled over. If set, a direct TCP/IP tunnel will be opened
- over this connection to the requested host and port rather than
- connecting directly via TCP.
- :param family: (optional)
- The address family to use when creating the socket. By default,
- the address family is automatically selected based on the host.
- :param flags: (optional)
- The flags to pass to getaddrinfo() when looking up the host address
- :param local_addr: (optional)
- The host and port to bind the socket to before connecting
- :param known_hosts: (optional)
- The list of keys which will be used to validate the server host
- key presented during the SSH handshake. If this is not specified,
- the keys will be looked up in the file :file:`.ssh/known_hosts`.
- If this is explicitly set to ``None``, server host key validation
- will be disabled.
- :param str username: (optional)
- Username to authenticate as on the server. If not specified,
- the currently logged in user on the local machine will be used.
- :param str password: (optional)
- The password to use for client password authentication or
- keyboard-interactive authentication which prompts for a password.
- If this is not specified, client password authentication will
- not be performed.
- :param client_keys: (optional)
- A list of keys which will be used to authenticate this client
- via public key authentication. If no client keys are specified,
- an attempt will be made to get them from an ssh-agent process.
- If that is not available, an attempt will be made to load them
- from the files :file:`.ssh/id_ed25519`, :file:`.ssh/id_ecdsa`,
- :file:`.ssh/id_rsa`, and :file:`.ssh/id_dsa` in the user's home
- directory, with optional certificates loaded from the files
- :file:`.ssh/id_ed25519-cert.pub`, :file:`.ssh/id_ecdsa-cert.pub`,
- :file:`.ssh/id_rsa-cert.pub`, and :file:`.ssh/id_dsa-cert.pub`.
- If this argument is explicitly set to ``None``, client public
- key authentication will not be performed.
- :param str passphrase: (optional)
- The passphrase to use to decrypt client keys when loading them,
- if they are encrypted. If this is not specified, only unencrypted
- client keys can be loaded. If the keys passed into client_keys
- are already loaded, this argument is ignored.
- :param agent_path: (optional)
- The path of a UNIX domain socket to use to contact an ssh-agent
- process which will perform the operations needed for client
- public key authentication, or the :class:`SSHServerConnection`
- to use to forward ssh-agent requests over. If this is not
- specified and the environment variable ``SSH_AUTH_SOCK`` is
- set, its value will be used as the path. If ``client_keys``
- is specified or this argument is explicitly set to ``None``,
- an ssh-agent will not be used.
- :param bool agent_forwarding: (optional)
- Whether or not to allow forwarding of ssh-agent requests from
- processes running on the server. By default, ssh-agent forwarding
- requests from the server are not allowed.
- :param str client_version: (optional)
- An ASCII string to advertise to the SSH server as the version of
- this client, defaulting to ``AsyncSSH`` and its version number.
- :param kex_algs: (optional)
- A list of allowed key exchange algorithms in the SSH handshake,
- taken from :ref:`key exchange algorithms <KexAlgs>`
- :param encryption_algs: (optional)
- A list of encryption algorithms to use during the SSH handshake,
- taken from :ref:`encryption algorithms <EncryptionAlgs>`
- :param mac_algs: (optional)
- A list of MAC algorithms to use during the SSH handshake, taken
- from :ref:`MAC algorithms <MACAlgs>`
- :param compression_algs: (optional)
- A list of compression algorithms to use during the SSH handshake,
- taken from :ref:`compression algorithms <CompressionAlgs>`, or
- ``None`` to disable compression
- :param signature_algs: (optional)
- A list of public key signature algorithms to use during the SSH
- handshake, taken from :ref:`signature algorithms <SignatureAlgs>`
- :param int rekey_bytes: (optional)
- The number of bytes which can be sent before the SSH session
- key is renegotiated. This defaults to 1 GB.
- :param int rekey_seconds: (optional)
- The maximum time in seconds before the SSH session key is
- renegotiated. This defaults to 1 hour.
- :type tunnel: :class:`SSHClientConnection`
- :type family: ``socket.AF_UNSPEC``, ``socket.AF_INET``, or
- ``socket.AF_INET6``
- :type flags: flags to pass to :meth:`getaddrinfo() <socket.getaddrinfo>`
- :type local_addr: tuple of str and int
- :type known_hosts: *see* :ref:`SpecifyingKnownHosts`
- :type client_keys: *see* :ref:`SpecifyingPrivateKeys`
- :type agent_path: str or :class:`SSHServerConnection`
- :type kex_algs: list of str
- :type encryption_algs: list of str
- :type mac_algs: list of str
- :type compression_algs: list of str
- :type signature_algs: list of str
- :returns: An :class:`SSHClientConnection` and :class:`SSHClient`
- """
- def conn_factory():
- """Return an SSH client connection handler"""
- return SSHClientConnection(client_factory, loop, client_version,
- kex_algs, encryption_algs, mac_algs,
- compression_algs, signature_algs,
- rekey_bytes, rekey_seconds, host, port,
- known_hosts, username, password,
- client_keys, agent, agent_path, auth_waiter)
- if not client_factory:
- client_factory = SSHClient
- if not loop:
- loop = asyncio.get_event_loop()
- client_version = _validate_version(client_version)
- kex_algs, encryption_algs, mac_algs, compression_algs, signature_algs = \
- _validate_algs(kex_algs, encryption_algs, mac_algs,
- compression_algs, signature_algs)
- if username is None:
- username = getpass.getuser()
- agent = None
- if agent_path is ():
- agent_path = os.environ.get('SSH_AUTH_SOCK', None)
- if client_keys:
- client_keys = load_keypairs(client_keys, passphrase)
- elif client_keys is ():
- if agent_path:
- agent = await connect_agent(agent_path)
- if agent:
- client_keys = await agent.get_keys()
- else:
- agent_path = None
- if not client_keys:
- client_keys = load_default_keypairs()
- if not agent_forwarding:
- agent_path = None
- auth_waiter = asyncio.Future(loop=loop)
- # pylint: disable=broad-except
- try:
- if tunnel:
- _, conn = await tunnel.create_connection(conn_factory, host,
- port)
- else:
- _, conn = await loop.create_connection(conn_factory, host,
- port, family=family,
- flags=flags,
- local_addr=local_addr)
- except Exception:
- if agent:
- agent.close()
- raise
- await auth_waiter
- return conn, conn.get_owner()