[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