[Python-modules-commits] [python-asyncssh] 01/06: Import python-asyncssh_1.8.1.orig.tar.gz
Vincent Bernat
bernat at moszumanska.debian.org
Sun Jan 8 16:50:50 UTC 2017
This is an automated email from the git hooks/post-receive script.
bernat pushed a commit to branch master
in repository python-asyncssh.
commit ba570a9963219db1000e6e051f60ad5e81692438
Author: Vincent Bernat <bernat at debian.org>
Date: Sun Jan 8 17:44:25 2017 +0100
Import python-asyncssh_1.8.1.orig.tar.gz
---
README.rst | 31 +-
asyncssh/agent.py | 78 +++--
asyncssh/agent_unix.py | 33 +++
asyncssh/agent_win32.py | 115 ++++++++
asyncssh/channel.py | 184 ++++++++++--
asyncssh/connection.py | 223 ++++++++++++---
asyncssh/constants.py | 1 +
asyncssh/crypto/__init__.py | 6 +
asyncssh/crypto/umac.py | 117 ++++++++
asyncssh/ecdh.py | 19 +-
asyncssh/listener.py | 22 +-
asyncssh/mac.py | 98 +++++--
asyncssh/sftp.py | 13 +-
asyncssh/stream.py | 89 ++++--
asyncssh/version.py | 2 +-
asyncssh/x11.py | 518 ++++++++++++++++++++++++++++++++++
docs/api.rst | 50 +++-
docs/changes.rst | 59 ++++
setup.py | 5 +-
tests/server.py | 6 +-
tests/test_channel.py | 104 ++++++-
tests/test_connection.py | 20 +-
tests/test_forward.py | 6 +-
tests/test_mac.py | 34 ++-
tests/test_stream.py | 42 +++
tests/test_x11.py | 672 ++++++++++++++++++++++++++++++++++++++++++++
26 files changed, 2362 insertions(+), 185 deletions(-)
diff --git a/README.rst b/README.rst
index 217a5e9..c83c04b 100644
--- a/README.rst
+++ b/README.rst
@@ -34,6 +34,7 @@ Features
* OpenSSH-compatible direct and forwarded UNIX domain socket channels
* Local and remote TCP/IP port forwarding
* Local and remote UNIX domain socket forwarding
+ * X11 forwarding support on both the client and the server
* SFTP protocol version 3 with OpenSSH extensions
* Multiple simultaneous sessions on a single SSH connection
@@ -46,8 +47,11 @@ 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
+* Support for accessing keys managed by `ssh-agent`__ on UNIX systems
+
+ * Including agent forwarding support on both the client and the server
+
+* Support for accessing keys managed by PuTTY's Pageant agent on Windows
* OpenSSH-style `known_hosts file`__ support
* OpenSSH-style `authorized_keys file`__ support
* Compatibility with OpenSSH "Encrypt then MAC" option for better security
@@ -108,26 +112,39 @@ functionality:
if you want support for OpenSSH private key encryption.
* Install libsodium from https://github.com/jedisct1/libsodium
- and libnacl from https://github.com/saltstack/libnacl if you want
+ and libnacl from https://pypi.python.org/pypi/libnacl if you want
support for curve25519 Diffie Hellman key exchange, ed25519 keys,
and the chacha20-poly1305 cipher.
+* Install libnettle from http://www.lysator.liu.se/~nisse/nettle/
+ if you want support for UMAC cryptographic hashes.
+
+* Install pypiwin32 from https://pypi.python.org/pypi/pypiwin32
+ if you want support for using the Pageant agent on Windows.
+
AsyncSSH defines the following optional PyPI extra packages to make it
easy to install any or all of these dependencies:
| bcrypt
| libnacl
+ | pypiwin32
-For example, to install both of these, you can run:
+For example, to install bcrypt and libnacl, you can run:
::
pip install 'asyncssh[bcrypt,libnacl]'
+To install all three of these packages on a Windows system, you can run:
+
+ ::
+
+ pip install 'asyncssh[bcrypt,libnacl,pypiwin32]'
+
Note that you will still need to manually install the libsodium library
-listed above for libnacl to work correctly. Unfortunately, since
-libsodium is not a Python package, it cannot be directly installed using
-pip.
+listed above for libnacl to work correctly and/or libnettle for UMAC
+support. Unfortunately, since libsodium and libnettle are not Python
+packages, they cannot be directly installed using pip.
Installing the development branch
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/asyncssh/agent.py b/asyncssh/agent.py
index 570af31..bd7c7be 100644
--- a/asyncssh/agent.py
+++ b/asyncssh/agent.py
@@ -13,10 +13,30 @@
"""SSH agent client"""
import asyncio
+import errno
import os
+import sys
+import tempfile
import asyncssh
+from .logging import logger
+
+
+try:
+ if sys.platform == 'win32': # pragma: no cover
+ from .agent_win32 import open_agent
+ else:
+ from .agent_unix import open_agent
+except ImportError as exc: # pragma: no cover
+ def open_agent(loop, agent_path, reason=str(exc)):
+ """Dummy function if we're unable to import agent support"""
+
+ # pylint: disable=unused-argument
+
+ raise OSError(errno.ENOENT, 'Agent support unavailable: %s' % reason)
+
+from .listener import create_unix_forward_listener
from .misc import ChannelOpenError, load_default_keypairs
from .packet import Byte, String, UInt32, PacketDecodeError, SSHPacket
from .public_key import SSHKeyPair
@@ -57,6 +77,26 @@ SSH_AGENT_RSA_SHA2_512 = 4
# pylint: enable=bad-whitespace
+class _X11AgentListener:
+ """Listener used to forward agent connections"""
+
+ def __init__(self, tempdir, path, unix_listener):
+ self._tempdir = tempdir
+ self._path = path
+ self._unix_listener = unix_listener
+
+ def get_path(self):
+ """Return the path being listened on"""
+
+ return self._path
+
+ def close(self):
+ """Close the agent listener"""
+
+ self._unix_listener.close()
+ self._tempdir.cleanup()
+
+
class SSHAgentKeyPair(SSHKeyPair):
"""Surrogate for a key managed by the SSH agent"""
@@ -122,7 +162,7 @@ class SSHAgentClient:
self._agent_path = agent_path
self._reader = None
self._writer = None
- self._lock = asyncio.Lock()
+ self._lock = asyncio.Lock(loop=loop)
def _cleanup(self):
"""Clean up this SSH agent client"""
@@ -150,15 +190,12 @@ class SSHAgentClient:
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
+ if isinstance(self._agent_path, asyncssh.SSHServerConnection):
self._reader, self._writer = \
- yield from asyncio.open_unix_connection(self._agent_path,
- loop=self._loop)
+ yield from self._agent_path.open_agent_connection()
else:
self._reader, self._writer = \
- yield from self._agent_path.open_agent_connection()
+ yield from open_agent(self._loop, self._agent_path)
@asyncio.coroutine
def _make_request(self, msgtype, *args):
@@ -510,19 +547,26 @@ def connect_agent(agent_path=None, *, loop=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):
+ except (OSError, ChannelOpenError) as exc:
+ logger.debug('Unable to contact agent: %s', exc)
+ return None
+
+
+ at asyncio.coroutine
+def create_agent_listener(conn, loop):
+ """Create a listener for forwarding ssh-agent connections"""
+
+ try:
+ tempdir = tempfile.TemporaryDirectory(prefix='asyncssh-')
+ path = os.path.join(tempdir.name, 'agent')
+ unix_listener = yield from create_unix_forward_listener(
+ conn, loop, conn.create_agent_connection, path)
+
+ return _X11AgentListener(tempdir, path, unix_listener)
+ except OSError:
return None
diff --git a/asyncssh/agent_unix.py b/asyncssh/agent_unix.py
new file mode 100644
index 0000000..d795091
--- /dev/null
+++ b/asyncssh/agent_unix.py
@@ -0,0 +1,33 @@
+# 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 support code for UNIX"""
+
+import asyncio
+import errno
+import os
+
+
+ at asyncio.coroutine
+def open_agent(loop, agent_path):
+ """Open a connection to ssh-agent"""
+
+ 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:
+ raise OSError(errno.ENOENT, 'Agent not found')
+
+ return (yield from asyncio.open_unix_connection(agent_path, loop=loop))
diff --git a/asyncssh/agent_win32.py b/asyncssh/agent_win32.py
new file mode 100644
index 0000000..c3c522c
--- /dev/null
+++ b/asyncssh/agent_win32.py
@@ -0,0 +1,115 @@
+# 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 support code for Windows"""
+
+# Some of the imports below won't be found when running pylint on UNIX
+# pylint: disable=import-error
+
+import asyncio
+import ctypes
+import ctypes.wintypes
+import errno
+import mmapfile
+import win32api
+import win32con
+import win32ui
+
+
+_AGENT_COPYDATA_ID = 0x804e50ba
+_AGENT_MAX_MSGLEN = 8192
+_AGENT_NAME = 'Pageant'
+
+
+def _find_agent_window():
+ """Find and return the Pageant window"""
+
+ try:
+ return win32ui.FindWindow(_AGENT_NAME, _AGENT_NAME)
+ except win32ui.error:
+ raise OSError(errno.ENOENT, 'Agent not found') from None
+
+
+class _CopyDataStruct(ctypes.Structure):
+ """Windows COPYDATASTRUCT argument for WM_COPYDATA message"""
+
+ _fields_ = (('dwData', ctypes.wintypes.LPARAM),
+ ('cbData', ctypes.wintypes.DWORD),
+ ('lpData', ctypes.c_char_p))
+
+
+class _PageantTransport:
+ """Transport to connect to Pageant agent on Windows"""
+
+ def __init__(self):
+ self._mapname = '%s%08x' % (_AGENT_NAME, win32api.GetCurrentThreadId())
+
+ try:
+ self._mapfile = mmapfile.mmapfile(None, self._mapname,
+ _AGENT_MAX_MSGLEN, 0, 0)
+ except mmapfile.error as exc:
+ raise OSError(errno.EIO, str(exc)) from None
+
+ self._cds = _CopyDataStruct(_AGENT_COPYDATA_ID, len(self._mapname) + 1,
+ self._mapname.encode())
+
+ self._writing = False
+
+ def write(self, data):
+ """Write request data to Pageant agent"""
+
+ if not self._writing:
+ self._mapfile.seek(0)
+ self._writing = True
+
+ try:
+ self._mapfile.write(data)
+ except ValueError as exc:
+ raise OSError(errno.EIO, str(exc)) from None
+
+ @asyncio.coroutine
+ def readexactly(self, n):
+ """Read response data from Pageant agent"""
+
+ if self._writing:
+ cwnd = _find_agent_window()
+
+ if not cwnd.SendMessage(win32con.WM_COPYDATA, None, self._cds):
+ raise OSError(errno.EIO, 'Unable to send agent request')
+
+ self._writing = False
+ self._mapfile.seek(0)
+
+ result = self._mapfile.read(n)
+
+ if len(result) != n:
+ raise asyncio.IncompleteReadError(result, n)
+
+ return result
+
+ def close(self):
+ """Close the connection to Pageant"""
+
+ if self._mapfile:
+ self._mapfile.close()
+ self._mapfile = None
+
+
+ at asyncio.coroutine
+def open_agent(loop, agent_path):
+ """Open a connection to the Pageant agent"""
+
+ # pylint: disable=unused-argument
+
+ _find_agent_window()
+ transport = _PageantTransport()
+ return transport, transport
diff --git a/asyncssh/channel.py b/asyncssh/channel.py
index 2102eb2..12b9520 100644
--- a/asyncssh/channel.py
+++ b/asyncssh/channel.py
@@ -13,6 +13,7 @@
"""SSH channel and session handlers"""
import asyncio
+import binascii
from .constants import DEFAULT_LANG, DISC_PROTOCOL_ERROR, EXTENDED_DATA_STDERR
from .constants import MSG_CHANNEL_OPEN, MSG_CHANNEL_WINDOW_ADJUST
@@ -20,6 +21,7 @@ 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 OPEN_CONNECT_FAILED, PTY_OP_RESERVED, PTY_OP_END
+from .constants import OPEN_REQUEST_X11_FORWARDING_FAILED
from .constants import OPEN_REQUEST_PTY_FAILED, OPEN_REQUEST_SESSION_FAILED
from .editor import SSHLineEditorChannel, SSHLineEditorSession
from .misc import ChannelOpenError, DisconnectError, map_handler_name
@@ -68,10 +70,12 @@ class SSHChannel(SSHPacketHandler):
self._recv_buf = []
self._recv_partial = {}
+ self._request_queue = []
+
self._open_waiter = None
self._request_waiters = []
- self._close_event = asyncio.Event()
+ self._close_event = asyncio.Event(loop=loop)
self.set_write_buffer_limits()
@@ -292,6 +296,36 @@ class SSHChannel(SSHPacketHandler):
else:
self._deliver_data(data, datatype)
+ def _service_next_request(self):
+ """Process next item on channel request queue"""
+
+ request, packet, _ = self._request_queue[0]
+
+ name = '_process_' + map_handler_name(request) + '_request'
+ handler = getattr(self, name, None)
+ result = handler(packet) if callable(handler) else False
+
+ if result is not None:
+ self._report_response(result)
+
+ def _report_response(self, result):
+ """Report back the response to a previously issued channel request"""
+
+ request, _, want_reply = self._request_queue.pop(0)
+
+ 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'}:
+ self._session.session_started()
+ self.resume_reading()
+
+ if self._request_queue:
+ self._service_next_request()
+
def process_connection_close(self, exc):
"""Process the SSH connection closing"""
@@ -471,19 +505,9 @@ class SSHChannel(SSHPacketHandler):
raise DisconnectError(DISC_PROTOCOL_ERROR,
'Invalid channel request') from None
- name = '_process_' + map_handler_name(request) + '_request'
- handler = getattr(self, name, None)
- result = handler(packet) if callable(handler) else False
-
- 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'}:
- self._session.session_started()
- self.resume_reading()
+ self._request_queue.append((request, packet, want_reply))
+ if len(self._request_queue) == 1:
+ self._service_next_request()
def _process_response(self, pkttype, packet):
"""Process a success or failure response"""
@@ -795,9 +819,18 @@ class SSHClientChannel(SSHChannel):
self._exit_status = None
self._exit_signal = None
+ def _cleanup(self, exc=None):
+ """Clean up this channel"""
+
+ if self._conn: # pragma: no branch
+ self._conn.detach_x11_listener(self)
+
+ super()._cleanup(exc)
+
@asyncio.coroutine
- def create(self, session_factory, command, subsystem, env,
- term_type, term_size, term_modes, agent_forwarding):
+ def create(self, session_factory, command, subsystem, env, term_type,
+ term_size, term_modes, x11_forwarding, x11_display,
+ x11_auth_path, x11_single_connection, agent_forwarding):
"""Create an SSH client session"""
packet = yield from self._open(b'session')
@@ -843,6 +876,24 @@ class SSHClientChannel(SSHChannel):
raise ChannelOpenError(OPEN_REQUEST_PTY_FAILED,
'PTY request failed')
+ if x11_forwarding:
+ try:
+ auth_proto, remote_auth, screen = \
+ yield from self._conn.attach_x11_listener(
+ self, x11_display, x11_auth_path, x11_single_connection)
+ except ValueError as exc:
+ raise ChannelOpenError(OPEN_REQUEST_X11_FORWARDING_FAILED,
+ str(exc)) from None
+
+ result = yield from self._make_request(
+ b'x11-req', Boolean(x11_single_connection), String(auth_proto),
+ String(binascii.b2a_hex(remote_auth)), UInt32(screen))
+
+ if not result:
+ self._conn.detach_x11_listener(self)
+ raise ChannelOpenError(OPEN_REQUEST_X11_FORWARDING_FAILED,
+ 'X11 forwarding request failed')
+
if agent_forwarding:
self._send_request(b'auth-agent-req at openssh.com')
@@ -1019,7 +1070,7 @@ class SSHServerChannel(SSHChannel):
_write_datatypes = {EXTENDED_DATA_STDERR}
def __init__(self, conn, loop, allow_pty, line_editor, line_history,
- agent_forwarding, encoding, window, max_pktsize):
+ encoding, window, max_pktsize):
"""Initialize an SSH server channel"""
super().__init__(conn, loop, encoding, window, max_pktsize)
@@ -1027,13 +1078,20 @@ class SSHServerChannel(SSHChannel):
self._allow_pty = allow_pty
self._line_editor = line_editor
self._line_history = line_history
- self._agent_forwarding = agent_forwarding
self._env = self._conn.get_key_option('environment', {})
self._command = None
self._subsystem = None
self._term_type = None
self._term_size = (0, 0, 0, 0)
self._term_modes = {}
+ self._x11_display = None
+
+ def _cleanup(self, exc=None):
+ """Clean up this channel"""
+
+ self._conn.detach_x11_listener(self)
+
+ super()._cleanup(exc)
def _wrap_session(self, session):
"""Wrap a line editor around the session if enabled"""
@@ -1095,18 +1153,44 @@ class SSHServerChannel(SSHChannel):
return result
+ def _process_x11_req_request(self, packet):
+ """Process request to enable X11 forwarding"""
+
+ _ = packet.get_boolean() # single_connection
+ auth_proto = packet.get_string()
+ auth_data = packet.get_string()
+ screen = packet.get_uint32()
+ packet.check_end()
+
+ try:
+ auth_data = binascii.a2b_hex(auth_data)
+ except binascii.Error:
+ return False
+
+ self._conn.create_task(self._finish_x11_req_request(auth_proto,
+ auth_data, screen))
+
+ @asyncio.coroutine
+ def _finish_x11_req_request(self, auth_proto, auth_data, screen):
+ """Finish processing request to enable X11 forwarding"""
+
+ self._x11_display = yield from self._conn.attach_x11_listener(
+ self, auth_proto, auth_data, screen)
+
+ self._report_response(bool(self._x11_display))
+
def _process_auth_agent_req_at_openssh_dot_com_request(self, packet):
"""Process a request to enable ssh-agent forwarding"""
packet.check_end()
- if not self._agent_forwarding or \
- not self._conn.check_key_permission('agent-forwarding') or \
- not self._conn.check_certificate_permission('agent-forwarding'):
- return False
+ self._conn.create_task(self._finish_agent_req_request())
- self._conn.agent_forwarding_enabled()
- return True
+ @asyncio.coroutine
+ def _finish_agent_req_request(self):
+ """Finish processing request to enable agent forwarding"""
+
+ self._report_response((yield from self._conn.create_agent_listener()))
def _process_env_request(self, packet):
"""Process a request to set an environment variable"""
@@ -1331,6 +1415,37 @@ class SSHServerChannel(SSHChannel):
return self._term_modes.get(mode)
+ def get_x11_display(self):
+ """Return the display to use for X11 forwarding
+
+ When X11 forwarding has been requested by the client, this
+ method returns the X11 display which should be used to open
+ a forwarded connection. If the client did not request X11
+ forwarding, this method returns ``None``.
+
+ :returns: A str containing the X11 display or ``None`` if
+ X11 fowarding was not requested
+
+ """
+
+ return self._x11_display
+
+ def get_agent_path(self):
+ """Return the path of the ssh-agent listening socket
+
+ When agent forwarding has been requested by the client,
+ this method returns the path of the listening socket which
+ should be used to open a forwarded agent connection. If the
+ client did not request agent forwarding, this method returns
+ ``None``.
+
+ :returns: A str containing the ssh-agent socket path or
+ ``None`` if agent fowarding was not requested
+
+ """
+
+ return self._conn.get_agent_path()
+
def set_xon_xoff(self, client_can_do):
"""Set whether the client should enable XON/XOFF flow control
@@ -1535,6 +1650,27 @@ class SSHUNIXChannel(SSHForwardChannel):
self._extra['local_peername'] = dest_path
self._extra['remote_peername'] = ''
+
+class SSHX11Channel(SSHForwardChannel):
+ """SSH X11 channel"""
+
+ @asyncio.coroutine
+ def open(self, session_factory, orig_host, orig_port):
+ """Open an SSH X11 channel"""
+
+ self._extra['local_peername'] = (orig_host, orig_port)
+ self._extra['remote_peername'] = None
+
+ return (yield from self._open(session_factory, b'x11',
+ String(orig_host), UInt32(orig_port)))
+
+ def set_inbound_peer_names(self, orig_host, orig_port):
+ """Set local and remote peer name for inbound connections"""
+
+ self._extra['local_peername'] = None
+ self._extra['remote_peername'] = (orig_host, orig_port)
+
+
class SSHAgentChannel(SSHForwardChannel):
"""SSH agent channel"""
diff --git a/asyncssh/connection.py b/asyncssh/connection.py
index a9a281e..4cf01c9 100644
--- a/asyncssh/connection.py
+++ b/asyncssh/connection.py
@@ -22,7 +22,7 @@ import time
from collections import OrderedDict
-from .agent import connect_agent
+from .agent import connect_agent, create_agent_listener
from .auth import lookup_client_auth
from .auth import get_server_auth_methods, lookup_server_auth
@@ -30,7 +30,8 @@ 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 .channel import SSHTCPChannel, SSHUNIXChannel
+from .channel import SSHX11Channel, SSHAgentChannel
from .cipher import get_encryption_algs, get_encryption_params, get_cipher
@@ -98,6 +99,8 @@ from .stream import SSHClientStreamSession, SSHServerStreamSession
from .stream import SSHTCPStreamSession, SSHUNIXStreamSession
from .stream import SSHReader, SSHWriter
+from .x11 import create_x11_client_listener, create_x11_server_listener
+
# SSH default port
_DEFAULT_PORT = 22
@@ -278,7 +281,9 @@ class SSHConnection(SSHPacketHandler):
self._local_listeners = {}
- self._close_event = asyncio.Event()
+ self._x11_listener = None
+
+ self._close_event = asyncio.Event(loop=loop)
self._server_host_key_algs = []
@@ -641,7 +646,7 @@ class SSHConnection(SSHPacketHandler):
packet = self._packet + rest
mac = self._inpbuf[rem-self._recv_macsize:rem]
- if not self._recv_mac.verify(UInt32(self._recv_seq) + packet, mac):
+ if not self._recv_mac.verify(self._recv_seq, packet, mac):
raise DisconnectError(DISC_MAC_ERROR,
'MAC verification failed')
@@ -654,8 +659,7 @@ class SSHConnection(SSHPacketHandler):
mac = self._inpbuf[rem-self._recv_macsize:rem]
if self._recv_mac:
- if not self._recv_mac.verify(UInt32(self._recv_seq) +
- packet, mac):
+ if not self._recv_mac.verify(self._recv_seq, packet, mac):
raise DisconnectError(DISC_MAC_ERROR,
'MAC verification failed')
@@ -747,12 +751,12 @@ class SSHConnection(SSHPacketHandler):
packet = hdr + packet
elif self._send_mode == 'etm':
packet = hdr + self._send_cipher.encrypt(packet)
- mac = self._send_mac.sign(UInt32(self._send_seq) + packet)
+ mac = self._send_mac.sign(self._send_seq, packet)
else:
packet = hdr + packet
if self._send_mac:
- mac = self._send_mac.sign(UInt32(self._send_seq) + packet)
+ mac = self._send_mac.sign(self._send_seq, packet)
else:
mac = b''
@@ -1555,7 +1559,8 @@ class SSHConnection(SSHPacketHandler):
yield from self._close_event.wait()
- yield from asyncio.gather(*self._tasks, return_exceptions=True)
+ yield from asyncio.gather(*self._tasks, return_exceptions=True,
+ loop=self._loop)
def disconnect(self, code, reason, lang=DEFAULT_LANG):
"""Disconnect the SSH connection
@@ -1683,6 +1688,18 @@ class SSHConnection(SSHPacketHandler):
return SSHUNIXChannel(self, self._loop, encoding, window, max_pktsize)
+ def create_x11_channel(self, encoding=None, window=_DEFAULT_WINDOW,
+ max_pktsize=_DEFAULT_MAX_PKTSIZE):
+ """Create an SSH X11 channel to use in X11 forwarding"""
+
+ return SSHX11Channel(self, self._loop, encoding, window, max_pktsize)
+
+ def create_agent_channel(self, encoding=None, window=_DEFAULT_WINDOW,
+ max_pktsize=_DEFAULT_MAX_PKTSIZE):
+ """Create an SSH agent channel to use in agent forwarding"""
+
+ return SSHAgentChannel(self, self._loop, encoding, window, max_pktsize)
+
@asyncio.coroutine
def create_connection(self, session_factory, remote_host, remote_port,
orig_host='', orig_port=0, *, encoding=None,
@@ -2246,6 +2263,24 @@ class SSHClientConnection(SSHConnection):
if listen_path in self._remote_listeners:
del self._remote_listeners[listen_path]
+ def _process_x11_open(self, packet):
+ """Process an inbound X11 channel open request"""
+
+ orig_host = packet.get_string()
+ orig_port = packet.get_uint32()
+
+ packet.check_end()
+
+ if self._x11_listener:
+ chan = self.create_x11_channel()
+
+ chan.set_inbound_peer_names(orig_host, orig_port)
+
+ return chan, self._x11_listener.forward_connection()
+ else:
+ raise ChannelOpenError(OPEN_CONNECT_FAILED,
+ 'X11 forwarding disabled')
+
def _process_auth_agent_at_openssh_dot_com_open(self, packet):
"""Process an inbound auth agent channel open request"""
@@ -2259,8 +2294,33 @@ class SSHClientConnection(SSHConnection):
'Auth agent forwarding disabled')
@asyncio.coroutine
+ def attach_x11_listener(self, chan, display, auth_path, single_connection):
+ """Attach a channel to a local X11 display"""
+
+ if not display:
+ display = os.environ.get('DISPLAY')
+
+ if not display:
+ raise ValueError('X11 display not set')
+
+ if not self._x11_listener:
+ self._x11_listener = yield from create_x11_client_listener(
+ self._loop, display, auth_path)
+
+ return self._x11_listener.attach(display, chan, single_connection)
+
+ def detach_x11_listener(self, chan):
+ """Detach a session from a local X11 listener"""
+
+ if self._x11_listener:
+ if self._x11_listener.detach(chan):
+ self._x11_listener = None
+
+ @asyncio.coroutine
def create_session(self, session_factory, command=None, *, subsystem=None,
env={}, term_type=None, term_size=None, term_modes={},
+ x11_forwarding=False, x11_display=None,
+ x11_auth_path=None, x11_single_connection=False,
encoding='utf-8', window=_DEFAULT_WINDOW,
max_pktsize=_DEFAULT_MAX_PKTSIZE):
"""Create an SSH client session
@@ -2310,6 +2370,20 @@ class SSHClientConnection(SSHConnection):
POSIX terminal modes to set for this session, where keys
are taken from :ref:`POSIX terminal modes <PTYModes>` with
values defined in section 8 of :rfc:`4254#section-8`.
+ :param bool x11_forwarding: (optional)
+ Whether or not to request X11 forwarding for this session,
+ defaulting to ``False``
+ :param str x11_display: (optional)
+ The display that X11 connections should be forwarded to,
+ defaulting to the value in the environment variable ``DISPLAY``
+ :param str x11_auth_path: (optional)
+ The path to the Xauthority file to read X11 authentication
+ data from, defaulting to the value in the environment variable
+ ``XAUTHORITY`` or the file ``.Xauthority`` in the user's
+ home directory if that's not set
+ :param bool x11_single_connection: (optional)
+ Whether or not to limit X11 forwarding to a single connection,
+ defaulting to ``False``
:param str encoding: (optional)
The Unicode encoding to use for data exchanged on the connection
:param int window: (optional)
@@ -2329,6 +2403,8 @@ class SSHClientConnection(SSHConnection):
return (yield from chan.create(session_factory, command, subsystem,
env, term_type, term_size, term_modes,
+ x11_forwarding, x11_display,
+ x11_auth_path, x11_single_connection,
bool(self._agent_path)))
@asyncio.coroutine
@@ -2590,7 +2666,7 @@ class SSHClientConnection(SSHConnection):
packet.check_end()
- listener = SSHTCPClientListener(self, session_factory,
+ listener = SSHTCPClientListener(self, self._loop, session_factory,
listen_host, listen_port,
encoding, window, max_pktsize)
@@ -2757,7 +2833,7 @@ class SSHClientConnection(SSHConnection):
packet.check_end()
if pkttype == MSG_REQUEST_SUCCESS:
- listener = SSHUNIXClientListener(self, session_factory,
+ listener = SSHUNIXClientListener(self, self._loop, session_factory,
listen_path, encoding,
window, max_pktsize)
@@ -2940,7 +3016,7 @@ class SSHClientConnection(SSHConnection):
yield from handler.start()
- return SFTPClient(handler, path_encoding, path_errors)
+ return SFTPClient(self._loop, handler, path_encoding, path_errors)
class SSHServerConnection(SSHConnection):
@@ -2981,8 +3057,9 @@ class SSHServerConnection(SSHConnection):
encryption_algs, mac_algs, compression_algs, signature_algs,
rekey_bytes, rekey_seconds, server_host_keys,
authorized_client_keys, allow_pty, line_editor, line_history,
- agent_forwarding, session_factory, session_encoding,
- sftp_factory, window, max_pktsize, login_timeout):
+ x11_forwarding, x11_auth_path, agent_forwarding,
+ session_factory, session_encoding, sftp_factory, window,
+ max_pktsize, login_timeout):
super().__init__(server_factory, loop, server_version, kex_algs,
encryption_algs, mac_algs, compression_algs,
signature_algs, rekey_bytes, rekey_seconds,
@@ -2994,8 +3071,9 @@ class SSHServerConnection(SSHConnection):
self._allow_pty = allow_pty
self._line_editor = line_editor
self._line_history = line_history
+ self._x11_forwarding = x11_forwarding
+ self._x11_auth_path = x11_auth_path
self._agent_forwarding = agent_forwarding
- self._agent_forwarding_enabled = False
self._session_factory = session_factory
self._session_encoding = session_encoding
self._sftp_factory = sftp_factory
@@ -3013,9 +3091,15 @@ class SSHServerConnection(SSHConnection):
self._cert_options = None
self._kbdint_password_auth = False
+ self._agent_listener = None
+
def _cleanup(self, exc):
"""Clean up this server connection"""
+ if self._agent_listener:
+ self._agent_listener.close()
+ self._agent_listener = None
+
self._cancel_login_timer()
super()._cleanup(exc)
@@ -3532,6 +3616,53 @@ class SSHServerConnection(SSHConnection):
self._report_global_response(True)
+ @asyncio.coroutine
+ def attach_x11_listener(self, chan, auth_proto, auth_data, screen):
+ """Attach a channel to a remote X11 display"""
+
+ if (not self._x11_forwarding or
+ not self.check_key_permission('X11-forwarding') or
+ not self.check_certificate_permission('X11-forwarding')):
+ return None
+
+ if not self._x11_listener:
+ self._x11_listener = yield from create_x11_server_listener(
+ self, self._loop, self._x11_auth_path, auth_proto, auth_data)
+
+ if self._x11_listener:
+ return self._x11_listener.attach(chan, screen)
+ else:
+ return None
+
+ def detach_x11_listener(self, chan):
+ """Detach a session from a remote X11 listener"""
+
+ if self._x11_listener:
+ if self._x11_listener.detach(chan):
+ self._x11_listener = None
+
+ def create_agent_listener(self):
+ """Create a listener for forwarding ssh-agent connections"""
+
+ if (not self._agent_forwarding or
+ not self.check_key_permission('agent-forwarding') or
+ not self.check_certificate_permission('agent-forwarding')):
+ return False
+
+ if not self._agent_listener:
+ self._agent_listener = yield from create_agent_listener(self,
+ self._loop)
+
+ return bool(self._agent_listener)
+
+ def get_agent_path(self):
+ """Return the path of the ssh-agent listener, if one exists"""
+
+ if self._agent_listener:
+ return self._agent_listener.get_path()
+ else:
+ return None
+
def send_auth_banner(self, msg, lang=DEFAULT_LANG):
"""Send an authentication banner to the client
@@ -3626,9 +3757,9 @@ class SSHServerConnection(SSHConnection):
| pty
| user-rc
- AsyncSSH internally enforces agent-forwarding, port-forwarding
... 2548 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