asyncssh 2.19.0__tar.gz → 2.21.0__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.19.0 → asyncssh-2.21.0}/.github/workflows/run_tests.yml +3 -18
- {asyncssh-2.19.0/asyncssh.egg-info → asyncssh-2.21.0}/PKG-INFO +4 -3
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/auth.py +12 -12
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/channel.py +12 -5
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/client.py +9 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/config.py +50 -28
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/connection.py +134 -31
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/kex_dh.py +17 -1
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/misc.py +32 -7
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/process.py +2 -2
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/scp.py +61 -27
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/server.py +54 -21
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/sftp.py +401 -125
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/stream.py +3 -5
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/version.py +1 -1
- {asyncssh-2.19.0 → asyncssh-2.21.0/asyncssh.egg-info}/PKG-INFO +4 -3
- {asyncssh-2.19.0 → asyncssh-2.21.0}/docs/changes.rst +68 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/pyproject.toml +1 -1
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/server.py +16 -12
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_auth.py +2 -2
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_channel.py +19 -3
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_config.py +29 -4
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_connection.py +18 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_connection_auth.py +6 -2
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_forward.py +63 -6
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_kex.py +44 -11
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_sftp.py +156 -8
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_stream.py +33 -1
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_tuntap.py +59 -1
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/util.py +0 -2
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tox.ini +3 -3
- {asyncssh-2.19.0 → asyncssh-2.21.0}/.coveragerc +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/.gitignore +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/.readthedocs.yaml +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/CONTRIBUTING.rst +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/COPYRIGHT +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/LICENSE +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/MANIFEST.in +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/README.rst +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/__init__.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/agent.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/agent_unix.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/agent_win32.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/asn1.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/auth_keys.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/compression.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/constants.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/crypto/__init__.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/crypto/chacha.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/crypto/cipher.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/crypto/dh.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/crypto/dsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/crypto/ec.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/crypto/ec_params.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/crypto/ed.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/crypto/kdf.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/crypto/misc.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/crypto/pq.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/crypto/rsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/crypto/umac.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/crypto/x509.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/dsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/ecdsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/eddsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/editor.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/encryption.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/forward.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/gss.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/gss_unix.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/gss_win32.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/kex.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/kex_rsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/keysign.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/known_hosts.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/listener.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/logging.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/mac.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/packet.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/pattern.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/pbe.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/pkcs11.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/public_key.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/py.typed +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/rsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/saslprep.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/session.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/sk.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/sk_ecdsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/sk_eddsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/socks.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/subprocess.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/tuntap.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh/x11.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh.egg-info/SOURCES.txt +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh.egg-info/dependency_links.txt +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh.egg-info/requires.txt +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/asyncssh.egg-info/top_level.txt +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/docs/_templates/sidebarbottom.html +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/docs/_templates/sidebartop.html +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/docs/api.rst +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/docs/conf.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/docs/contributing.rst +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/docs/index.rst +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/docs/requirements.txt +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/docs/rftheme/layout.html +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/docs/rftheme/static/rftheme.css_t +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/docs/rftheme/theme.conf +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/docs/rtd-req.txt +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/callback_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/callback_client2.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/callback_client3.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/callback_math_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/chat_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/check_exit_status.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/chroot_sftp_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/direct_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/direct_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/editor.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/gather_results.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/listening_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/local_forwarding_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/local_forwarding_client2.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/local_forwarding_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/math_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/math_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/redirect_input.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/redirect_local_pipe.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/redirect_remote_pipe.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/redirect_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/remote_forwarding_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/remote_forwarding_client2.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/remote_forwarding_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/reverse_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/reverse_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/scp_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/set_environment.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/set_terminal.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/sftp_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/show_environment.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/show_terminal.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/simple_cert_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/simple_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/simple_keyed_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/simple_scp_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/simple_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/simple_sftp_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/stream_direct_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/stream_direct_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/examples/stream_listening_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/mypy.ini +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/pylintrc +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/setup.cfg +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/__init__.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/gss_stub.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/gssapi_stub.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/keysign_stub.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/pkcs11_stub.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/sk_stub.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/sspi_stub.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_agent.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_asn1.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_auth_keys.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_compression.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_editor.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_encryption.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_known_hosts.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_logging.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_mac.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_packet.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_pkcs11.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_process.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_public_key.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_saslprep.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_sk.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_subprocess.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_x11.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.21.0}/tests/test_x509.py +0 -0
|
@@ -8,7 +8,7 @@ jobs:
|
|
|
8
8
|
fail-fast: false
|
|
9
9
|
matrix:
|
|
10
10
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
11
|
-
python-version: ["3.
|
|
11
|
+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
12
12
|
include:
|
|
13
13
|
- os: macos-latest
|
|
14
14
|
python-version: "3.10"
|
|
@@ -19,22 +19,9 @@ jobs:
|
|
|
19
19
|
- os: macos-latest
|
|
20
20
|
python-version: "3.12"
|
|
21
21
|
openssl-version: "3"
|
|
22
|
-
exclude:
|
|
23
|
-
# having trouble with arch arm64 on macos-ltest on Python 3.7
|
|
24
22
|
- os: macos-latest
|
|
25
|
-
python-version: "3.
|
|
26
|
-
|
|
27
|
-
# test hangs on these combination
|
|
28
|
-
- os: windows-latest
|
|
29
|
-
python-version: "3.8"
|
|
30
|
-
- os: windows-latest
|
|
31
|
-
python-version: "3.9"
|
|
32
|
-
- os: windows-latest
|
|
33
|
-
python-version: "3.10"
|
|
34
|
-
- os: windows-latest
|
|
35
|
-
python-version: "3.11"
|
|
36
|
-
- os: windows-latest
|
|
37
|
-
python-version: "3.12"
|
|
23
|
+
python-version: "3.13"
|
|
24
|
+
openssl-version: "3"
|
|
38
25
|
|
|
39
26
|
runs-on: ${{ matrix.os }}
|
|
40
27
|
env:
|
|
@@ -153,8 +140,6 @@ jobs:
|
|
|
153
140
|
steps:
|
|
154
141
|
- uses: actions/checkout@v4
|
|
155
142
|
- uses: actions/setup-python@v5
|
|
156
|
-
with:
|
|
157
|
-
python-version: "3.7"
|
|
158
143
|
- uses: actions/download-artifact@v4
|
|
159
144
|
with:
|
|
160
145
|
name: coverage
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: asyncssh
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.21.0
|
|
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
|
|
@@ -14,12 +14,12 @@ Classifier: Intended Audience :: Developers
|
|
|
14
14
|
Classifier: License :: OSI Approved
|
|
15
15
|
Classifier: Operating System :: MacOS :: MacOS X
|
|
16
16
|
Classifier: Operating System :: POSIX
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.8
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.9
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
23
|
Classifier: Topic :: Internet
|
|
24
24
|
Classifier: Topic :: Security :: Cryptography
|
|
25
25
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
@@ -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:
|
|
@@ -220,6 +220,15 @@ class SSHClient:
|
|
|
220
220
|
|
|
221
221
|
"""
|
|
222
222
|
|
|
223
|
+
def begin_auth(self, username: str) -> None:
|
|
224
|
+
"""Begin client authentication
|
|
225
|
+
|
|
226
|
+
This method is called when client authentication is about to
|
|
227
|
+
begin, Applications may store the username passed here to
|
|
228
|
+
be used in future authentication callbacks.
|
|
229
|
+
|
|
230
|
+
"""
|
|
231
|
+
|
|
223
232
|
def auth_completed(self) -> None:
|
|
224
233
|
"""Authentication was completed successfully
|
|
225
234
|
|
|
@@ -41,6 +41,10 @@ from .pattern import HostPatternList, WildcardPatternList
|
|
|
41
41
|
ConfigPaths = Union[None, FilePath, Sequence[FilePath]]
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
_token_pattern = re.compile(r'%(.)')
|
|
45
|
+
_env_pattern = re.compile(r'\${(.*)}')
|
|
46
|
+
|
|
47
|
+
|
|
44
48
|
def _exec(cmd: str) -> bool:
|
|
45
49
|
"""Execute a command and return if exit status is 0"""
|
|
46
50
|
|
|
@@ -93,36 +97,38 @@ class SSHConfig:
|
|
|
93
97
|
|
|
94
98
|
raise NotImplementedError
|
|
95
99
|
|
|
96
|
-
def
|
|
97
|
-
"""
|
|
100
|
+
def _expand_token(self, match):
|
|
101
|
+
"""Expand a percent token reference"""
|
|
98
102
|
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
try:
|
|
104
|
+
token = match.group(1)
|
|
105
|
+
return self._tokens[token]
|
|
106
|
+
except KeyError:
|
|
107
|
+
if token == 'd':
|
|
108
|
+
raise ConfigParseError('Home directory is '
|
|
109
|
+
'not available') from None
|
|
110
|
+
elif token == 'i':
|
|
111
|
+
raise ConfigParseError('User id not available') from None
|
|
112
|
+
else:
|
|
113
|
+
raise ConfigParseError('Invalid token expansion: ' +
|
|
114
|
+
token) from None
|
|
101
115
|
|
|
102
|
-
|
|
103
|
-
|
|
116
|
+
@staticmethod
|
|
117
|
+
def _expand_env(match):
|
|
118
|
+
"""Expand an environment variable reference"""
|
|
104
119
|
|
|
105
|
-
|
|
106
|
-
|
|
120
|
+
try:
|
|
121
|
+
var = match.group(1)
|
|
122
|
+
return os.environ[var]
|
|
123
|
+
except KeyError:
|
|
124
|
+
raise ConfigParseError('Invalid environment expansion: ' +
|
|
125
|
+
var) from None
|
|
107
126
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
raise ConfigParseError('Invalid token substitution') from None
|
|
114
|
-
except KeyError:
|
|
115
|
-
if token == 'd':
|
|
116
|
-
raise ConfigParseError('Home directory is '
|
|
117
|
-
'not available') from None
|
|
118
|
-
elif token == 'i':
|
|
119
|
-
raise ConfigParseError('User id not available') from None
|
|
120
|
-
else:
|
|
121
|
-
raise ConfigParseError('Invalid token substitution: ' +
|
|
122
|
-
value[idx+1]) from None
|
|
123
|
-
|
|
124
|
-
result.append(value[last_idx:])
|
|
125
|
-
return ''.join(result)
|
|
127
|
+
def _expand_val(self, value: str) -> str:
|
|
128
|
+
"""Perform percent token and environment expansion on a string"""
|
|
129
|
+
|
|
130
|
+
return _env_pattern.sub(self._expand_env,
|
|
131
|
+
_token_pattern.sub(self._expand_token, value))
|
|
126
132
|
|
|
127
133
|
def _include(self, option: str, args: List[str]) -> None:
|
|
128
134
|
"""Read config from a list of other config files"""
|
|
@@ -219,6 +225,22 @@ class SSHConfig:
|
|
|
219
225
|
if option not in self._options:
|
|
220
226
|
self._options[option] = value
|
|
221
227
|
|
|
228
|
+
def _set_bool_or_str(self, option: str, args: List[str]) -> None:
|
|
229
|
+
"""Set a boolean or string config option"""
|
|
230
|
+
|
|
231
|
+
value_str = args.pop(0)
|
|
232
|
+
value_lower = value_str.lower()
|
|
233
|
+
|
|
234
|
+
if value_lower in ('yes', 'true'):
|
|
235
|
+
value: Union[bool, str] = True
|
|
236
|
+
elif value_lower in ('no', 'false'):
|
|
237
|
+
value = False
|
|
238
|
+
else:
|
|
239
|
+
value = value_str
|
|
240
|
+
|
|
241
|
+
if option not in self._options:
|
|
242
|
+
self._options[option] = value
|
|
243
|
+
|
|
222
244
|
def _set_int(self, option: str, args: List[str]) -> None:
|
|
223
245
|
"""Set an integer config option"""
|
|
224
246
|
|
|
@@ -468,7 +490,7 @@ class SSHClientConfig(SSHConfig):
|
|
|
468
490
|
|
|
469
491
|
_conditionals = {'host', 'match'}
|
|
470
492
|
_no_split = {'proxycommand', 'remotecommand'}
|
|
471
|
-
_percent_expand = {'CertificateFile', 'IdentityAgent',
|
|
493
|
+
_percent_expand = {'CertificateFile', 'ForwardAgent', 'IdentityAgent',
|
|
472
494
|
'IdentityFile', 'ProxyCommand', 'RemoteCommand'}
|
|
473
495
|
|
|
474
496
|
def __init__(self, last_config: 'SSHConfig', reload: bool,
|
|
@@ -587,7 +609,7 @@ class SSHClientConfig(SSHConfig):
|
|
|
587
609
|
('Compression', SSHConfig._set_bool),
|
|
588
610
|
('ConnectTimeout', SSHConfig._set_int),
|
|
589
611
|
('EnableSSHKeySign', SSHConfig._set_bool),
|
|
590
|
-
('ForwardAgent', SSHConfig.
|
|
612
|
+
('ForwardAgent', SSHConfig._set_bool_or_str),
|
|
591
613
|
('ForwardX11Trusted', SSHConfig._set_bool),
|
|
592
614
|
('GlobalKnownHostsFile', SSHConfig._set_string_list),
|
|
593
615
|
('GSSAPIAuthentication', SSHConfig._set_bool),
|