[Python-modules-commits] [python-asyncssh] 02/08: Import python-asyncssh_1.10.1.orig.tar.gz

Vincent Bernat bernat at moszumanska.debian.org
Mon Sep 18 10:02:30 UTC 2017


This is an automated email from the git hooks/post-receive script.

bernat pushed a commit to branch debian/master
in repository python-asyncssh.

commit 0dc15b0d2a6d3a2bb983e7d42845220212169792
Author: Vincent Bernat <bernat at debian.org>
Date:   Wed Jul 5 16:49:20 2017 +0200

    Import python-asyncssh_1.10.1.orig.tar.gz
---
 .travis.yml                                        |    2 +-
 COPYRIGHT                                          |    2 +-
 README.rst                                         |   18 +-
 appveyor.yml                                       |   32 +
 asyncssh/__init__.py                               |    7 +-
 asyncssh/auth.py                                   |  354 ++++-
 asyncssh/channel.py                                |    5 +
 asyncssh/client.py                                 |    9 +-
 asyncssh/connection.py                             |  364 +++--
 asyncssh/dh.py                                     |  461 ++++--
 asyncssh/ecdh.py                                   |    9 +-
 asyncssh/gss.py                                    |   55 +
 asyncssh/gss_unix.py                               |  146 ++
 asyncssh/gss_win32.py                              |  173 ++
 asyncssh/kex.py                                    |   41 +-
 asyncssh/known_hosts.py                            |   29 +-
 asyncssh/misc.py                                   |   10 +-
 asyncssh/process.py                                |  476 ++++--
 asyncssh/public_key.py                             |   71 +-
 asyncssh/scp.py                                    |  832 ++++++++++
 asyncssh/server.py                                 |   42 +-
 asyncssh/sftp.py                                   |  798 +++++++---
 asyncssh/stream.py                                 |   84 +-
 asyncssh/version.py                                |    4 +-
 asyncssh/x11.py                                    |    9 +-
 docs/api.rst                                       |  168 +-
 docs/changes.rst                                   |   85 +-
 docs/conf.py                                       |    2 +
 docs/index.rst                                     |   91 ++
 examples/chat_server.py                            |   19 +-
 examples/editor.py                                 |   27 +-
 examples/math_server.py                            |   16 +-
 .../{simple_cert_server.py => redirect_server.py}  |   18 +-
 examples/scp_client.py                             |   23 +
 examples/show_environment.py                       |   18 +-
 examples/show_terminal.py                          |   29 +-
 examples/simple_cert_server.py                     |   12 +-
 examples/simple_keyed_server.py                    |   12 +-
 ...{simple_cert_server.py => simple_scp_server.py} |    9 +-
 examples/simple_server.py                          |   12 +-
 pylintrc                                           |    4 +-
 setup.py                                           |    1 +
 tests/gss_stub.py                                  |   43 +
 tests/gssapi_stub.py                               |  128 ++
 tests/server.py                                    |   65 +-
 tests/sspi_stub.py                                 |  125 ++
 tests/test_auth.py                                 |  238 ++-
 tests/test_cipher.py                               |    5 +-
 tests/test_connection.py                           |  138 +-
 tests/test_connection_auth.py                      |  257 ++-
 tests/test_forward.py                              |    8 +
 tests/test_kex.py                                  |  259 ++-
 tests/test_known_hosts.py                          |   25 +-
 tests/test_process.py                              |  556 ++++---
 tests/test_public_key.py                           |  117 +-
 tests/test_sftp.py                                 | 1674 +++++++++++++++++---
 tests/test_x11.py                                  |   18 +-
 tests/util.py                                      |   84 +-
 tests_py35/test_sftp.py                            |   18 +-
 tox.ini                                            |   21 +-
 60 files changed, 6860 insertions(+), 1498 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index d471d7d..16c130f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
 language: python
 
 install:
-  - pip install tox bcrypt libnacl
+  - pip install tox
 
 matrix:
   allow_failures:
diff --git a/COPYRIGHT b/COPYRIGHT
index 5c6c0dc..e5b8563 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -1,4 +1,4 @@
-Copyright (c) 2013-2016 by Ron Frederick <ronf at timeheart.net>.
+Copyright (c) 2013-2017 by Ron Frederick <ronf at timeheart.net>.
 All rights reserved.
 
 This program and the accompanying materials are made available under
diff --git a/README.rst b/README.rst
index c83c04b..47b3925 100644
--- a/README.rst
+++ b/README.rst
@@ -26,7 +26,7 @@ __ http://asyncssh.readthedocs.io/en/stable/#client-examples
 Features
 --------
 
-* Full support for SSHv2 and SFTP client and server functions
+* Full support for SSHv2, SFTP, and SCP client and server functions
 
   * Shell, command, and subsystem channels
   * Environment variables, terminal type, and window size
@@ -36,6 +36,7 @@ Features
   * Local and remote UNIX domain socket forwarding
   * X11 forwarding support on both the client and the server
   * SFTP protocol version 3 with OpenSSH extensions
+  * SCP protocol support, including third-party remote to remote copies
 
 * Multiple simultaneous sessions on a single SSH connection
 * Multiple SSH connections in a single event loop
@@ -73,7 +74,7 @@ License
 
 This package is released under the following terms:
 
-  Copyright (c) 2013-2016 by Ron Frederick <ronf at timeheart.net>.
+  Copyright (c) 2013-2017 by Ron Frederick <ronf at timeheart.net>.
   All rights reserved.
 
   This program and the accompanying materials are made available under
@@ -111,6 +112,9 @@ functionality:
 * Install bcrypt from https://pypi.python.org/pypi/bcrypt
   if you want support for OpenSSH private key encryption.
 
+* Install gssapi from https://pypi.python.org/pypi/gssapi if you
+  want support for GSSAPI key exchange and authentication on UNIX.
+
 * Install libsodium from https://github.com/jedisct1/libsodium
   and libnacl from https://pypi.python.org/pypi/libnacl if you want
   support for curve25519 Diffie Hellman key exchange, ed25519 keys,
@@ -120,22 +124,24 @@ functionality:
   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.
+  if you want support for using the Pageant agent or support for
+  GSSAPI key exchange and authentication on Windows.
 
 AsyncSSH defines the following optional PyPI extra packages to make it
 easy to install any or all of these dependencies:
 
   | bcrypt
+  | gssapi
   | libnacl
   | pypiwin32
 
-For example, to install bcrypt and libnacl, you can run:
+For example, to install bcrypt, gssapi, and libnacl on UNIX, you can run:
 
   ::
 
-    pip install 'asyncssh[bcrypt,libnacl]'
+    pip install 'asyncssh[bcrypt,gssapi,libnacl]'
 
-To install all three of these packages on a Windows system, you can run:
+To install bcrypt, libnacl, and pypiwin32 on Windows, you can run:
 
   ::
 
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..6233062
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,32 @@
+environment:
+  matrix:
+    - PYTHON: "C:\\Python34"
+      ARCH: win32
+
+    - PYTHON: "C:\\Python34-x64"
+      ARCH: win64
+
+    - PYTHON: "C:\\Python35"
+      ARCH: win32
+
+    - PYTHON: "C:\\Python35-x64"
+      ARCH: win64
+
+    - PYTHON: "C:\\Python36"
+      ARCH: win32
+
+    - PYTHON: "C:\\Python36-x64"
+      ARCH: win64
+
+install:
+  - "cd %PYTHON%"
+  - "curl https://www.timeheart.net/appveyor/%ARCH%/libsodium-18.dll -O"
+  - "curl https://www.timeheart.net/appveyor/%ARCH%/libnettle-6.dll -O"
+  - "curl https://www.timeheart.net/appveyor/%ARCH%/libhogweed-4.dll -O"
+  - "cd %APPVEYOR_BUILD_FOLDER%"
+  - "%PYTHON%\\python.exe -m pip install tox"
+
+build: off
+
+test_script:
+  - "%PYTHON%\\python.exe -m tox -e py"
diff --git a/asyncssh/__init__.py b/asyncssh/__init__.py
index 0785f7c..3e13b6b 100644
--- a/asyncssh/__init__.py
+++ b/asyncssh/__init__.py
@@ -49,7 +49,8 @@ from .misc import BreakReceived, SignalReceived, TerminalSizeChanged
 
 from .pbe import KeyEncryptionError
 
-from .process import SSHClientProcess, SSHCompletedProcess, ProcessError
+from .process import SSHClientProcess, SSHServerProcess
+from .process import SSHCompletedProcess, ProcessError
 from .process import DEVNULL, PIPE, STDOUT
 
 from .public_key import SSHKey, SSHKeyPair, SSHCertificate
@@ -61,12 +62,14 @@ from .public_key import read_private_key_list, read_public_key_list
 from .public_key import read_certificate_list
 from .public_key import load_keypairs, load_public_keys
 
+from .scp import scp
+
 from .session import SSHClientSession, SSHServerSession
 from .session import SSHTCPSession, SSHUNIXSession
 
 from .server import SSHServer
 
-from .sftp import SFTPClient, SFTPServer, SFTPFile, SFTPError
+from .sftp import SFTPClient, SFTPClientFile, SFTPServer, SFTPError
 from .sftp import SFTPAttrs, SFTPVFSAttrs, SFTPName
 from .sftp import SEEK_SET, SEEK_CUR, SEEK_END
 
diff --git a/asyncssh/auth.py b/asyncssh/auth.py
index 603ebad..178a9b2 100644
--- a/asyncssh/auth.py
+++ b/asyncssh/auth.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2013-2016 by Ron Frederick <ronf at timeheart.net>.
+# Copyright (c) 2013-2017 by Ron Frederick <ronf at timeheart.net>.
 # All rights reserved.
 #
 # This program and the accompanying materials are made available under
@@ -14,7 +14,9 @@
 
 import asyncio
 
-from .constants import DISC_PROTOCOL_ERROR
+from .constants import DEFAULT_LANG, DISC_PROTOCOL_ERROR
+from .gss import GSSError
+from .logging import logger
 from .misc import DisconnectError, PasswordChangeRequired
 from .packet import Boolean, Byte, String, UInt32, SSHPacketHandler
 from .saslprep import saslprep, SASLPrepError
@@ -22,15 +24,23 @@ from .saslprep import saslprep, SASLPrepError
 
 # pylint: disable=bad-whitespace
 
+# SSH message values for GSS auth
+MSG_USERAUTH_GSSAPI_RESPONSE          = 60
+MSG_USERAUTH_GSSAPI_TOKEN             = 61
+MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE = 63
+MSG_USERAUTH_GSSAPI_ERROR             = 64
+MSG_USERAUTH_GSSAPI_ERRTOK            = 65
+MSG_USERAUTH_GSSAPI_MIC               = 66
+
 # SSH message values for public key auth
-MSG_USERAUTH_PK_OK            = 60
+MSG_USERAUTH_PK_OK                    = 60
 
 # SSH message values for password auth
-MSG_USERAUTH_PASSWD_CHANGEREQ = 60
+MSG_USERAUTH_PASSWD_CHANGEREQ         = 60
 
 # SSH message values for 'keyboard-interactive' auth
-MSG_USERAUTH_INFO_REQUEST     = 60
-MSG_USERAUTH_INFO_RESPONSE    = 61
+MSG_USERAUTH_INFO_REQUEST             = 60
+MSG_USERAUTH_INFO_RESPONSE            = 61
 
 # pylint: enable=bad-whitespace
 
@@ -44,9 +54,7 @@ class _Auth(SSHPacketHandler):
 
     def __init__(self, conn, coro):
         self._conn = conn
-        self._coro = None
-
-        self.create_task(coro)
+        self._coro = conn.create_task(coro)
 
     def create_task(self, coro):
         """Create an asynchronous auth task"""
@@ -57,7 +65,7 @@ class _Auth(SSHPacketHandler):
     def cancel(self):
         """Cancel any authentication in progress"""
 
-        if self._coro:
+        if self._coro: # pragma: no branch
             self._coro.cancel()
             self._coro = None
 
@@ -101,6 +109,149 @@ class _ClientNullAuth(_ClientAuth):
         yield from self.send_request()
 
 
+class _ClientGSSKexAuth(_ClientAuth):
+    """Client side implementation of GSS key exchange auth"""
+
+    @asyncio.coroutine
+    def _start(self):
+        """Start client GSS key exchange authentication"""
+
+        if self._conn.gss_kex_auth_requested():
+            yield from self.send_request(key=self._conn.get_gss_context())
+        else:
+            self._conn.try_next_auth()
+
+
+class _ClientGSSMICAuth(_ClientAuth):
+    """Client side implementation of GSS MIC auth"""
+
+    def __init__(self, conn, method):
+        super().__init__(conn, method)
+
+        self._gss = None
+        self._got_error = False
+
+    @asyncio.coroutine
+    def _start(self):
+        """Start client GSS MIC authentication"""
+
+        if self._conn.gss_mic_auth_requested():
+            self._gss = self._conn.get_gss_context()
+            mechs = b''.join((String(mech) for mech in self._gss.mechs))
+            yield from self.send_request(UInt32(len(self._gss.mechs)), mechs)
+        else:
+            self._conn.try_next_auth()
+
+    def _finish(self):
+        """Finish client GSS MIC authentication"""
+
+        if self._gss.provides_integrity:
+            data = self._conn.get_userauth_request_data(self._method)
+
+            self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_MIC),
+                                   String(self._gss.sign(data)))
+        else:
+            self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE))
+
+    def _process_response(self, pkttype, packet):
+        """Process a GSS response from the server"""
+
+        # pylint: disable=unused-argument
+
+        mech = packet.get_string()
+        packet.check_end()
+
+        if mech not in self._gss.mechs:
+            raise DisconnectError(DISC_PROTOCOL_ERROR, 'Mechanism mismatch')
+
+        try:
+            token = self._gss.step()
+
+            self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_TOKEN),
+                                   String(token))
+
+            if self._gss.complete:
+                self._finish()
+        except GSSError as exc:
+            if exc.token:
+                self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_ERRTOK),
+                                       String(exc.token))
+
+            self._conn.try_next_auth()
+
+        return True
+
+    def _process_token(self, pkttype, packet):
+        """Process a GSS token from the server"""
+
+        # pylint: disable=unused-argument
+
+        token = packet.get_string()
+        packet.check_end()
+
+        try:
+            token = self._gss.step(token)
+
+            if token:
+                self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_TOKEN),
+                                       String(token))
+
+            if self._gss.complete:
+                self._finish()
+        except GSSError as exc:
+            if exc.token:
+                self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_ERRTOK),
+                                       String(exc.token))
+
+            self._conn.try_next_auth()
+
+        return True
+
+    def _process_error(self, pkttype, packet):
+        """Process a GSS error from the server"""
+
+        # pylint: disable=unused-argument
+
+        _ = packet.get_uint32()         # major_status
+        _ = packet.get_uint32()         # minor_status
+        msg = packet.get_string()
+        _ = packet.get_string()         # lang
+        packet.check_end()
+
+        logger.warning('GSS error from server: %s',
+                       msg.decode('utf-8', errors='ignore'))
+        self._got_error = True
+
+        return True
+
+    def _process_error_token(self, pkttype, packet):
+        """Process a GSS error token from the server"""
+
+        # pylint: disable=no-self-use,unused-argument
+
+        token = packet.get_string()
+        packet.check_end()
+
+        try:
+            self._gss.step(token)
+        except GSSError as exc:
+            if not self._got_error: # pragma: no cover
+                logger.warning('GSS error from server: %s', str(exc))
+
+        return True
+
+    # pylint: disable=bad-whitespace
+
+    packet_handlers = {
+        MSG_USERAUTH_GSSAPI_RESPONSE: _process_response,
+        MSG_USERAUTH_GSSAPI_TOKEN:    _process_token,
+        MSG_USERAUTH_GSSAPI_ERROR:    _process_error,
+        MSG_USERAUTH_GSSAPI_ERRTOK:   _process_error_token
+    }
+
+    # pylint: enable=bad-whitespace
+
+
 class _ClientPublicKeyAuth(_ClientAuth):
     """Client side implementation of public key auth"""
 
@@ -297,8 +448,9 @@ class _ClientPasswordAuth(_ClientAuth):
 class _ServerAuth(_Auth):
     """Parent class for server authentication"""
 
-    def __init__(self, conn, username, packet):
+    def __init__(self, conn, username, method, packet):
         self._username = username
+        self._method = method
 
         super().__init__(conn, self._start(packet))
 
@@ -332,10 +484,176 @@ class _ServerNullAuth(_ServerAuth):
 
     @asyncio.coroutine
     def _start(self, packet):
-        """Always fail null server authentication"""
+        """Supported always returns false, so we never get here"""
+
+
+class _ServerGSSKexAuth(_ServerAuth):
+    """Server side implementation of GSS key exchange auth"""
+
+    def __init__(self, conn, username, method, packet):
+        super().__init__(conn, username, method, packet)
+
+        self._gss = conn.get_gss_context()
+
+    @classmethod
+    def supported(cls, conn):
+        """Return whether GSS key exchange authentication is supported"""
+
+        return conn.gss_kex_auth_supported()
+
+    @asyncio.coroutine
+    def _start(self, packet):
+        """Start server GSS key exchange authentication"""
+
+        mic = packet.get_string()
+        packet.check_end()
+
+        data = self._conn.get_userauth_request_data(self._method)
+
+        if (self._gss.complete and self._gss.verify(data, mic) and
+                (yield from self._conn.validate_gss_principal(self._username,
+                                                              self._gss.user,
+                                                              self._gss.host))):
+            self.send_success()
+        else:
+            self.send_failure()
+
+
+class _ServerGSSMICAuth(_ServerAuth):
+    """Server side implementation of GSS MIC auth"""
+
+    def __init__(self, conn, username, method, packet):
+        super().__init__(conn, username, method, packet)
+
+        self._gss = conn.get_gss_context()
+
+    @classmethod
+    def supported(cls, conn):
+        """Return whether GSS MIC authentication is supported"""
+
+        return conn.gss_mic_auth_supported()
+
+    @asyncio.coroutine
+    def _start(self, packet):
+        """Start server GSS MIC authentication"""
+
+        mechs = set()
 
+        n = packet.get_uint32()
+        for _ in range(n):
+            mechs.add(packet.get_string())
         packet.check_end()
-        self.send_failure()
+
+        match = None
+
+        for mech in self._gss.mechs:
+            if mech in mechs:
+                match = mech
+                break
+
+        if not match:
+            self.send_failure()
+            return
+
+        self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_RESPONSE),
+                               String(match))
+
+    @asyncio.coroutine
+    def _finish(self):
+        """Finish server GSS MIC authentication"""
+
+        if (yield from self._conn.validate_gss_principal(self._username,
+                                                         self._gss.user,
+                                                         self._gss.host)):
+            self.send_success()
+        else:
+            self.send_failure()
+
+    def _process_token(self, pkttype, packet):
+        """Process a GSS token from the client"""
+
+        # pylint: disable=unused-argument
+
+        token = packet.get_string()
+        packet.check_end()
+
+        try:
+            token = self._gss.step(token)
+
+            if token:
+                self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_TOKEN),
+                                       String(token))
+        except GSSError as exc:
+            self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_ERROR),
+                                   UInt32(exc.maj_code), UInt32(exc.min_code),
+                                   String(str(exc)), String(DEFAULT_LANG))
+
+            if exc.token:
+                self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_ERRTOK),
+                                       String(exc.token))
+
+            self.send_failure()
+
+        return True
+
+    def _process_exchange_complete(self, pkttype, packet):
+        """Process a GSS exchange complete message from the client"""
+
+        # pylint: disable=unused-argument
+
+        packet.check_end()
+
+        if self._gss.complete and not self._gss.provides_integrity:
+            self.create_task(self._finish())
+        else:
+            self.send_failure()
+
+        return True
+
+    def _process_error_token(self, pkttype, packet):
+        """Process a GSS error token from the client"""
+
+        # pylint: disable=unused-argument
+
+        token = packet.get_string()
+        packet.check_end()
+
+        try:
+            self._gss.step(token)
+        except GSSError as exc:
+            logger.warning('GSS error from client: %s', str(exc))
+
+        return True
+
+    def _process_mic(self, pkttype, packet):
+        """Process a GSS MIC from the client"""
+
+        # pylint: disable=unused-argument
+
+        mic = packet.get_string()
+        packet.check_end()
+
+        data = self._conn.get_userauth_request_data(self._method)
+
+        if (self._gss.complete and self._gss.provides_integrity and
+                self._gss.verify(data, mic)):
+            self.create_task(self._finish())
+        else:
+            self.send_failure()
+
+        return True
+
+    # pylint: disable=bad-whitespace
+
+    packet_handlers = {
+        MSG_USERAUTH_GSSAPI_TOKEN:             _process_token,
+        MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE: _process_exchange_complete,
+        MSG_USERAUTH_GSSAPI_ERRTOK:            _process_error_token,
+        MSG_USERAUTH_GSSAPI_MIC:               _process_mic
+    }
+
+    # pylint: enable=bad-whitespace
+
 
 class _ServerPublicKeyAuth(_ServerAuth):
     """Server side implementation of public key auth"""
@@ -452,6 +770,7 @@ class _ServerKbdIntAuth(_ServerAuth):
         packet.check_end()
 
         self.create_task(self._validate_response(responses))
+        return True
 
     packet_handlers = {
         MSG_USERAUTH_INFO_RESPONSE: _process_info_response
@@ -534,16 +853,21 @@ def get_server_auth_methods(conn):
 def lookup_server_auth(conn, username, method, packet):
     """Look up the server authentication method to use"""
 
-    if method in _auth_methods:
-        return _server_auth_handlers[method](conn, username, packet)
+    handler = _server_auth_handlers.get(method)
+
+    if handler and handler.supported(conn):
+        return handler(conn, username, method, packet)
     else:
         conn.send_userauth_failure(False)
         return None
 
+
 # pylint: disable=bad-whitespace
 
 _auth_method_list = (
     (b'none',                 _ClientNullAuth,      _ServerNullAuth),
+    (b'gssapi-keyex',         _ClientGSSKexAuth,    _ServerGSSKexAuth),
+    (b'gssapi-with-mic',      _ClientGSSMICAuth,    _ServerGSSMICAuth),
     (b'publickey',            _ClientPublicKeyAuth, _ServerPublicKeyAuth),
     (b'keyboard-interactive', _ClientKbdIntAuth,    _ServerKbdIntAuth),
     (b'password',             _ClientPasswordAuth,  _ServerPasswordAuth)
diff --git a/asyncssh/channel.py b/asyncssh/channel.py
index 12b9520..a32080b 100644
--- a/asyncssh/channel.py
+++ b/asyncssh/channel.py
@@ -111,6 +111,11 @@ class SSHChannel(SSHPacketHandler):
 
         return self._read_datatypes
 
+    def get_write_datatypes(self):
+        """Return the legal write data types for this channel"""
+
+        return self._write_datatypes
+
     def _cleanup(self, exc=None):
         """Clean up this channel"""
 
diff --git a/asyncssh/client.py b/asyncssh/client.py
index 0e38749..7ffe5ab 100644
--- a/asyncssh/client.py
+++ b/asyncssh/client.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2013-2016 by Ron Frederick <ronf at timeheart.net>.
+# Copyright (c) 2013-2017 by Ron Frederick <ronf at timeheart.net>.
 # All rights reserved.
 #
 # This program and the accompanying materials are made available under
@@ -39,6 +39,13 @@ class SSHClient:
        :meth:`password_changed` or :meth:`password_change_failed` depending
        on whether the password change is successful.
 
+       .. note:: The authentication callbacks described here can be
+                 defined as coroutines. However, they may be cancelled if
+                 they are running when the SSH connection is closed by
+                 the server. If they attempt to catch the CancelledError
+                 exception to perform cleanup, they should make sure to
+                 re-raise it to allow AsyncSSH to finish its own cleanup.
+
     """
 
     # pylint: disable=no-self-use,unused-argument
diff --git a/asyncssh/connection.py b/asyncssh/connection.py
index 4cf01c9..753ffc2 100644
--- a/asyncssh/connection.py
+++ b/asyncssh/connection.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2013-2016 by Ron Frederick <ronf at timeheart.net>.
+# Copyright (c) 2013-2017 by Ron Frederick <ronf at timeheart.net>.
 # All rights reserved.
 #
 # This program and the accompanying materials are made available under
@@ -64,7 +64,9 @@ from .constants import OPEN_UNKNOWN_CHANNEL_TYPE
 
 from .forward import SSHForwarder
 
-from .kex import get_kex_algs, get_kex
+from .gss import GSSClient, GSSServer, GSSError
+
+from .kex import get_kex_algs, expand_kex_algs, get_kex
 
 from .known_hosts import match_known_hosts
 
@@ -76,24 +78,24 @@ from .logging import logger
 from .mac import get_mac_algs, get_mac_params, get_mac
 
 from .misc import ChannelOpenError, DisconnectError, PasswordChangeRequired
-from .misc import async_context_manager, ensure_future, ip_address
+from .misc import async_context_manager, create_task, ip_address
 from .misc import load_default_keypairs, map_handler_name
 
 from .packet import Boolean, Byte, NameList, String, UInt32, UInt64
 from .packet import PacketDecodeError, SSHPacket, SSHPacketHandler
 
-from .process import PIPE, SSHClientProcess
+from .process import PIPE, SSHClientProcess, SSHServerProcess
 
 from .public_key import CERT_TYPE_HOST, CERT_TYPE_USER, KeyImportError
 from .public_key import get_public_key_algs, get_certificate_algs
 from .public_key import decode_ssh_public_key, decode_ssh_certificate
-from .public_key import load_keypairs, load_public_keys
+from .public_key import load_keypairs
 
 from .saslprep import saslprep, SASLPrepError
 
 from .server import SSHServer
 
-from .sftp import SFTPClient, SFTPServer, SFTPClientHandler
+from .sftp import SFTPServer, start_sftp_client
 
 from .stream import SSHClientStreamSession, SSHServerStreamSession
 from .stream import SSHTCPStreamSession, SSHUNIXStreamSession
@@ -244,6 +246,10 @@ class SSHConnection(SSHPacketHandler):
         self._kex_complete = False
         self._ignore_first_kex = False
 
+        self._gss = None
+        self._gss_kex_auth = False
+        self._gss_mic_auth = False
+
         self._rekey_bytes = rekey_bytes
         self._rekey_bytes_sent = 0
         self._rekey_seconds = rekey_seconds
@@ -366,6 +372,8 @@ class SSHConnection(SSHPacketHandler):
         # pylint: disable=broad-except
         try:
             yield from coro
+        except asyncio.CancelledError:
+            pass
         except DisconnectError as exc:
             self._send_disconnect(exc.code, exc.reason, exc.lang)
             self._force_close(exc)
@@ -377,7 +385,7 @@ class SSHConnection(SSHPacketHandler):
     def create_task(self, coro):
         """Create an asynchronous task which catches and reports errors"""
 
-        task = ensure_future(self._run_task(coro), loop=self._loop)
+        task = create_task(self._run_task(coro), loop=self._loop)
         self._tasks.add(task)
         return task
 
@@ -416,6 +424,7 @@ class SSHConnection(SSHPacketHandler):
         self._transport = transport
 
         sock = transport.get_extra_info('socket')
+        sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
         sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
 
         peername = transport.get_extra_info('peername')
@@ -511,6 +520,16 @@ class SSHConnection(SSHPacketHandler):
 
         del self._channels[recv_chan]
 
+    def get_gss_context(self):
+        """Return the GSS context associated with this connection"""
+
+        return self._gss
+
+    def enable_gss_kex_auth(self):
+        """Enable GSS key exchange authentication"""
+
+        self._gss_kex_auth = True
+
     def _choose_alg(self, alg_type, local_algs, remote_algs):
         """Choose a common algorithm from the client & server lists
 
@@ -791,9 +810,13 @@ class SSHConnection(SSHPacketHandler):
         self._rekey_bytes_sent = 0
         self._rekey_time = time.monotonic() + self._rekey_seconds
 
+        gss_mechs = self._gss.mechs if self._gss else []
+        kex_algs = expand_kex_algs(self._kex_algs, gss_mechs,
+                                   bool(self._server_host_key_algs))
+
         cookie = os.urandom(16)
-        kex_algs = NameList(self._kex_algs + self._get_ext_info_kex_alg())
-        host_key_algs = NameList(self._server_host_key_algs)
+        kex_algs = NameList(kex_algs + self._get_ext_info_kex_alg())
+        host_key_algs = NameList(self._server_host_key_algs or [b'null'])
         enc_algs = NameList(self._enc_algs)
         mac_algs = NameList(self._mac_algs)
         cmp_algs = NameList(self._cmp_algs)
@@ -945,12 +968,23 @@ class SSHConnection(SSHPacketHandler):
         self._next_service = service
         self.send_packet(Byte(MSG_SERVICE_REQUEST), String(service))
 
+    def _get_userauth_request_packet(self, method, args):
+        """Get packet data for a user authentication request"""
+
+        return b''.join((Byte(MSG_USERAUTH_REQUEST), String(self._username),
+                         String(_CONNECTION_SERVICE), String(method)) + args)
+
+    def get_userauth_request_data(self, method, *args):
+        """Get signature data for a user authentication request"""
+
+        return (String(self._session_id) +
+                self._get_userauth_request_packet(method, args))
+
     @asyncio.coroutine
     def send_userauth_request(self, method, *args, key=None):
         """Send a user authentication request"""
 
-        packet = b''.join((Byte(MSG_USERAUTH_REQUEST), String(self._username),
-                           String(_CONNECTION_SERVICE), String(method)) + args)
+        packet = self._get_userauth_request_packet(method, args)
 
         if key:
             sig = key.sign(String(self._session_id) + packet)
@@ -1178,8 +1212,8 @@ class SSHConnection(SSHPacketHandler):
                                   'Key exchange already in progress')
 
         _ = packet.get_bytes(16)                        # cookie
-        kex_algs = packet.get_namelist()
-        server_host_key_algs = packet.get_namelist()
+        peer_kex_algs = packet.get_namelist()
+        peer_host_key_algs = packet.get_namelist()
         enc_algs_cs = packet.get_namelist()
         enc_algs_sc = packet.get_namelist()
         mac_algs_cs = packet.get_namelist()
@@ -1195,13 +1229,7 @@ class SSHConnection(SSHPacketHandler):
         if self.is_server():
             self._client_kexinit = packet.get_consumed_payload()
 
-            # This method is only in SSHServerConnection
-            # pylint: disable=no-member
-            if not self._choose_server_host_key(server_host_key_algs):
-                raise DisconnectError(DISC_KEY_EXCHANGE_FAILED, 'Unable to '
-                                      'find compatible server host key')
-
-            if b'ext-info-c' in kex_algs:
+            if b'ext-info-c' in peer_kex_algs:
                 self._can_send_ext_info = True
         else:
             self._server_kexinit = packet.get_consumed_payload()
@@ -1211,10 +1239,25 @@ class SSHConnection(SSHPacketHandler):
         else:
             self._send_kexinit()
 
-        kex_alg = self._choose_alg('key exchange', self._kex_algs, kex_algs)
+        if self._gss:
+            self._gss.reset()
+
+        gss_mechs = self._gss.mechs if self._gss else []
+        kex_algs = expand_kex_algs(self._kex_algs, gss_mechs,
+                                   bool(self._server_host_key_algs))
+
+        kex_alg = self._choose_alg('key exchange', kex_algs, peer_kex_algs)
         self._kex = get_kex(self, kex_alg)
         self._ignore_first_kex = (first_kex_follows and
-                                  self._kex.algorithm != kex_algs[0])
+                                  self._kex.algorithm != peer_kex_algs[0])
+
+        if self.is_server():
+            # This method is only in SSHServerConnection
+            # pylint: disable=no-member
+            if (not self._choose_server_host_key(peer_host_key_algs) and
+                    not kex_alg.startswith(b'gss-')):
+                raise DisconnectError(DISC_KEY_EXCHANGE_FAILED, 'Unable '
+                                      'to find compatible server host key')
 
         self._enc_alg_cs = self._choose_alg('encryption', self._enc_algs,
                                             enc_algs_cs)
@@ -1229,6 +1272,9 @@ class SSHConnection(SSHPacketHandler):
         self._cmp_alg_sc = self._choose_alg('compression', self._cmp_algs,
                                             cmp_algs_sc)
 
+        if self.is_client():
+            self._kex.start()
+
     def _process_newkeys(self, pkttype, packet):
         """Process a new keys message, finishing a key exchange"""
 
@@ -1873,8 +1919,8 @@ class SSHClientConnection(SSHConnection):
     def __init__(self, client_factory, loop, client_version, kex_algs,
                  encryption_algs, mac_algs, compression_algs, signature_algs,
                  rekey_bytes, rekey_seconds, host, port, known_hosts,
-                 username, password, client_keys, agent, agent_path,
-                 auth_waiter):
+                 username, password, client_keys, gss_host, gss_delegate_creds,
+                 agent, agent_path, auth_waiter):
         super().__init__(client_factory, loop, client_version, kex_algs,
                          encryption_algs, mac_algs, compression_algs,
                          signature_algs, rekey_bytes, rekey_seconds,
@@ -1890,6 +1936,13 @@ class SSHClientConnection(SSHConnection):
         self._agent_path = agent_path
         self._auth_waiter = auth_waiter
 
+        if gss_host:
+            try:
+                self._gss = GSSClient(gss_host, gss_delegate_creds)
+                self._gss_mic_auth = True
+            except GSSError:
+                pass
+
         self._server_host_keys = set()
         self._server_ca_keys = set()
         self._revoked_server_keys = set()
@@ -1906,24 +1959,16 @@ class SSHClientConnection(SSHConnection):
             self._server_host_keys = None
             self._server_ca_keys = None
             self._revoked_server_keys = None
-            self._server_host_key_algs = (get_public_key_algs() +
-                                          get_certificate_algs())
+            self._server_host_key_algs = None
         else:
             if not self._known_hosts:
                 self._known_hosts = os.path.join(os.path.expanduser('~'),
                                                  '.ssh', 'known_hosts')
 
-            if isinstance(self._known_hosts, (str, bytes)):
-                server_host_keys, server_ca_keys, revoked_server_keys = \
-                    match_known_hosts(self._known_hosts, self._host,
-                                      self._peer_addr, self._port)
-            else:
-                server_host_keys, server_ca_keys, revoked_server_keys = \
-                    self._known_hosts
+            known_hosts = match_known_hosts(self._known_hosts, self._host,
+                                            self._peer_addr, self._port)
 
-                server_host_keys = load_public_keys(server_host_keys)
-                server_ca_keys = load_public_keys(server_ca_keys)
-                revoked_server_keys = load_public_keys(revoked_server_keys)
+            server_host_keys, server_ca_keys, revoked_server_keys = known_hosts
 
             self._server_host_keys = set()
             self._server_host_key_algs = []
@@ -1940,8 +1985,14 @@ class SSHClientConnection(SSHConnection):
                     self._server_host_key_algs.extend(key.sig_algorithms)
 
         if not self._server_host_key_algs:
-            raise DisconnectError(DISC_HOST_KEY_NOT_VERIFYABLE,
-                                  'No trusted server host keys available')
+            if self._known_hosts is None:
+                self._server_host_key_algs = (get_public_key_algs() +
+                                              get_certificate_algs())
+            elif self._gss:
+                self._server_host_key_algs = [b'null']
+            else:
+                raise DisconnectError(DISC_HOST_KEY_NOT_VERIFYABLE,
+                                      'No trusted server host keys available')
 
     def _cleanup(self, exc):
         """Clean up this client connection"""
@@ -2028,6 +2079,24 @@ class SSHClientConnection(SSHConnection):
         self._force_close(DisconnectError(DISC_NO_MORE_AUTH_METHODS_AVAILABLE,
                                           'Permission denied'))
 
+    def gss_kex_auth_requested(self):
+        """Return whether to allow GSS key exchange authentication or not"""
+
+        if self._gss_kex_auth:
+            self._gss_kex_auth = False
+            return True
... 12173 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