asyncssh 2.20.0__tar.gz → 2.21.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {asyncssh-2.20.0 → asyncssh-2.21.1}/.github/workflows/run_tests.yml +12 -1
- {asyncssh-2.20.0/asyncssh.egg-info → asyncssh-2.21.1}/PKG-INFO +4 -3
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/auth.py +12 -12
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/channel.py +12 -5
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/connection.py +119 -28
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/forward.py +2 -1
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/kex_dh.py +17 -1
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/misc.py +28 -4
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/public_key.py +147 -97
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/scp.py +69 -30
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/server.py +54 -21
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/sftp.py +367 -119
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/stream.py +3 -5
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/version.py +1 -1
- {asyncssh-2.20.0 → asyncssh-2.21.1/asyncssh.egg-info}/PKG-INFO +4 -3
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh.egg-info/requires.txt +1 -1
- {asyncssh-2.20.0 → asyncssh-2.21.1}/docs/changes.rst +59 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/pyproject.toml +1 -1
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_agent.py +3 -4
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_auth.py +2 -2
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_channel.py +16 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_connection.py +18 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_connection_auth.py +9 -6
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_forward.py +63 -6
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_kex.py +44 -11
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_public_key.py +5 -16
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_sftp.py +121 -9
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_stream.py +33 -1
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_tuntap.py +59 -1
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/util.py +0 -2
- {asyncssh-2.20.0 → asyncssh-2.21.1}/.coveragerc +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/.gitignore +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/.readthedocs.yaml +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/CONTRIBUTING.rst +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/COPYRIGHT +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/LICENSE +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/MANIFEST.in +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/README.rst +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/__init__.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/agent.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/agent_unix.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/agent_win32.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/asn1.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/auth_keys.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/client.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/compression.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/config.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/constants.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/crypto/__init__.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/crypto/chacha.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/crypto/cipher.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/crypto/dh.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/crypto/dsa.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/crypto/ec.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/crypto/ec_params.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/crypto/ed.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/crypto/kdf.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/crypto/misc.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/crypto/pq.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/crypto/rsa.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/crypto/umac.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/crypto/x509.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/dsa.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/ecdsa.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/eddsa.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/editor.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/encryption.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/gss.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/gss_unix.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/gss_win32.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/kex.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/kex_rsa.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/keysign.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/known_hosts.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/listener.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/logging.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/mac.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/packet.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/pattern.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/pbe.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/pkcs11.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/process.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/py.typed +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/rsa.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/saslprep.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/session.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/sk.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/sk_ecdsa.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/sk_eddsa.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/socks.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/subprocess.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/tuntap.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh/x11.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh.egg-info/SOURCES.txt +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh.egg-info/dependency_links.txt +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/asyncssh.egg-info/top_level.txt +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/docs/_templates/sidebarbottom.html +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/docs/_templates/sidebartop.html +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/docs/api.rst +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/docs/conf.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/docs/contributing.rst +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/docs/index.rst +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/docs/requirements.txt +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/docs/rftheme/layout.html +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/docs/rftheme/static/rftheme.css_t +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/docs/rftheme/theme.conf +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/docs/rtd-req.txt +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/callback_client.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/callback_client2.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/callback_client3.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/callback_math_server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/chat_server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/check_exit_status.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/chroot_sftp_server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/direct_client.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/direct_server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/editor.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/gather_results.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/listening_client.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/local_forwarding_client.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/local_forwarding_client2.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/local_forwarding_server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/math_client.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/math_server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/redirect_input.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/redirect_local_pipe.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/redirect_remote_pipe.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/redirect_server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/remote_forwarding_client.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/remote_forwarding_client2.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/remote_forwarding_server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/reverse_client.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/reverse_server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/scp_client.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/set_environment.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/set_terminal.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/sftp_client.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/show_environment.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/show_terminal.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/simple_cert_server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/simple_client.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/simple_keyed_server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/simple_scp_server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/simple_server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/simple_sftp_server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/stream_direct_client.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/stream_direct_server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/examples/stream_listening_client.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/mypy.ini +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/pylintrc +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/setup.cfg +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/__init__.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/gss_stub.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/gssapi_stub.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/keysign_stub.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/pkcs11_stub.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/server.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/sk_stub.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/sspi_stub.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_asn1.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_auth_keys.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_compression.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_config.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_editor.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_encryption.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_known_hosts.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_logging.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_mac.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_packet.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_pkcs11.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_process.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_saslprep.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_sk.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_subprocess.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_x11.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tests/test_x509.py +0 -0
- {asyncssh-2.20.0 → asyncssh-2.21.1}/tox.ini +0 -0
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
name: Run tests
|
|
2
2
|
on: [push, pull_request]
|
|
3
3
|
|
|
4
|
+
permissions:
|
|
5
|
+
contents: read
|
|
6
|
+
|
|
4
7
|
jobs:
|
|
5
8
|
run-tests:
|
|
6
9
|
name: Run tests
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
actions: write
|
|
7
13
|
strategy:
|
|
8
14
|
fail-fast: false
|
|
9
15
|
matrix:
|
|
10
|
-
os: [ubuntu-latest, macos-latest, windows-
|
|
16
|
+
os: [ubuntu-latest, macos-latest, windows-2022]
|
|
11
17
|
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
12
18
|
include:
|
|
13
19
|
- os: macos-latest
|
|
@@ -124,6 +130,8 @@ jobs:
|
|
|
124
130
|
runs-on: ubuntu-latest
|
|
125
131
|
needs: run-tests
|
|
126
132
|
if: ${{ always() }}
|
|
133
|
+
permissions:
|
|
134
|
+
actions: write
|
|
127
135
|
steps:
|
|
128
136
|
- name: Merge coverage
|
|
129
137
|
uses: actions/upload-artifact/merge@v4
|
|
@@ -137,6 +145,9 @@ jobs:
|
|
|
137
145
|
runs-on: ubuntu-latest
|
|
138
146
|
needs: merge-coverage
|
|
139
147
|
if: ${{ always() }}
|
|
148
|
+
permissions:
|
|
149
|
+
contents: read
|
|
150
|
+
actions: read
|
|
140
151
|
steps:
|
|
141
152
|
- uses: actions/checkout@v4
|
|
142
153
|
- uses: actions/setup-python@v5
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: asyncssh
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.21.1
|
|
4
4
|
Summary: AsyncSSH: Asynchronous SSHv2 client and server library
|
|
5
5
|
Author-email: Ron Frederick <ronf@timeheart.net>
|
|
6
6
|
License: EPL-2.0 OR GPL-2.0-or-later
|
|
@@ -32,7 +32,7 @@ Requires-Dist: typing_extensions>=4.0.0
|
|
|
32
32
|
Provides-Extra: bcrypt
|
|
33
33
|
Requires-Dist: bcrypt>=3.1.3; extra == "bcrypt"
|
|
34
34
|
Provides-Extra: fido2
|
|
35
|
-
Requires-Dist: fido2
|
|
35
|
+
Requires-Dist: fido2<2,>=0.9.2; extra == "fido2"
|
|
36
36
|
Provides-Extra: gssapi
|
|
37
37
|
Requires-Dist: gssapi>=1.2.0; extra == "gssapi"
|
|
38
38
|
Provides-Extra: libnacl
|
|
@@ -43,6 +43,7 @@ Provides-Extra: pyopenssl
|
|
|
43
43
|
Requires-Dist: pyOpenSSL>=23.0.0; extra == "pyopenssl"
|
|
44
44
|
Provides-Extra: pywin32
|
|
45
45
|
Requires-Dist: pywin32>=227; extra == "pywin32"
|
|
46
|
+
Dynamic: license-file
|
|
46
47
|
|
|
47
48
|
.. image:: https://readthedocs.org/projects/asyncssh/badge/?version=latest
|
|
48
49
|
:target: https://asyncssh.readthedocs.io/en/latest/?badge=latest
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c) 2013-
|
|
1
|
+
# Copyright (c) 2013-2025 by Ron Frederick <ronf@timeheart.net> and others.
|
|
2
2
|
#
|
|
3
3
|
# This program and the accompanying materials are made available under
|
|
4
4
|
# the terms of the Eclipse Public License v2.0 which accompanies this
|
|
@@ -548,10 +548,10 @@ class ServerAuth(Auth):
|
|
|
548
548
|
|
|
549
549
|
self._conn.send_userauth_failure(partial_success)
|
|
550
550
|
|
|
551
|
-
def send_success(self) -> None:
|
|
551
|
+
async def send_success(self) -> None:
|
|
552
552
|
"""Send a user authentication success response"""
|
|
553
553
|
|
|
554
|
-
self._conn.send_userauth_success()
|
|
554
|
+
await self._conn.send_userauth_success()
|
|
555
555
|
|
|
556
556
|
|
|
557
557
|
class _ServerNullAuth(ServerAuth):
|
|
@@ -596,7 +596,7 @@ class _ServerGSSKexAuth(ServerAuth):
|
|
|
596
596
|
(await self._conn.validate_gss_principal(self._username,
|
|
597
597
|
self._gss.user,
|
|
598
598
|
self._gss.host))):
|
|
599
|
-
self.send_success()
|
|
599
|
+
await self.send_success()
|
|
600
600
|
else:
|
|
601
601
|
self.send_failure()
|
|
602
602
|
|
|
@@ -650,7 +650,7 @@ class _ServerGSSMICAuth(ServerAuth):
|
|
|
650
650
|
if (await self._conn.validate_gss_principal(self._username,
|
|
651
651
|
self._gss.user,
|
|
652
652
|
self._gss.host)):
|
|
653
|
-
self.send_success()
|
|
653
|
+
await self.send_success()
|
|
654
654
|
else:
|
|
655
655
|
self.send_failure()
|
|
656
656
|
|
|
@@ -757,7 +757,7 @@ class _ServerHostBasedAuth(ServerAuth):
|
|
|
757
757
|
key_data, client_host,
|
|
758
758
|
client_username,
|
|
759
759
|
msg, signature)):
|
|
760
|
-
self.send_success()
|
|
760
|
+
await self.send_success()
|
|
761
761
|
else:
|
|
762
762
|
self.send_failure()
|
|
763
763
|
|
|
@@ -795,7 +795,7 @@ class _ServerPublicKeyAuth(ServerAuth):
|
|
|
795
795
|
if (await self._conn.validate_public_key(self._username, key_data,
|
|
796
796
|
msg, signature)):
|
|
797
797
|
if sig_present:
|
|
798
|
-
self.send_success()
|
|
798
|
+
await self.send_success()
|
|
799
799
|
else:
|
|
800
800
|
self.send_packet(MSG_USERAUTH_PK_OK, String(algorithm),
|
|
801
801
|
String(key_data))
|
|
@@ -832,9 +832,9 @@ class _ServerKbdIntAuth(ServerAuth):
|
|
|
832
832
|
|
|
833
833
|
challenge = await self._conn.get_kbdint_challenge(self._username,
|
|
834
834
|
lang, submethods)
|
|
835
|
-
self._send_challenge(challenge)
|
|
835
|
+
await self._send_challenge(challenge)
|
|
836
836
|
|
|
837
|
-
def _send_challenge(self, challenge: KbdIntChallenge) -> None:
|
|
837
|
+
async def _send_challenge(self, challenge: KbdIntChallenge) -> None:
|
|
838
838
|
"""Send a keyboard interactive authentication request"""
|
|
839
839
|
|
|
840
840
|
if isinstance(challenge, (tuple, list)):
|
|
@@ -848,7 +848,7 @@ class _ServerKbdIntAuth(ServerAuth):
|
|
|
848
848
|
String(instruction), String(lang),
|
|
849
849
|
UInt32(num_prompts), *prompts_bytes)
|
|
850
850
|
elif challenge:
|
|
851
|
-
self.send_success()
|
|
851
|
+
await self.send_success()
|
|
852
852
|
else:
|
|
853
853
|
self.send_failure()
|
|
854
854
|
|
|
@@ -857,7 +857,7 @@ class _ServerKbdIntAuth(ServerAuth):
|
|
|
857
857
|
|
|
858
858
|
next_challenge = \
|
|
859
859
|
await self._conn.validate_kbdint_response(self._username, responses)
|
|
860
|
-
self._send_challenge(next_challenge)
|
|
860
|
+
await self._send_challenge(next_challenge)
|
|
861
861
|
|
|
862
862
|
def _process_info_response(self, _pkttype: int, _pktid: int,
|
|
863
863
|
packet: SSHPacket) -> None:
|
|
@@ -922,7 +922,7 @@ class _ServerPasswordAuth(ServerAuth):
|
|
|
922
922
|
await self._conn.validate_password(self._username, password)
|
|
923
923
|
|
|
924
924
|
if result:
|
|
925
|
-
self.send_success()
|
|
925
|
+
await self.send_success()
|
|
926
926
|
else:
|
|
927
927
|
self.send_failure()
|
|
928
928
|
except PasswordChangeRequired as exc:
|
|
@@ -26,6 +26,7 @@ import codecs
|
|
|
26
26
|
import inspect
|
|
27
27
|
import re
|
|
28
28
|
import signal as _signal
|
|
29
|
+
import sys
|
|
29
30
|
from types import MappingProxyType
|
|
30
31
|
from typing import TYPE_CHECKING, Any, AnyStr, Awaitable, Callable
|
|
31
32
|
from typing import Dict, Generic, Iterable, List, Mapping, Optional
|
|
@@ -225,7 +226,13 @@ class SSHChannel(Generic[AnyStr], SSHPacketHandler):
|
|
|
225
226
|
self._request_waiters = []
|
|
226
227
|
|
|
227
228
|
if self._session is not None:
|
|
228
|
-
|
|
229
|
+
# pylint: disable=broad-except
|
|
230
|
+
try:
|
|
231
|
+
self._session.connection_lost(exc)
|
|
232
|
+
except Exception:
|
|
233
|
+
self.logger.debug1('Uncaught exception in session ignored',
|
|
234
|
+
exc_info=sys.exc_info)
|
|
235
|
+
|
|
229
236
|
self._session = None
|
|
230
237
|
|
|
231
238
|
self._close_event.set()
|
|
@@ -2058,16 +2065,16 @@ class SSHTCPChannel(SSHForwardChannel, Generic[AnyStr]):
|
|
|
2058
2065
|
SSHTCPSession[AnyStr]:
|
|
2059
2066
|
"""Create a new outbound TCP session"""
|
|
2060
2067
|
|
|
2061
|
-
return
|
|
2062
|
-
|
|
2068
|
+
return await self._open_tcp(session_factory, b'direct-tcpip',
|
|
2069
|
+
host, port, orig_host, orig_port)
|
|
2063
2070
|
|
|
2064
2071
|
async def accept(self, session_factory: SSHTCPSessionFactory[AnyStr],
|
|
2065
2072
|
host: str, port: int, orig_host: str,
|
|
2066
2073
|
orig_port: int) -> SSHTCPSession[AnyStr]:
|
|
2067
2074
|
"""Create a new forwarded TCP session"""
|
|
2068
2075
|
|
|
2069
|
-
return
|
|
2070
|
-
|
|
2076
|
+
return await self._open_tcp(session_factory, b'forwarded-tcpip',
|
|
2077
|
+
host, port, orig_host, orig_port)
|
|
2071
2078
|
|
|
2072
2079
|
def set_inbound_peer_names(self, dest_host: str, dest_port: int,
|
|
2073
2080
|
orig_host: str, orig_port: int) -> None:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c) 2013-
|
|
1
|
+
# Copyright (c) 2013-2025 by Ron Frederick <ronf@timeheart.net> and others.
|
|
2
2
|
#
|
|
3
3
|
# This program and the accompanying materials are made available under
|
|
4
4
|
# the terms of the Eclipse Public License v2.0 which accompanies this
|
|
@@ -1075,7 +1075,13 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
1075
1075
|
self._wait = None
|
|
1076
1076
|
|
|
1077
1077
|
if self._owner: # pragma: no branch
|
|
1078
|
-
|
|
1078
|
+
# pylint: disable=broad-except
|
|
1079
|
+
try:
|
|
1080
|
+
self._owner.connection_lost(exc)
|
|
1081
|
+
except Exception:
|
|
1082
|
+
self.logger.debug1('Uncaught exception in owner ignored',
|
|
1083
|
+
exc_info=sys.exc_info)
|
|
1084
|
+
|
|
1079
1085
|
self._owner = None
|
|
1080
1086
|
|
|
1081
1087
|
self._cancel_login_timer()
|
|
@@ -1196,7 +1202,7 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
1196
1202
|
|
|
1197
1203
|
return self._server
|
|
1198
1204
|
|
|
1199
|
-
def is_closed(self):
|
|
1205
|
+
def is_closed(self) -> bool:
|
|
1200
1206
|
"""Return whether the connection is closed"""
|
|
1201
1207
|
|
|
1202
1208
|
return self._close_event.is_set()
|
|
@@ -2069,7 +2075,7 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
2069
2075
|
self.send_packet(MSG_USERAUTH_FAILURE, NameList(methods),
|
|
2070
2076
|
Boolean(partial_success))
|
|
2071
2077
|
|
|
2072
|
-
def send_userauth_success(self) -> None:
|
|
2078
|
+
async def send_userauth_success(self) -> None:
|
|
2073
2079
|
"""Send a user authentication success response"""
|
|
2074
2080
|
|
|
2075
2081
|
self.logger.info('Auth for user %s succeeded', self._username)
|
|
@@ -2086,13 +2092,15 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
2086
2092
|
self._set_keepalive_timer()
|
|
2087
2093
|
|
|
2088
2094
|
if self._owner: # pragma: no branch
|
|
2089
|
-
self._owner.auth_completed()
|
|
2095
|
+
result = self._owner.auth_completed()
|
|
2096
|
+
|
|
2097
|
+
if inspect.isawaitable(result):
|
|
2098
|
+
await result
|
|
2090
2099
|
|
|
2091
2100
|
if self._acceptor:
|
|
2092
2101
|
result = self._acceptor(self)
|
|
2093
2102
|
|
|
2094
2103
|
if inspect.isawaitable(result):
|
|
2095
|
-
assert result is not None
|
|
2096
2104
|
self.create_task(result)
|
|
2097
2105
|
|
|
2098
2106
|
self._acceptor = None
|
|
@@ -2506,7 +2514,7 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
2506
2514
|
result = await cast(Awaitable[bool], result)
|
|
2507
2515
|
|
|
2508
2516
|
if not result:
|
|
2509
|
-
self.send_userauth_success()
|
|
2517
|
+
await self.send_userauth_success()
|
|
2510
2518
|
return
|
|
2511
2519
|
|
|
2512
2520
|
if not self._owner: # pragma: no cover
|
|
@@ -2603,7 +2611,6 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
2603
2611
|
result = self._acceptor(self)
|
|
2604
2612
|
|
|
2605
2613
|
if inspect.isawaitable(result):
|
|
2606
|
-
assert result is not None
|
|
2607
2614
|
self.create_task(result)
|
|
2608
2615
|
|
|
2609
2616
|
self._acceptor = None
|
|
@@ -3229,9 +3236,8 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
3229
3236
|
raise ChannelOpenError(OPEN_ADMINISTRATIVELY_PROHIBITED,
|
|
3230
3237
|
'Connection forwarding denied')
|
|
3231
3238
|
|
|
3232
|
-
return
|
|
3233
|
-
|
|
3234
|
-
orig_host, orig_port))
|
|
3239
|
+
return await self.create_connection(session_factory, dest_host,
|
|
3240
|
+
dest_port, orig_host, orig_port)
|
|
3235
3241
|
|
|
3236
3242
|
if (listen_host, listen_port) == (dest_host, dest_port):
|
|
3237
3243
|
self.logger.info('Creating local TCP forwarder on %s',
|
|
@@ -4130,7 +4136,6 @@ class SSHClientConnection(SSHConnection):
|
|
|
4130
4136
|
retained, revoked)
|
|
4131
4137
|
|
|
4132
4138
|
if inspect.isawaitable(result):
|
|
4133
|
-
assert result is not None
|
|
4134
4139
|
await result
|
|
4135
4140
|
|
|
4136
4141
|
self._report_global_response(True)
|
|
@@ -4164,7 +4169,7 @@ class SSHClientConnection(SSHConnection):
|
|
|
4164
4169
|
async def create_session(self, session_factory: SSHClientSessionFactory,
|
|
4165
4170
|
command: DefTuple[Optional[str]] = (), *,
|
|
4166
4171
|
subsystem: DefTuple[Optional[str]]= (),
|
|
4167
|
-
env: DefTuple[Env] = (),
|
|
4172
|
+
env: DefTuple[Optional[Env]] = (),
|
|
4168
4173
|
send_env: DefTuple[Optional[EnvSeq]] = (),
|
|
4169
4174
|
request_pty: DefTuple[Union[bool, str]] = (),
|
|
4170
4175
|
term_type: DefTuple[Optional[str]] = (),
|
|
@@ -5052,8 +5057,8 @@ class SSHClientConnection(SSHConnection):
|
|
|
5052
5057
|
|
|
5053
5058
|
"""
|
|
5054
5059
|
|
|
5055
|
-
return
|
|
5056
|
-
|
|
5060
|
+
return await create_connection(client_factory, host, port,
|
|
5061
|
+
tunnel=self, **kwargs) # type: ignore
|
|
5057
5062
|
|
|
5058
5063
|
@async_context_manager
|
|
5059
5064
|
async def connect_ssh(self, host: str, port: DefTuple[int] = (),
|
|
@@ -5321,8 +5326,7 @@ class SSHClientConnection(SSHConnection):
|
|
|
5321
5326
|
raise ChannelOpenError(OPEN_ADMINISTRATIVELY_PROHIBITED,
|
|
5322
5327
|
'Connection forwarding denied')
|
|
5323
5328
|
|
|
5324
|
-
return
|
|
5325
|
-
dest_path))
|
|
5329
|
+
return await self.create_unix_connection(session_factory, dest_path)
|
|
5326
5330
|
|
|
5327
5331
|
self.logger.info('Creating local TCP forwarder from %s to %s',
|
|
5328
5332
|
(listen_host, listen_port), dest_path)
|
|
@@ -5683,7 +5687,7 @@ class SSHClientConnection(SSHConnection):
|
|
|
5683
5687
|
return cast(SSHForwarder, peer)
|
|
5684
5688
|
|
|
5685
5689
|
@async_context_manager
|
|
5686
|
-
async def start_sftp_client(self, env: DefTuple[Env] = (),
|
|
5690
|
+
async def start_sftp_client(self, env: DefTuple[Optional[Env]] = (),
|
|
5687
5691
|
send_env: DefTuple[Optional[EnvSeq]] = (),
|
|
5688
5692
|
path_encoding: Optional[str] = 'utf-8',
|
|
5689
5693
|
path_errors = 'strict',
|
|
@@ -6301,6 +6305,9 @@ class SSHServerConnection(SSHConnection):
|
|
|
6301
6305
|
if not result:
|
|
6302
6306
|
raise ChannelOpenError(OPEN_CONNECT_FAILED, 'Session refused')
|
|
6303
6307
|
|
|
6308
|
+
if isinstance(result, SSHClientConnection):
|
|
6309
|
+
result = self.forward_tunneled_session(result)
|
|
6310
|
+
|
|
6304
6311
|
if isinstance(result, tuple):
|
|
6305
6312
|
chan, result = result
|
|
6306
6313
|
else:
|
|
@@ -6356,6 +6363,10 @@ class SSHServerConnection(SSHConnection):
|
|
|
6356
6363
|
if result is True:
|
|
6357
6364
|
result = cast(SSHTCPSession[bytes],
|
|
6358
6365
|
self.forward_connection(dest_host, dest_port))
|
|
6366
|
+
elif isinstance(result, SSHClientConnection):
|
|
6367
|
+
result = cast(Awaitable[SSHTCPSession[bytes]],
|
|
6368
|
+
self.forward_tunneled_connection(
|
|
6369
|
+
result, dest_host, dest_port))
|
|
6359
6370
|
|
|
6360
6371
|
if isinstance(result, tuple):
|
|
6361
6372
|
chan, result = result
|
|
@@ -6502,6 +6513,10 @@ class SSHServerConnection(SSHConnection):
|
|
|
6502
6513
|
if result is True:
|
|
6503
6514
|
result = cast(SSHUNIXSession[bytes],
|
|
6504
6515
|
self.forward_unix_connection(dest_path))
|
|
6516
|
+
elif isinstance(result, SSHClientConnection):
|
|
6517
|
+
result = cast(Awaitable[SSHUNIXSession[bytes]],
|
|
6518
|
+
self.forward_tunneled_unix_connection(
|
|
6519
|
+
result, dest_path))
|
|
6505
6520
|
|
|
6506
6521
|
if isinstance(result, tuple):
|
|
6507
6522
|
chan, result = result
|
|
@@ -6617,10 +6632,14 @@ class SSHServerConnection(SSHConnection):
|
|
|
6617
6632
|
result = False
|
|
6618
6633
|
|
|
6619
6634
|
if not result:
|
|
6620
|
-
raise ChannelOpenError(OPEN_CONNECT_FAILED,
|
|
6635
|
+
raise ChannelOpenError(OPEN_CONNECT_FAILED,
|
|
6636
|
+
'TUN/TAP request refused')
|
|
6621
6637
|
|
|
6622
6638
|
if result is True:
|
|
6623
6639
|
result = cast(SSHTunTapSession, self.forward_tuntap(mode, unit))
|
|
6640
|
+
elif isinstance(result, SSHClientConnection):
|
|
6641
|
+
result = cast(Awaitable[SSHTunTapSession],
|
|
6642
|
+
self.forward_tunneled_tuntap(result, mode, unit))
|
|
6624
6643
|
|
|
6625
6644
|
if isinstance(result, tuple):
|
|
6626
6645
|
chan, result = result
|
|
@@ -7175,6 +7194,76 @@ class SSHServerConnection(SSHConnection):
|
|
|
7175
7194
|
|
|
7176
7195
|
return SSHReader[bytes](session, chan), SSHWriter[bytes](session, chan)
|
|
7177
7196
|
|
|
7197
|
+
async def forward_tunneled_session(
|
|
7198
|
+
self, conn: SSHClientConnection) -> SSHServerProcess:
|
|
7199
|
+
"""Forward a tunneled session between SSH connections"""
|
|
7200
|
+
|
|
7201
|
+
async def process_factory(process: SSHServerProcess) -> None:
|
|
7202
|
+
"""Return an upstream process used to forward the session"""
|
|
7203
|
+
|
|
7204
|
+
encoding, errors = process.channel.get_encoding()
|
|
7205
|
+
|
|
7206
|
+
upstream_process: SSHClientProcess = await conn.create_process(
|
|
7207
|
+
command=process.command, subsystem=process.subsystem,
|
|
7208
|
+
env=process.env, term_type=process.term_type,
|
|
7209
|
+
term_size=process.term_size, term_modes=process.term_modes,
|
|
7210
|
+
encoding=encoding, errors=errors, stdin=process.stdin,
|
|
7211
|
+
stdout=process.stdout, stderr=process.stderr)
|
|
7212
|
+
|
|
7213
|
+
await upstream_process.wait_closed()
|
|
7214
|
+
|
|
7215
|
+
self.logger.info(' Forwarding session via SSH tunnel')
|
|
7216
|
+
|
|
7217
|
+
return SSHServerProcess(process_factory, None, MIN_SFTP_VERSION, False)
|
|
7218
|
+
|
|
7219
|
+
async def forward_tunneled_connection(
|
|
7220
|
+
self, conn: SSHClientConnection,
|
|
7221
|
+
dest_host: str, dest_port: int) -> SSHForwarder:
|
|
7222
|
+
"""Forward a tunneled TCP connection between SSH connections"""
|
|
7223
|
+
|
|
7224
|
+
_, peer = await conn.create_connection(
|
|
7225
|
+
cast(SSHTCPSessionFactory[bytes], SSHForwarder),
|
|
7226
|
+
dest_host, dest_port)
|
|
7227
|
+
|
|
7228
|
+
self.logger.info(' Forwarding TCP connection to %s via SSH tunnel',
|
|
7229
|
+
(dest_host, dest_port))
|
|
7230
|
+
|
|
7231
|
+
return SSHForwarder(cast(SSHForwarder, peer))
|
|
7232
|
+
|
|
7233
|
+
async def forward_tunneled_unix_connection(
|
|
7234
|
+
self, conn: SSHClientConnection,
|
|
7235
|
+
dest_path: str) -> SSHForwarder:
|
|
7236
|
+
"""Forward a tunneled UNIX connection between SSH connections"""
|
|
7237
|
+
|
|
7238
|
+
_, peer = await conn.create_unix_connection(
|
|
7239
|
+
cast(SSHUNIXSessionFactory[bytes], SSHForwarder), dest_path)
|
|
7240
|
+
|
|
7241
|
+
self.logger.info(' Forwarding UNIX connection to %s via SSH tunnel',
|
|
7242
|
+
dest_path)
|
|
7243
|
+
|
|
7244
|
+
return SSHForwarder(cast(SSHForwarder, peer))
|
|
7245
|
+
|
|
7246
|
+
async def forward_tunneled_tuntap(
|
|
7247
|
+
self, conn: SSHClientConnection,
|
|
7248
|
+
mode: int, unit: Optional[int]) -> SSHForwarder:
|
|
7249
|
+
"""Forward a TUN/TAP connection between SSH connections"""
|
|
7250
|
+
|
|
7251
|
+
if mode == SSH_TUN_MODE_POINTTOPOINT:
|
|
7252
|
+
create_func = conn.create_tun
|
|
7253
|
+
layer = 3
|
|
7254
|
+
else:
|
|
7255
|
+
create_func = conn.create_tap
|
|
7256
|
+
layer = 2
|
|
7257
|
+
|
|
7258
|
+
transport, peer = await create_func(
|
|
7259
|
+
cast(SSHTunTapSessionFactory, SSHForwarder), unit)
|
|
7260
|
+
interface = transport.get_extra_info('interface')
|
|
7261
|
+
|
|
7262
|
+
self.logger.info(' Forwarding layer %d traffic to %s via SSH tunnel',
|
|
7263
|
+
layer, interface)
|
|
7264
|
+
|
|
7265
|
+
return SSHForwarder(cast(SSHForwarder, peer))
|
|
7266
|
+
|
|
7178
7267
|
|
|
7179
7268
|
class SSHConnectionOptions(Options, Generic[_Options]):
|
|
7180
7269
|
"""SSH connection options"""
|
|
@@ -7953,7 +8042,7 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
7953
8042
|
pkcs11_pin: Optional[str]
|
|
7954
8043
|
command: Optional[str]
|
|
7955
8044
|
subsystem: Optional[str]
|
|
7956
|
-
env: Env
|
|
8045
|
+
env: Optional[Env]
|
|
7957
8046
|
send_env: Optional[EnvSeq]
|
|
7958
8047
|
request_pty: _RequestPTY
|
|
7959
8048
|
term_type: Optional[str]
|
|
@@ -8026,7 +8115,8 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
8026
8115
|
pkcs11_provider: DefTuple[Optional[str]] = (),
|
|
8027
8116
|
pkcs11_pin: Optional[str] = None,
|
|
8028
8117
|
command: DefTuple[Optional[str]] = (),
|
|
8029
|
-
subsystem: Optional[str] = None,
|
|
8118
|
+
subsystem: Optional[str] = None,
|
|
8119
|
+
env: DefTuple[Optional[Env]] = (),
|
|
8030
8120
|
send_env: DefTuple[Optional[EnvSeq]] = (),
|
|
8031
8121
|
request_pty: DefTuple[_RequestPTY] = (),
|
|
8032
8122
|
term_type: Optional[str] = None,
|
|
@@ -8265,7 +8355,8 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
8265
8355
|
|
|
8266
8356
|
self.subsystem = subsystem
|
|
8267
8357
|
|
|
8268
|
-
self.env = cast(Env, env if env != () else
|
|
8358
|
+
self.env = cast(Optional[Env], env if env != () else
|
|
8359
|
+
config.get('SetEnv'))
|
|
8269
8360
|
|
|
8270
8361
|
self.send_env = cast(Optional[EnvSeq], send_env if send_env != () else
|
|
8271
8362
|
config.get('SendEnv'))
|
|
@@ -8473,11 +8564,11 @@ class SSHServerConnectionOptions(SSHConnectionOptions):
|
|
|
8473
8564
|
errors of data exchanged on sessions on this server, defaulting
|
|
8474
8565
|
to 'strict'.
|
|
8475
8566
|
:param sftp_factory: (optional)
|
|
8476
|
-
A `callable` which returns an :class:`SFTPServer`
|
|
8477
|
-
will be created each time an SFTP session is
|
|
8478
|
-
client, or `True` to use the base
|
|
8479
|
-
to handle SFTP requests. If not
|
|
8480
|
-
rejected by default.
|
|
8567
|
+
A `callable` or coroutine which returns an :class:`SFTPServer`
|
|
8568
|
+
object that will be created each time an SFTP session is
|
|
8569
|
+
requested by the client, or `True` to use the base
|
|
8570
|
+
:class:`SFTPServer` class to handle SFTP requests. If not
|
|
8571
|
+
specified, SFTP sessions are rejected by default.
|
|
8481
8572
|
:param sftp_version: (optional)
|
|
8482
8573
|
The maximum version of the SFTP protocol to support, currently
|
|
8483
8574
|
either 3 or 4, defaulting to 3.
|
|
@@ -8624,7 +8715,7 @@ class SSHServerConnectionOptions(SSHConnectionOptions):
|
|
|
8624
8715
|
:type session_factory: `callable` or coroutine
|
|
8625
8716
|
:type encoding: `str` or `None`
|
|
8626
8717
|
:type errors: `str`
|
|
8627
|
-
:type sftp_factory: `callable`
|
|
8718
|
+
:type sftp_factory: `callable` or coroutine
|
|
8628
8719
|
:type sftp_version: `int`
|
|
8629
8720
|
:type allow_scp: `bool`
|
|
8630
8721
|
:type window: `int`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c) 2013-
|
|
1
|
+
# Copyright (c) 2013-2025 by Ron Frederick <ronf@timeheart.net> and others.
|
|
2
2
|
#
|
|
3
3
|
# This program and the accompanying materials are made available under
|
|
4
4
|
# the terms of the Eclipse Public License v2.0 which accompanies this
|
|
@@ -343,6 +343,9 @@ class _KexDHGex(_KexDHBase):
|
|
|
343
343
|
if self._conn.is_client():
|
|
344
344
|
raise ProtocolError('Unexpected kex request msg')
|
|
345
345
|
|
|
346
|
+
if self._p:
|
|
347
|
+
raise ProtocolError('Kex DH group already requested')
|
|
348
|
+
|
|
346
349
|
self._gex_data = packet.get_remaining_payload()
|
|
347
350
|
|
|
348
351
|
if pkttype == MSG_KEX_DH_GEX_REQUEST_OLD:
|
|
@@ -377,6 +380,9 @@ class _KexDHGex(_KexDHBase):
|
|
|
377
380
|
if self._conn.is_server():
|
|
378
381
|
raise ProtocolError('Unexpected kex group msg')
|
|
379
382
|
|
|
383
|
+
if self._p:
|
|
384
|
+
raise ProtocolError('Kex DH group already sent')
|
|
385
|
+
|
|
380
386
|
p = packet.get_mpint()
|
|
381
387
|
g = packet.get_mpint()
|
|
382
388
|
packet.check_end()
|
|
@@ -529,6 +535,7 @@ class _KexGSSBase(_KexDHBase):
|
|
|
529
535
|
|
|
530
536
|
self._gss = conn.get_gss_context()
|
|
531
537
|
self._token: Optional[bytes] = None
|
|
538
|
+
self._host_key_msg_ok = False
|
|
532
539
|
self._host_key_data = b''
|
|
533
540
|
|
|
534
541
|
def _check_secure(self) -> None:
|
|
@@ -621,6 +628,8 @@ class _KexGSSBase(_KexDHBase):
|
|
|
621
628
|
if self._conn.is_client() and self._gss.complete:
|
|
622
629
|
raise ProtocolError('Unexpected kexgss continue msg')
|
|
623
630
|
|
|
631
|
+
self._host_key_msg_ok = False
|
|
632
|
+
|
|
624
633
|
await self._process_token(token)
|
|
625
634
|
|
|
626
635
|
if self._conn.is_server() and self._gss.complete:
|
|
@@ -636,6 +645,8 @@ class _KexGSSBase(_KexDHBase):
|
|
|
636
645
|
if self._conn.is_server():
|
|
637
646
|
raise ProtocolError('Unexpected kexgss complete msg')
|
|
638
647
|
|
|
648
|
+
self._host_key_msg_ok = False
|
|
649
|
+
|
|
639
650
|
self._parse_server_key(packet)
|
|
640
651
|
mic = packet.get_string()
|
|
641
652
|
token_present = packet.get_boolean()
|
|
@@ -662,6 +673,10 @@ class _KexGSSBase(_KexDHBase):
|
|
|
662
673
|
packet: SSHPacket) -> None:
|
|
663
674
|
"""Process a GSS hostkey message"""
|
|
664
675
|
|
|
676
|
+
if not self._host_key_msg_ok:
|
|
677
|
+
raise ProtocolError('Unexpected kexgss hostkey msg')
|
|
678
|
+
|
|
679
|
+
self._host_key_msg_ok = False
|
|
665
680
|
self._host_key_data = packet.get_string()
|
|
666
681
|
packet.check_end()
|
|
667
682
|
|
|
@@ -685,6 +700,7 @@ class _KexGSSBase(_KexDHBase):
|
|
|
685
700
|
"""Start GSS key exchange"""
|
|
686
701
|
|
|
687
702
|
if self._conn.is_client():
|
|
703
|
+
self._host_key_msg_ok = True
|
|
688
704
|
await self._process_token()
|
|
689
705
|
await super().start()
|
|
690
706
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c) 2013-
|
|
1
|
+
# Copyright (c) 2013-2025 by Ron Frederick <ronf@timeheart.net> and others.
|
|
2
2
|
#
|
|
3
3
|
# This program and the accompanying materials are made available under
|
|
4
4
|
# the terms of the Eclipse Public License v2.0 which accompanies this
|
|
@@ -46,6 +46,17 @@ from .constants import DISC_NO_MORE_AUTH_METHODS_AVAILABLE
|
|
|
46
46
|
from .constants import DISC_PROTOCOL_ERROR, DISC_PROTOCOL_VERSION_NOT_SUPPORTED
|
|
47
47
|
from .constants import DISC_SERVICE_NOT_AVAILABLE
|
|
48
48
|
|
|
49
|
+
_pywin32_available = False
|
|
50
|
+
|
|
51
|
+
if sys.platform == 'win32': # pragma: no cover
|
|
52
|
+
try:
|
|
53
|
+
import msvcrt
|
|
54
|
+
import win32file
|
|
55
|
+
import winioctlcon
|
|
56
|
+
_pywin32_available = True
|
|
57
|
+
except ImportError:
|
|
58
|
+
pass
|
|
59
|
+
|
|
49
60
|
if sys.platform != 'win32': # pragma: no branch
|
|
50
61
|
import fcntl
|
|
51
62
|
import struct
|
|
@@ -114,7 +125,7 @@ SockAddr = Union[Tuple[str, int], Tuple[str, int, int, int]]
|
|
|
114
125
|
EnvMap = Mapping[BytesOrStr, BytesOrStr]
|
|
115
126
|
EnvItems = Sequence[Tuple[BytesOrStr, BytesOrStr]]
|
|
116
127
|
EnvSeq = Sequence[BytesOrStr]
|
|
117
|
-
Env =
|
|
128
|
+
Env = Union[EnvMap, EnvItems, EnvSeq]
|
|
118
129
|
|
|
119
130
|
# Define a version of randrange which is based on SystemRandom(), so that
|
|
120
131
|
# we get back numbers suitable for cryptographic use.
|
|
@@ -130,8 +141,8 @@ _time_units = {'': 1, 's': 1, 'm': 60, 'h': 60*60,
|
|
|
130
141
|
def encode_env(env: Env) -> Iterator[Tuple[bytes, bytes]]:
|
|
131
142
|
"""Convert environemnt dict or list to bytes-based dictionary"""
|
|
132
143
|
|
|
133
|
-
|
|
134
|
-
|
|
144
|
+
if hasattr(env, 'items'):
|
|
145
|
+
env = cast(Env, env.items())
|
|
135
146
|
|
|
136
147
|
try:
|
|
137
148
|
for item in env:
|
|
@@ -305,6 +316,19 @@ def write_file(filename: FilePath, data: bytes, mode: str = 'wb') -> int:
|
|
|
305
316
|
return f.write(data)
|
|
306
317
|
|
|
307
318
|
|
|
319
|
+
if sys.platform == 'win32' and _pywin32_available: # pragma: no cover
|
|
320
|
+
def make_sparse_file(file_obj: IO) -> None:
|
|
321
|
+
"""Enable sparse file support on a file on Windows"""
|
|
322
|
+
|
|
323
|
+
handle = msvcrt.get_osfhandle(file_obj.fileno())
|
|
324
|
+
|
|
325
|
+
win32file.DeviceIoControl(handle, winioctlcon.FSCTL_SET_SPARSE,
|
|
326
|
+
b'', 0, None)
|
|
327
|
+
else:
|
|
328
|
+
def make_sparse_file(_file_obj: IO) -> None:
|
|
329
|
+
"""Sparse files are automatically enabled on non-Windows systems"""
|
|
330
|
+
|
|
331
|
+
|
|
308
332
|
def _parse_units(value: str, suffixes: Mapping[str, int], label: str) -> float:
|
|
309
333
|
"""Parse a series of integers followed by unit suffixes"""
|
|
310
334
|
|