[Python-modules-commits] [python-asyncssh] 02/07: Import python-asyncssh_1.5.3.orig.tar.gz
Vincent Bernat
bernat at moszumanska.debian.org
Sat May 21 14:17:21 UTC 2016
This is an automated email from the git hooks/post-receive script.
bernat pushed a commit to branch master
in repository python-asyncssh.
commit 79cb36f3893c01e2808ecb05f8a247f65b508100
Author: Vincent Bernat <bernat at debian.org>
Date: Sat May 21 16:13:21 2016 +0200
Import python-asyncssh_1.5.3.orig.tar.gz
---
README.rst | 38 +-
asyncssh/__init__.py | 22 +-
asyncssh/agent.py | 215 +++
asyncssh/auth.py | 54 +-
asyncssh/auth_keys.py | 4 +-
asyncssh/channel.py | 510 ++++---
asyncssh/client.py | 298 ++++
asyncssh/connection.py | 2612 ++++++++++++++++-----------------
asyncssh/crypto/chacha.py | 1 +
asyncssh/crypto/ec.py | 10 +-
asyncssh/crypto/pyca/cipher.py | 4 +-
asyncssh/crypto/pyca/dsa.py | 4 +-
asyncssh/crypto/pyca/ec.py | 4 +-
asyncssh/dh.py | 8 +-
asyncssh/ecdh.py | 11 +-
asyncssh/ed25519.py | 1 +
asyncssh/forward.py | 134 +-
asyncssh/known_hosts.py | 228 ++-
asyncssh/listener.py | 215 ++-
asyncssh/misc.py | 40 +-
asyncssh/packet.py | 8 +-
asyncssh/public_key.py | 100 +-
asyncssh/server.py | 656 +++++++++
asyncssh/session.py | 113 +-
asyncssh/sftp.py | 963 ++++++------
asyncssh/stream.py | 63 +-
asyncssh/version.py | 2 +-
docs/api.rst | 255 +++-
docs/changes.rst | 152 ++
examples/check_exit_status.py | 2 +
examples/direct_client.py | 2 +
examples/listening_client.py | 2 +
examples/local_forwarding_client.py | 2 +
examples/local_forwarding_client2.py | 2 +
examples/math_client.py | 2 +
examples/remote_forwarding_client.py | 2 +
examples/remote_forwarding_client2.py | 2 +
examples/sample_client.py | 2 +
examples/set_environment.py | 2 +
examples/set_terminal.py | 2 +
examples/sftp_client.py | 2 +
examples/simple_client.py | 2 +
examples/stderr_client.py | 2 +
examples/stream_direct_client.py | 2 +
examples/stream_listening_client.py | 2 +
examples/stream_math_client.py | 2 +
pylintrc | 46 +-
tests/server.py | 148 ++
tests/test_agent.py | 221 +++
tests/test_auth.py | 54 +-
tests/test_channel.py | 1355 +++++++++++++++++
tests/test_connection.py | 843 +++++++++++
tests/test_connection_auth.py | 832 +++++++++++
tests/test_forward.py | 933 ++++++++++++
tests/test_kex.py | 34 +-
tests/test_known_hosts.py | 18 +-
tests/test_packet.py | 180 +++
tests/test_public_key.py | 68 +-
tests/test_sftp.py | 58 +
tests/test_stream.py | 296 ++++
tests/util.py | 150 +-
61 files changed, 9461 insertions(+), 2534 deletions(-)
diff --git a/README.rst b/README.rst
index 31e3eb7..b5696da 100644
--- a/README.rst
+++ b/README.rst
@@ -13,9 +13,12 @@ asyncio framework.
def run_client():
with (yield from asyncssh.connect('localhost')) as conn:
stdin, stdout, stderr = yield from conn.open_session('echo "Hello!"')
+
output = yield from stdout.read()
print(output, end='')
+ yield from stdout.channel.wait_closed()
+
status = stdout.channel.get_exit_status()
if status:
print('Program exited with status %d' % status, file=sys.stderr)
@@ -25,7 +28,8 @@ asyncio framework.
asyncio.get_event_loop().run_until_complete(run_client())
Check out the `examples`__ to get started!
- __ http://asyncssh.readthedocs.org/en/stable/#client-examples
+
+__ http://asyncssh.readthedocs.org/en/stable/#client-examples
Features
--------
@@ -35,7 +39,9 @@ Features
* Shell, command, and subsystem channels
* Environment variables, terminal type, and window size
* Direct and forwarded TCP/IP channels
+ * OpenSSH-compatible direct and forwarded UNIX domain socket channels
* Local and remote TCP/IP port forwarding
+ * Local and remote UNIX domain socket forwarding
* SFTP protocol version 3 with OpenSSH extensions
* Multiple simultaneous sessions on a single SSH connection
@@ -48,6 +54,8 @@ Features
* Password, public key, and keyboard-interactive user authentication methods
* Many types and formats of `public keys and certificates`__
+* Support for accessing keys managed by `ssh-agent`__
+* OpenSSH-style ssh-agent forwarding support
* OpenSSH-style `known_hosts file`__ support
* OpenSSH-style `authorized_keys file`__ support
* Compatibility with OpenSSH "Encrypt then MAC" option for better security
@@ -55,12 +63,21 @@ Features
* Designed to be easy to extend to support new forms of key exchange,
authentication, encryption, and compression algorithms
+__ http://asyncssh.readthedocs.org/en/stable/api.html#key-exchange-algorithms
+__ http://asyncssh.readthedocs.org/en/stable/api.html#encryption-algorithms
+__ http://asyncssh.readthedocs.org/en/stable/api.html#mac-algorithms
+__ http://asyncssh.readthedocs.org/en/stable/api.html#compression-algorithms
+__ http://asyncssh.readthedocs.org/en/stable/api.html#public-key-support
+__ http://asyncssh.readthedocs.org/en/stable/api.html#ssh-agent-support
+__ http://asyncssh.readthedocs.org/en/stable/api.html#known-hosts
+__ http://asyncssh.readthedocs.org/en/stable/api.html#authorized-keys
+
License
-------
This package is released under the following terms:
- Copyright (c) 2013-2015 by Ron Frederick <ronf at timeheart.net>.
+ Copyright (c) 2013-2016 by Ron Frederick <ronf at timeheart.net>.
All rights reserved.
This program and the accompanying materials are made available under
@@ -120,6 +137,16 @@ listed above for libnacl to work correctly. Unfortunately, since
libsodium is not a Python package, it cannot be directly installed using
pip.
+Installing the development branch
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you would like to install the development branch of asyncssh directly
+from Github, you can use the following command to do this:
+
+ ::
+
+ pip install git+https://github.com/ronf/asyncssh@develop
+
Mailing Lists
-------------
@@ -129,13 +156,6 @@ Three mailing lists are available for AsyncSSH:
* `asyncssh-dev at googlegroups.com`__: Development discussions
* `asyncssh-users at googlegroups.com`__: End-user discussions
-__ http://asyncssh.readthedocs.org/en/stable/api.html#key-exchange-algorithms
-__ http://asyncssh.readthedocs.org/en/stable/api.html#encryption-algorithms
-__ http://asyncssh.readthedocs.org/en/stable/api.html#mac-algorithms
-__ http://asyncssh.readthedocs.org/en/stable/api.html#compression-algorithms
-__ http://asyncssh.readthedocs.org/en/stable/api.html#public-key-support
-__ http://asyncssh.readthedocs.org/en/stable/api.html#known-hosts
-__ http://asyncssh.readthedocs.org/en/stable/api.html#authorized-keys
__ http://groups.google.com/d/forum/asyncssh-announce
__ http://groups.google.com/d/forum/asyncssh-dev
__ http://groups.google.com/d/forum/asyncssh-users
diff --git a/asyncssh/__init__.py b/asyncssh/__init__.py
index f0d8414..5018948 100644
--- a/asyncssh/__init__.py
+++ b/asyncssh/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2013-2015 by Ron Frederick <ronf at timeheart.net>.
+# Copyright (c) 2013-2016 by Ron Frederick <ronf at timeheart.net>.
# All rights reserved.
#
# This program and the accompanying materials are made available under
@@ -20,15 +20,23 @@ from .constants import *
# pylint: enable=wildcard-import
+from .agent import SSHAgentClient, connect_agent
+
from .auth_keys import SSHAuthorizedKeys
from .auth_keys import import_authorized_keys, read_authorized_keys
-from .channel import SSHClientChannel, SSHServerChannel, SSHTCPChannel
+from .channel import SSHClientChannel, SSHServerChannel
+from .channel import SSHTCPChannel, SSHUNIXChannel
+
+from .client import SSHClient
-from .connection import SSHClient, SSHServer
from .connection import SSHClientConnection, SSHServerConnection
from .connection import create_connection, create_server, connect, listen
+from .known_hosts import SSHKnownHosts
+from .known_hosts import import_known_hosts, read_known_hosts
+from .known_hosts import match_known_hosts
+
from .listener import SSHListener
from .logging import logger
@@ -39,14 +47,18 @@ from .misc import BreakReceived, SignalReceived, TerminalSizeChanged
from .pbe import KeyEncryptionError
-from .public_key import SSHKey, SSHCertificate, KeyImportError, KeyExportError
+from .public_key import SSHKey, SSHKeyPair, SSHCertificate
+from .public_key import KeyImportError, KeyExportError
from .public_key import import_private_key, import_public_key
from .public_key import import_certificate
from .public_key import read_private_key, read_public_key, read_certificate
from .public_key import read_private_key_list, read_public_key_list
from .public_key import read_certificate_list
-from .session import SSHClientSession, SSHServerSession, SSHTCPSession
+from .session import SSHClientSession, SSHServerSession
+from .session import SSHTCPSession, SSHUNIXSession
+
+from .server import SSHServer
from .sftp import SFTPClient, SFTPServer, SFTPFile, SFTPError
from .sftp import SFTPAttrs, SFTPVFSAttrs, SFTPName
diff --git a/asyncssh/agent.py b/asyncssh/agent.py
new file mode 100644
index 0000000..4a4aea3
--- /dev/null
+++ b/asyncssh/agent.py
@@ -0,0 +1,215 @@
+# Copyright (c) 2016 by Ron Frederick <ronf at timeheart.net>.
+# All rights reserved.
+#
+# This program and the accompanying materials are made available under
+# the terms of the Eclipse Public License v1.0 which accompanies this
+# distribution and is available at:
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# Ron Frederick - initial implementation, API, and documentation
+
+"""SSH agent client"""
+
+import asyncio
+import os
+
+from .misc import ChannelOpenError
+from .packet import Byte, String, UInt32, PacketDecodeError, SSHPacket
+from .public_key import SSHKeyPair
+
+
+# pylint: disable=bad-whitespace
+
+# Generic agent replies
+SSH_AGENT_FAILURE = 5
+
+# Protocol 2 key operations
+SSH2_AGENTC_REQUEST_IDENTITIES = 11
+SSH2_AGENT_IDENTITIES_ANSWER = 12
+SSH2_AGENTC_SIGN_REQUEST = 13
+SSH2_AGENT_SIGN_RESPONSE = 14
+
+# pylint: enable=bad-whitespace
+
+
+class _SSHAgentKeyPair(SSHKeyPair):
+ """Surrogate for a key managed by the SSH agent"""
+
+ def __init__(self, agent, public_data, comment):
+ self._agent = agent
+
+ packet = SSHPacket(public_data)
+ self.algorithm = packet.get_string()
+
+ self.public_data = public_data
+ self.comment = comment
+
+ @asyncio.coroutine
+ def sign(self, data):
+ """Sign a block of data with this private key"""
+
+ return (yield from self._agent.sign(self.public_data, data))
+
+
+class SSHAgentClient:
+ """SSH agent client"""
+
+ def __init__(self, loop, agent_path):
+ self._loop = loop
+ self._agent_path = agent_path
+ self._reader = None
+ self._writer = None
+ self._lock = asyncio.Lock()
+
+ def _cleanup(self):
+ """Clean up this SSH agent client"""
+
+ if self._writer:
+ self._writer.close()
+ self._reader = None
+ self._writer = None
+
+ @asyncio.coroutine
+ def connect(self):
+ """Connect to the SSH agent"""
+
+ if isinstance(self._agent_path, str):
+ # pylint doesn't think open_unix_connection exists
+ # pylint: disable=no-member
+ self._reader, self._writer = \
+ yield from asyncio.open_unix_connection(self._agent_path,
+ loop=self._loop)
+ else:
+ self._reader, self._writer = \
+ yield from self._agent_path.open_agent_connection()
+
+ @asyncio.coroutine
+ def _make_request(self, msgtype, *args):
+ """Send an SSH agent request"""
+
+ with (yield from self._lock):
+ try:
+ if not self._writer:
+ yield from self.connect()
+
+ payload = Byte(msgtype) + b''.join(args)
+ self._writer.write(UInt32(len(payload)) + payload)
+
+ resplen = yield from self._reader.readexactly(4)
+ resplen = int.from_bytes(resplen, 'big')
+
+ resp = yield from self._reader.readexactly(resplen)
+ resp = SSHPacket(resp)
+
+ resptype = resp.get_byte()
+
+ return resptype, resp
+ except (OSError, EOFError, PacketDecodeError) as exc:
+ self._cleanup()
+ raise ValueError(str(exc)) from None
+
+ @asyncio.coroutine
+ def get_keys(self):
+ """Request the available client keys
+
+ This method is a coroutine which returns a list of client keys
+ available in the ssh-agent.
+
+ :returns: A list of :class:`SSHKeyPair` objects
+
+ """
+
+ resptype, resp = \
+ yield from self._make_request(SSH2_AGENTC_REQUEST_IDENTITIES)
+
+ if resptype == SSH2_AGENT_IDENTITIES_ANSWER:
+ result = []
+
+ num_keys = resp.get_uint32()
+ for _ in range(num_keys):
+ key_blob = resp.get_string()
+ comment = resp.get_string()
+
+ result.append(_SSHAgentKeyPair(self, key_blob, comment))
+
+ resp.check_end()
+ return result
+ else:
+ raise ValueError('Unknown SSH agent response: %d' % resptype)
+
+ @asyncio.coroutine
+ def sign(self, key_blob, data):
+ """Sign a block of data with this private key"""
+
+ resptype, resp = \
+ yield from self._make_request(SSH2_AGENTC_SIGN_REQUEST,
+ String(key_blob), String(data),
+ UInt32(0))
+
+ if resptype == SSH2_AGENT_SIGN_RESPONSE:
+ sig = resp.get_string()
+ resp.check_end()
+ return sig
+ elif resptype == SSH_AGENT_FAILURE:
+ raise ValueError('Unknown key passed to SSH agent')
+ else:
+ raise ValueError('Unknown SSH agent response: %d' % resptype)
+
+ def close(self):
+ """Close the SSH agent connection
+
+ This method closes the connection to the ssh-agent. Any
+ attempts to use this :class:``SSHAgentClient`` or the key
+ pairs it previously returned will result in an error.
+
+ """
+
+ self._cleanup()
+
+
+ at asyncio.coroutine
+def connect_agent(agent_path=None, *, loop=None):
+ """Make a connection to the SSH agent
+
+ This function attempts to connect to an ssh-agent process
+ listening on a UNIX domain socket at ``agent_path``. If not
+ provided, it will attempt to get the path from the ``SSH_AUTH_SOCK``
+ environment variable.
+
+ If the connection is successful, an ``SSHAgentClient`` object
+ is returned that has methods on it you can use to query the
+ ssh-agent. If no path is specified and the environment variable
+ is not set or the connection to the agent fails, this function
+ returns ``None``.
+
+ :param agent_path: (optional)
+ The path to use to contact the ssh-agent process, or the
+ :class:`SSHServerConnection` to forward the agent request
+ over.
+ :param loop: (optional)
+ The event loop to use when creating the connection. If not
+ specified, the default event loop is used.
+ :type agent_path: str or :class:`SSHServerConnection`
+
+ :returns: An :class:`SSHAgentClient` or ``None``
+
+ """
+
+ if not loop:
+ loop = asyncio.get_event_loop()
+
+ if not agent_path:
+ agent_path = os.environ.get('SSH_AUTH_SOCK', None)
+
+ if not agent_path:
+ return None
+
+ agent = SSHAgentClient(loop, agent_path)
+
+ try:
+ yield from agent.connect()
+ return agent
+ except (OSError, ChannelOpenError):
+ return None
diff --git a/asyncssh/auth.py b/asyncssh/auth.py
index cb32362..603ebad 100644
--- a/asyncssh/auth.py
+++ b/asyncssh/auth.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2013-2015 by Ron Frederick <ronf at timeheart.net>.
+# Copyright (c) 2013-2016 by Ron Frederick <ronf at timeheart.net>.
# All rights reserved.
#
# This program and the accompanying materials are made available under
@@ -52,16 +52,7 @@ class _Auth(SSHPacketHandler):
"""Create an asynchronous auth task"""
self.cancel()
- self._coro = asyncio.async(self.run_task(coro))
-
- @asyncio.coroutine
- def run_task(self, coro):
- """Run an async auth task, catching disconnect errors"""
-
- try:
- yield from coro
- except DisconnectError as exc:
- self._conn.connection_lost(exc)
+ self._coro = self._conn.create_task(coro)
def cancel(self):
"""Cancel any authentication in progress"""
@@ -92,10 +83,12 @@ class _ClientAuth(_Auth):
def auth_failed(self):
"""Callback when auth fails"""
+ @asyncio.coroutine
def send_request(self, *args, key=None):
"""Send a user authentication request"""
- self._conn.send_userauth_request(self._method, *args, key=key)
+ yield from self._conn.send_userauth_request(self._method,
+ *args, key=key)
class _ClientNullAuth(_ClientAuth):
@@ -105,7 +98,7 @@ class _ClientNullAuth(_ClientAuth):
def _start(self):
"""Start client null authentication"""
- self.send_request()
+ yield from self.send_request()
class _ClientPublicKeyAuth(_ClientAuth):
@@ -115,15 +108,24 @@ class _ClientPublicKeyAuth(_ClientAuth):
def _start(self):
"""Start client public key authentication"""
- self._alg, self._key, self._key_data = \
- yield from self._conn.public_key_auth_requested()
+ self._keypair = yield from self._conn.public_key_auth_requested()
- if self._alg is None:
+ if self._keypair is None:
self._conn.try_next_auth()
return
- self.send_request(Boolean(False), String(self._alg),
- String(self._key_data))
+ yield from self.send_request(Boolean(False),
+ String(self._keypair.algorithm),
+ String(self._keypair.public_data))
+
+ @asyncio.coroutine
+ def _send_signed_request(self):
+ """Send signed public key request"""
+
+ yield from self.send_request(Boolean(True),
+ String(self._keypair.algorithm),
+ String(self._keypair.public_data),
+ key=self._keypair)
def _process_public_key_ok(self, pkttype, packet):
"""Process a public key ok response"""
@@ -134,11 +136,11 @@ class _ClientPublicKeyAuth(_ClientAuth):
key_data = packet.get_string()
packet.check_end()
- if algorithm != self._alg or key_data != self._key_data:
+ if (algorithm != self._keypair.algorithm or
+ key_data != self._keypair.public_data):
raise DisconnectError(DISC_PROTOCOL_ERROR, 'Key mismatch')
- self.send_request(Boolean(True), String(algorithm),
- String(key_data), key=self._key)
+ self.create_task(self._send_signed_request())
return True
packet_handlers = {
@@ -159,7 +161,7 @@ class _ClientKbdIntAuth(_ClientAuth):
self._conn.try_next_auth()
return
- self.send_request(String(''), String(submethods))
+ yield from self.send_request(String(''), String(submethods))
@asyncio.coroutine
def _receive_challenge(self, name, instruction, lang, prompts):
@@ -236,7 +238,7 @@ class _ClientPasswordAuth(_ClientAuth):
self._conn.try_next_auth()
return
- self.send_request(Boolean(False), String(password))
+ yield from self.send_request(Boolean(False), String(password))
@asyncio.coroutine
def _change_password(self, prompt, lang):
@@ -253,9 +255,9 @@ class _ClientPasswordAuth(_ClientAuth):
self._password_change = True
- self.send_request(Boolean(True),
- String(old_password.encode('utf-8')),
- String(new_password.encode('utf-8')))
+ yield from self.send_request(Boolean(True),
+ String(old_password.encode('utf-8')),
+ String(new_password.encode('utf-8')))
def auth_succeeded(self):
if self._password_change:
diff --git a/asyncssh/auth_keys.py b/asyncssh/auth_keys.py
index d0502c5..14e4438 100644
--- a/asyncssh/auth_keys.py
+++ b/asyncssh/auth_keys.py
@@ -195,7 +195,7 @@ def import_authorized_keys(data):
This function imports public keys and associated options in
OpenSSH authorized keys format.
- :param string data:
+ :param str data:
The key data to import.
:returns: An :class:`SSHAuthorizedKeys` object
@@ -211,7 +211,7 @@ def read_authorized_keys(filename):
This function reads public keys and associated options in
OpenSSH authorized_keys format from a file.
- :param string filename:
+ :param str filename:
The file to read the keys from.
:returns: An :class:`SSHAuthorizedKeys` object
diff --git a/asyncssh/channel.py b/asyncssh/channel.py
index cb96c75..1d918cb 100644
--- a/asyncssh/channel.py
+++ b/asyncssh/channel.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2013-2015 by Ron Frederick <ronf at timeheart.net>.
+# Copyright (c) 2013-2016 by Ron Frederick <ronf at timeheart.net>.
# All rights reserved.
#
# This program and the accompanying materials are made available under
@@ -21,12 +21,8 @@ from .constants import MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE, MSG_CHANNEL_REQUEST
from .constants import MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE
from .constants import OPEN_CONNECT_FAILED, PTY_OP_RESERVED, PTY_OP_END
from .constants import OPEN_REQUEST_PTY_FAILED, OPEN_REQUEST_SESSION_FAILED
-from .misc import ChannelOpenError, DisconnectError
+from .misc import ChannelOpenError, DisconnectError, map_handler_name
from .packet import Boolean, Byte, String, UInt32, SSHPacketHandler
-from .sftp import SFTPServerSession
-
-
-_EOF = object()
class SSHChannel(SSHPacketHandler):
@@ -73,12 +69,18 @@ class SSHChannel(SSHPacketHandler):
self._open_waiter = None
self._request_waiters = []
- self._close_waiters = []
+
+ self._close_event = asyncio.Event()
self.set_write_buffer_limits()
self._recv_chan = conn.add_channel(self)
+ def get_connection(self):
+ """Return the connection used by this channel"""
+
+ return self._conn
+
def get_loop(self):
"""Return the event loop used by this channel"""
@@ -89,6 +91,11 @@ class SSHChannel(SSHPacketHandler):
return self._encoding
+ def set_encoding(self, encoding):
+ """Set the encoding on this channel"""
+
+ self._encoding = encoding
+
def get_recv_window(self):
"""Return the configured receive window for this channel"""
@@ -103,7 +110,7 @@ class SSHChannel(SSHPacketHandler):
"""Clean up this channel"""
if self._open_waiter:
- if not self._open_waiter.cancelled():
+ if not self._open_waiter.cancelled(): # pragma: no branch
self._open_waiter.set_exception(
ChannelOpenError(OPEN_CONNECT_FAILED,
'SSH connection closed'))
@@ -112,31 +119,45 @@ class SSHChannel(SSHPacketHandler):
if self._request_waiters:
for waiter in self._request_waiters:
- if not waiter.cancelled():
+ if not waiter.cancelled(): # pragma: no branch
waiter.set_exception(exc)
self._request_waiters = []
- if self._close_waiters:
- for waiter in self._close_waiters:
- if not waiter.cancelled():
- waiter.set_result(None)
-
- self._close_waiters = []
-
if self._session:
self._session.connection_lost(exc)
self._session = None
- if self._conn:
- if self._recv_chan:
- self._conn.remove_channel(self._recv_chan)
- self._recv_chan = None
+ self._close_event.set()
+ if self._conn: # pragma: no branch
+ self._conn.remove_channel(self._recv_chan)
+ self._recv_chan = None
self._conn = None
- self._send_state = 'closed'
- self._recv_state = 'closed'
+ def _close_send(self):
+ """Discard unsent data and close the channel for sending"""
+
+ # Discard unsent data
+ self._send_buf = []
+ self._send_buf_len = 0
+
+ if self._send_state != 'closed':
+ self._send_packet(MSG_CHANNEL_CLOSE)
+ self._send_chan = None
+ self._send_state = 'closed'
+
+ def _discard_recv(self):
+ """Discard unreceived data and clean up if close received"""
+
+ # Discard unreceived data
+ self._recv_buf = []
+ self._recv_paused = False
+
+ # If recv is close_pending, we know send is already closed
+ if self._recv_state == 'close_pending':
+ self._recv_state = 'closed'
+ self._loop.call_soon(self._cleanup)
def _pause_resume_writing(self):
"""Pause or resume writing based on send buffer low/high water marks"""
@@ -178,59 +199,72 @@ class SSHChannel(SSHPacketHandler):
if not self._send_buf:
if self._send_state == 'eof_pending':
self._send_packet(MSG_CHANNEL_EOF)
- self._send_state = 'eof_sent'
+ self._send_state = 'eof'
elif self._send_state == 'close_pending':
- self._send_packet(MSG_CHANNEL_CLOSE)
- self._send_state = 'close_sent'
+ self._close_send()
+
+ def _flush_recv_buf(self, exc=None):
+ """Flush as much data in the recv buffer as the application allows"""
+
+ while self._recv_buf and not self._recv_paused:
+ self._deliver_data(*self._recv_buf.pop(0))
+
+ if not self._recv_buf:
+ if self._recv_state == 'eof_pending':
+ self._recv_state = 'eof'
+
+ if (not self._session.eof_received() and
+ self._send_state == 'open'):
+ self.write_eof()
+ elif self._recv_state == 'close_pending':
+ self._recv_state = 'closed'
+
+ if self._recv_partial and not exc:
+ exc = DisconnectError(DISC_PROTOCOL_ERROR,
+ 'Unicode decode error')
+
+ self._loop.call_soon(self._cleanup, exc)
def _deliver_data(self, data, datatype):
"""Deliver incoming data to the session"""
- if data == _EOF:
- if datatype in self._recv_partial:
- raise DisconnectError(DISC_PROTOCOL_ERROR,
- 'Unicode decode error')
+ self._recv_window -= len(data)
- if not self._session.eof_received() and self._send_state == 'open':
- self.write_eof()
- else:
- self._recv_window -= len(data)
-
- if self._recv_window < self._init_recv_window / 2:
- self._send_packet(MSG_CHANNEL_WINDOW_ADJUST,
- UInt32(self._init_recv_window -
- self._recv_window))
- self._recv_window = self._init_recv_window
-
- if self._encoding:
- if datatype in self._recv_partial:
- encdata = self._recv_partial.pop(datatype) + data
- else:
- encdata = data
-
- while encdata:
- try:
- data = encdata.decode(self._encoding)
- encdata = b''
- except UnicodeDecodeError as exc:
- if exc.start > 0:
- # Avoid pylint false positive
- # pylint: disable=invalid-slice-index
- data = encdata[:exc.start].decode()
- encdata = encdata[exc.start:]
- elif exc.reason == 'unexpected end of data':
- break
- else:
- raise DisconnectError(DISC_PROTOCOL_ERROR,
- 'Unicode decode error')
-
- self._session.data_received(data, datatype)
-
- if encdata:
- self._recv_partial[datatype] = encdata
+ if self._recv_window < self._init_recv_window / 2:
+ self._send_packet(MSG_CHANNEL_WINDOW_ADJUST,
+ UInt32(self._init_recv_window -
+ self._recv_window))
+ self._recv_window = self._init_recv_window
+
+ if self._encoding:
+ if datatype in self._recv_partial:
+ encdata = self._recv_partial.pop(datatype) + data
else:
+ encdata = data
+
+ while encdata:
+ try:
+ data = encdata.decode(self._encoding)
+ encdata = b''
+ except UnicodeDecodeError as exc:
+ if exc.start > 0:
+ # Avoid pylint false positive
+ # pylint: disable=invalid-slice-index
+ data = encdata[:exc.start].decode()
+ encdata = encdata[exc.start:]
+ elif exc.reason == 'unexpected end of data':
+ break
+ else:
+ raise DisconnectError(DISC_PROTOCOL_ERROR,
+ 'Unicode decode error')
+
self._session.data_received(data, datatype)
+ if encdata:
+ self._recv_partial[datatype] = encdata
+ else:
+ self._session.data_received(data, datatype)
+
def _accept_data(self, data, datatype=None):
"""Accept new data on the channel
@@ -246,10 +280,10 @@ class SSHChannel(SSHPacketHandler):
if not data:
return
- if self._send_state in {'close_pending', 'close_sent', 'closed'}:
+ if self._send_state in {'close_pending', 'closed'}:
return
- if data != _EOF and len(data) > self._recv_window:
+ if len(data) > self._recv_window:
raise DisconnectError(DISC_PROTOCOL_ERROR, 'Window exceeded')
if self._recv_paused:
@@ -260,25 +294,29 @@ class SSHChannel(SSHPacketHandler):
def process_connection_close(self, exc):
"""Process the SSH connection closing"""
- self._cleanup(exc)
+ if self._send_state != 'closed':
+ self._send_state = 'closed'
+
+ if self._recv_state not in {'close_pending', 'closed'}:
+ self._recv_state = 'close_pending'
+ self._flush_recv_buf(exc)
+ elif self._recv_state == 'closed':
+ self._loop.call_soon(self._cleanup, exc)
def process_open(self, send_chan, send_window, send_pktsize, session):
"""Process a channel open request"""
- if self._recv_state != 'closed':
- raise DisconnectError(DISC_PROTOCOL_ERROR, 'Channel already open')
-
- self._send_state = 'open_received'
self._send_chan = send_chan
self._send_window = send_window
self._send_pktsize = send_pktsize
- asyncio.async(self._finish_open_request(session), loop=self._loop)
+ self._conn.create_task(self._finish_open_request(session))
@asyncio.coroutine
def _finish_open_request(self, session):
"""Finish processing a channel open request"""
+ # pylint: disable=broad-except
try:
if asyncio.iscoroutine(session):
session = yield from session
@@ -314,7 +352,7 @@ class SSHChannel(SSHPacketHandler):
self._send_state = 'open'
self._recv_state = 'open'
- if not self._open_waiter.cancelled():
+ if not self._open_waiter.cancelled(): # pragma: no branch
self._open_waiter.set_result(packet)
self._open_waiter = None
@@ -326,7 +364,7 @@ class SSHChannel(SSHPacketHandler):
raise DisconnectError(DISC_PROTOCOL_ERROR,
'Channel not being opened')
- if not self._open_waiter.cancelled():
+ if not self._open_waiter.cancelled(): # pragma: no branch
self._open_waiter.set_exception(
ChannelOpenError(code, reason, lang))
@@ -338,7 +376,7 @@ class SSHChannel(SSHPacketHandler):
# pylint: disable=unused-argument
- if self._recv_state not in {'open', 'eof_received'}:
+ if self._recv_state not in {'open', 'eof_pending', 'eof'}:
raise DisconnectError(DISC_PROTOCOL_ERROR, 'Channel not open')
adjust = packet.get_uint32()
@@ -391,41 +429,32 @@ class SSHChannel(SSHPacketHandler):
packet.check_end()
- self._recv_state = 'eof_received'
- self._accept_data(_EOF)
+ self._recv_state = 'eof_pending'
+ self._flush_recv_buf()
def _process_close(self, pkttype, packet):
"""Process an incoming channel close"""
# pylint: disable=unused-argument
- if self._recv_state not in {'open', 'eof_received'}:
+ if self._recv_state not in {'open', 'eof_pending', 'eof'}:
raise DisconnectError(DISC_PROTOCOL_ERROR, 'Channel not open')
packet.check_end()
- # Flush any unsent data
- self._send_buf = []
- self._send_buf_len = 0
-
- # If we haven't yet sent a close, send one now
- if self._send_state not in {'close_sent', 'closed'}:
- self._send_packet(MSG_CHANNEL_CLOSE)
- self._send_state = 'close_sent'
+ self._close_send()
- self._loop.call_soon(self._cleanup)
+ self._recv_state = 'close_pending'
+ self._flush_recv_buf()
def _process_request(self, pkttype, packet):
"""Process an incoming channel request"""
# pylint: disable=unused-argument
- if self._recv_state not in {'open', 'eof_received'}:
+ if self._recv_state not in {'open', 'eof_pending', 'eof'}:
raise DisconnectError(DISC_PROTOCOL_ERROR, 'Channel not open')
- if self._send_state in {'close_pending', 'close_sent', 'closed'}:
- return
-
request = packet.get_string()
want_reply = packet.get_boolean()
@@ -435,34 +464,28 @@ class SSHChannel(SSHPacketHandler):
raise DisconnectError(DISC_PROTOCOL_ERROR,
'Invalid channel request') from None
- name = '_process_' + request.replace('-', '_') + '_request'
+ name = '_process_' + map_handler_name(request) + '_request'
handler = getattr(self, name, None)
result = handler(packet) if callable(handler) else False
- if want_reply:
+ if want_reply and self._send_state not in {'close_pending', 'closed'}:
if result:
self._send_packet(MSG_CHANNEL_SUCCESS)
else:
self._send_packet(MSG_CHANNEL_FAILURE)
- if result and request in ('shell', 'exec', 'subsystem'):
+ if result and request in {'shell', 'exec', 'subsystem'}:
self._session.session_started()
self.resume_reading()
def _process_response(self, pkttype, packet):
"""Process a success or failure response"""
- # pylint: disable=unused-argument
-
- if self._send_state not in {'open', 'eof_pending', 'eof_sent',
- 'close_pending', 'close_sent'}:
- raise DisconnectError(DISC_PROTOCOL_ERROR, 'Channel not open')
-
packet.check_end()
if self._request_waiters:
waiter = self._request_waiters.pop(0)
- if not waiter.cancelled():
+ if not waiter.cancelled(): # pragma: no branch
waiter.set_result(pkttype == MSG_CHANNEL_SUCCESS)
... 15377 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-asyncssh.git
More information about the Python-modules-commits
mailing list