asyncssh 2.19.0__tar.gz → 2.20.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.20.0}/.github/workflows/run_tests.yml +3 -18
- {asyncssh-2.19.0/asyncssh.egg-info → asyncssh-2.20.0}/PKG-INFO +3 -3
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/client.py +9 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/config.py +50 -28
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/connection.py +22 -8
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/misc.py +7 -6
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/process.py +2 -2
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/sftp.py +47 -14
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/version.py +1 -1
- {asyncssh-2.19.0 → asyncssh-2.20.0/asyncssh.egg-info}/PKG-INFO +3 -3
- {asyncssh-2.19.0 → asyncssh-2.20.0}/docs/changes.rst +30 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/pyproject.toml +1 -1
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/server.py +16 -12
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_channel.py +3 -3
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_config.py +29 -4
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_connection_auth.py +2 -1
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_sftp.py +38 -1
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tox.ini +3 -3
- {asyncssh-2.19.0 → asyncssh-2.20.0}/.coveragerc +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/.gitignore +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/.readthedocs.yaml +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/CONTRIBUTING.rst +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/COPYRIGHT +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/LICENSE +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/MANIFEST.in +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/README.rst +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/__init__.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/agent.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/agent_unix.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/agent_win32.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/asn1.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/auth.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/auth_keys.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/channel.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/compression.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/constants.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/crypto/__init__.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/crypto/chacha.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/crypto/cipher.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/crypto/dh.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/crypto/dsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/crypto/ec.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/crypto/ec_params.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/crypto/ed.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/crypto/kdf.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/crypto/misc.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/crypto/pq.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/crypto/rsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/crypto/umac.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/crypto/x509.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/dsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/ecdsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/eddsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/editor.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/encryption.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/forward.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/gss.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/gss_unix.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/gss_win32.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/kex.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/kex_dh.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/kex_rsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/keysign.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/known_hosts.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/listener.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/logging.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/mac.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/packet.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/pattern.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/pbe.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/pkcs11.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/public_key.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/py.typed +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/rsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/saslprep.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/scp.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/session.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/sk.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/sk_ecdsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/sk_eddsa.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/socks.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/stream.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/subprocess.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/tuntap.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh/x11.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh.egg-info/SOURCES.txt +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh.egg-info/dependency_links.txt +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh.egg-info/requires.txt +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/asyncssh.egg-info/top_level.txt +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/docs/_templates/sidebarbottom.html +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/docs/_templates/sidebartop.html +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/docs/api.rst +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/docs/conf.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/docs/contributing.rst +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/docs/index.rst +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/docs/requirements.txt +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/docs/rftheme/layout.html +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/docs/rftheme/static/rftheme.css_t +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/docs/rftheme/theme.conf +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/docs/rtd-req.txt +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/callback_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/callback_client2.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/callback_client3.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/callback_math_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/chat_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/check_exit_status.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/chroot_sftp_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/direct_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/direct_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/editor.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/gather_results.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/listening_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/local_forwarding_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/local_forwarding_client2.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/local_forwarding_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/math_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/math_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/redirect_input.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/redirect_local_pipe.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/redirect_remote_pipe.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/redirect_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/remote_forwarding_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/remote_forwarding_client2.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/remote_forwarding_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/reverse_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/reverse_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/scp_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/set_environment.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/set_terminal.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/sftp_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/show_environment.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/show_terminal.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/simple_cert_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/simple_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/simple_keyed_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/simple_scp_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/simple_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/simple_sftp_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/stream_direct_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/stream_direct_server.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/examples/stream_listening_client.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/mypy.ini +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/pylintrc +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/setup.cfg +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/__init__.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/gss_stub.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/gssapi_stub.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/keysign_stub.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/pkcs11_stub.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/sk_stub.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/sspi_stub.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_agent.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_asn1.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_auth.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_auth_keys.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_compression.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_connection.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_editor.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_encryption.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_forward.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_kex.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_known_hosts.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_logging.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_mac.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_packet.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_pkcs11.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_process.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_public_key.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_saslprep.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_sk.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_stream.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_subprocess.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_tuntap.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_x11.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/test_x509.py +0 -0
- {asyncssh-2.19.0 → asyncssh-2.20.0}/tests/util.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.2
|
|
2
2
|
Name: asyncssh
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.20.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
|
|
@@ -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),
|
|
@@ -1741,7 +1741,7 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
1741
1741
|
self._send_kexinit()
|
|
1742
1742
|
self._kexinit_sent = True
|
|
1743
1743
|
|
|
1744
|
-
if (((pkttype in {MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT} or
|
|
1744
|
+
if (((pkttype in {MSG_DEBUG, MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT} or
|
|
1745
1745
|
pkttype > MSG_KEX_LAST) and not self._kex_complete) or
|
|
1746
1746
|
(pkttype == MSG_USERAUTH_BANNER and
|
|
1747
1747
|
not (self._auth_in_progress or self._auth_complete)) or
|
|
@@ -1751,7 +1751,7 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
1751
1751
|
|
|
1752
1752
|
# If we're encrypting and we have no data outstanding, insert an
|
|
1753
1753
|
# ignore packet into the stream
|
|
1754
|
-
if self._send_encryption and pkttype
|
|
1754
|
+
if self._send_encryption and pkttype > MSG_KEX_LAST:
|
|
1755
1755
|
self.send_packet(MSG_IGNORE, String(b''))
|
|
1756
1756
|
|
|
1757
1757
|
orig_payload = Byte(pkttype) + b''.join(args)
|
|
@@ -2292,6 +2292,9 @@ class SSHConnection(SSHPacketHandler, asyncio.Protocol):
|
|
|
2292
2292
|
|
|
2293
2293
|
self._auth_in_progress = True
|
|
2294
2294
|
|
|
2295
|
+
if self._owner: # pragma: no branch
|
|
2296
|
+
self._owner.begin_auth(self._username)
|
|
2297
|
+
|
|
2295
2298
|
# This method is only in SSHClientConnection
|
|
2296
2299
|
# pylint: disable=no-member
|
|
2297
2300
|
cast('SSHClientConnection', self).try_next_auth()
|
|
@@ -7640,8 +7643,11 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
7640
7643
|
made available for use. This is the default.
|
|
7641
7644
|
:param agent_forwarding: (optional)
|
|
7642
7645
|
Whether or not to allow forwarding of ssh-agent requests from
|
|
7643
|
-
processes running on the server.
|
|
7644
|
-
|
|
7646
|
+
processes running on the server. This argument can also be set
|
|
7647
|
+
to the path of a UNIX domain socket in cases where forwarded
|
|
7648
|
+
agent requests should be sent to a different path than client
|
|
7649
|
+
agent requests. By default, forwarding ssh-agent requests from
|
|
7650
|
+
the server is not allowed.
|
|
7645
7651
|
:param pkcs11_provider: (optional)
|
|
7646
7652
|
The path of a shared library which should be used as a PKCS#11
|
|
7647
7653
|
provider for accessing keys on PIV security tokens. By default,
|
|
@@ -7874,7 +7880,7 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
7874
7880
|
:type agent_path: `str`
|
|
7875
7881
|
:type agent_identities:
|
|
7876
7882
|
*see* :ref:`SpecifyingPublicKeys` and :ref:`SpecifyingCertificates`
|
|
7877
|
-
:type agent_forwarding: `bool`
|
|
7883
|
+
:type agent_forwarding: `bool` or `str`
|
|
7878
7884
|
:type pkcs11_provider: `str` or `None`
|
|
7879
7885
|
:type pkcs11_pin: `str`
|
|
7880
7886
|
:type client_version: `str`
|
|
@@ -8016,7 +8022,7 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
8016
8022
|
disable_trivial_auth: bool = False,
|
|
8017
8023
|
agent_path: DefTuple[Optional[str]] = (),
|
|
8018
8024
|
agent_identities: DefTuple[Optional[IdentityListArg]] = (),
|
|
8019
|
-
agent_forwarding: DefTuple[bool] = (),
|
|
8025
|
+
agent_forwarding: DefTuple[Union[bool, str]] = (),
|
|
8020
8026
|
pkcs11_provider: DefTuple[Optional[str]] = (),
|
|
8021
8027
|
pkcs11_pin: Optional[str] = None,
|
|
8022
8028
|
command: DefTuple[Optional[str]] = (),
|
|
@@ -8242,9 +8248,17 @@ class SSHClientConnectionOptions(SSHConnectionOptions):
|
|
|
8242
8248
|
self.pkcs11_pin = None
|
|
8243
8249
|
|
|
8244
8250
|
if agent_forwarding == ():
|
|
8245
|
-
agent_forwarding = cast(bool,
|
|
8251
|
+
agent_forwarding = cast(Union[bool, str],
|
|
8252
|
+
config.get('ForwardAgent', False))
|
|
8253
|
+
|
|
8254
|
+
agent_forwarding: Union[bool, str]
|
|
8246
8255
|
|
|
8247
|
-
|
|
8256
|
+
if not agent_forwarding:
|
|
8257
|
+
self.agent_forward_path = None
|
|
8258
|
+
elif agent_forwarding is True:
|
|
8259
|
+
self.agent_forward_path = agent_path
|
|
8260
|
+
else:
|
|
8261
|
+
self.agent_forward_path = agent_forwarding
|
|
8248
8262
|
|
|
8249
8263
|
self.command = cast(Optional[str], command if command != () else
|
|
8250
8264
|
config.get('RemoteCommand'))
|
|
@@ -466,17 +466,18 @@ class Options:
|
|
|
466
466
|
class _RecordMeta(type):
|
|
467
467
|
"""Metaclass for general-purpose record type"""
|
|
468
468
|
|
|
469
|
+
__slots__: Dict[str, object] = {}
|
|
470
|
+
|
|
469
471
|
def __new__(mcs: Type['_RecordMeta'], name: str, bases: Tuple[type, ...],
|
|
470
472
|
ns: Dict[str, object]) -> '_RecordMeta':
|
|
473
|
+
cls = cast(_RecordMeta, super().__new__(mcs, name, bases, ns))
|
|
474
|
+
|
|
471
475
|
if name != 'Record':
|
|
472
|
-
fields = cast(Mapping[str, str],
|
|
473
|
-
ns.get('__annotations__', {})).keys()
|
|
476
|
+
fields = cast(Mapping[str, str], cls.__annotations__.keys())
|
|
474
477
|
defaults = {k: ns.get(k) for k in fields}
|
|
478
|
+
cls.__slots__ = defaults
|
|
475
479
|
|
|
476
|
-
|
|
477
|
-
ns['__slots__'] = defaults
|
|
478
|
-
|
|
479
|
-
return cast(_RecordMeta, super().__new__(mcs, name, bases, ns))
|
|
480
|
+
return cls
|
|
480
481
|
|
|
481
482
|
|
|
482
483
|
class Record(metaclass=_RecordMeta):
|
|
@@ -928,7 +928,7 @@ class SSHProcess(SSHStreamSession, Generic[AnyStr]):
|
|
|
928
928
|
file = source
|
|
929
929
|
|
|
930
930
|
if hasattr(file, 'read') and \
|
|
931
|
-
(
|
|
931
|
+
(inspect.iscoroutinefunction(file.read) or
|
|
932
932
|
inspect.isgeneratorfunction(file.read)):
|
|
933
933
|
reader = _AsyncFileReader(self, cast(_AsyncFileProtocol, file),
|
|
934
934
|
bufsize, datatype, self._encoding,
|
|
@@ -997,7 +997,7 @@ class SSHProcess(SSHStreamSession, Generic[AnyStr]):
|
|
|
997
997
|
needs_close = recv_eof
|
|
998
998
|
|
|
999
999
|
if hasattr(file, 'write') and \
|
|
1000
|
-
(
|
|
1000
|
+
(inspect.iscoroutinefunction(file.write) or
|
|
1001
1001
|
inspect.isgeneratorfunction(file.write)):
|
|
1002
1002
|
writer = _AsyncFileWriter(
|
|
1003
1003
|
self, cast(_AsyncFileProtocol, file), needs_close,
|
|
@@ -3165,7 +3165,6 @@ class SFTPClientFile:
|
|
|
3165
3165
|
self._appending = appending
|
|
3166
3166
|
self._encoding = encoding
|
|
3167
3167
|
self._errors = errors
|
|
3168
|
-
self._max_requests = max_requests
|
|
3169
3168
|
self._offset = None if appending else 0
|
|
3170
3169
|
|
|
3171
3170
|
self.read_len = \
|
|
@@ -3173,6 +3172,15 @@ class SFTPClientFile:
|
|
|
3173
3172
|
self.write_len = \
|
|
3174
3173
|
handler.limits.max_write_len if block_size == -1 else block_size
|
|
3175
3174
|
|
|
3175
|
+
if max_requests <= 0:
|
|
3176
|
+
if self.read_len:
|
|
3177
|
+
max_requests = max(16, min(MAX_SFTP_READ_LEN //
|
|
3178
|
+
self.read_len, 128))
|
|
3179
|
+
else:
|
|
3180
|
+
max_requests = 1
|
|
3181
|
+
|
|
3182
|
+
self._max_requests = max_requests
|
|
3183
|
+
|
|
3176
3184
|
async def __aenter__(self) -> Self:
|
|
3177
3185
|
"""Allow SFTPClientFile to be used as an async context manager"""
|
|
3178
3186
|
|
|
@@ -3859,6 +3867,9 @@ class SFTPClient:
|
|
|
3859
3867
|
block_size = min(srcfs.limits.max_read_len,
|
|
3860
3868
|
dstfs.limits.max_write_len)
|
|
3861
3869
|
|
|
3870
|
+
if max_requests <= 0:
|
|
3871
|
+
max_requests = max(16, min(MAX_SFTP_READ_LEN // block_size, 128))
|
|
3872
|
+
|
|
3862
3873
|
if isinstance(srcpaths, (bytes, str, PurePath)):
|
|
3863
3874
|
srcpaths = [srcpaths]
|
|
3864
3875
|
elif not isinstance(srcpaths, list):
|
|
@@ -3916,7 +3927,7 @@ class SFTPClient:
|
|
|
3916
3927
|
localpath: Optional[_SFTPPath] = None, *,
|
|
3917
3928
|
preserve: bool = False, recurse: bool = False,
|
|
3918
3929
|
follow_symlinks: bool = False, block_size: int = -1,
|
|
3919
|
-
max_requests: int =
|
|
3930
|
+
max_requests: int = -1,
|
|
3920
3931
|
progress_handler: SFTPProgressHandler = None,
|
|
3921
3932
|
error_handler: SFTPErrorHandler = None) -> None:
|
|
3922
3933
|
"""Download remote files
|
|
@@ -3957,7 +3968,9 @@ class SFTPClient:
|
|
|
3957
3968
|
doesn't advertise limits.
|
|
3958
3969
|
|
|
3959
3970
|
The max_requests argument specifies the maximum number of
|
|
3960
|
-
parallel read or write requests issued, defaulting to
|
|
3971
|
+
parallel read or write requests issued, defaulting to a
|
|
3972
|
+
value between 16 and 128 depending on the selected block
|
|
3973
|
+
size to avoid excessive memory usage.
|
|
3961
3974
|
|
|
3962
3975
|
If progress_handler is specified, it will be called after
|
|
3963
3976
|
each block of a file is successfully downloaded. The arguments
|
|
@@ -4022,7 +4035,7 @@ class SFTPClient:
|
|
|
4022
4035
|
remotepath: Optional[_SFTPPath] = None, *,
|
|
4023
4036
|
preserve: bool = False, recurse: bool = False,
|
|
4024
4037
|
follow_symlinks: bool = False, block_size: int = -1,
|
|
4025
|
-
max_requests: int =
|
|
4038
|
+
max_requests: int = -1,
|
|
4026
4039
|
progress_handler: SFTPProgressHandler = None,
|
|
4027
4040
|
error_handler: SFTPErrorHandler = None) -> None:
|
|
4028
4041
|
"""Upload local files
|
|
@@ -4063,7 +4076,9 @@ class SFTPClient:
|
|
|
4063
4076
|
doesn't advertise limits.
|
|
4064
4077
|
|
|
4065
4078
|
The max_requests argument specifies the maximum number of
|
|
4066
|
-
parallel read or write requests issued, defaulting to
|
|
4079
|
+
parallel read or write requests issued, defaulting to a
|
|
4080
|
+
value between 16 and 128 depending on the selected block
|
|
4081
|
+
size to avoid excessive memory usage.
|
|
4067
4082
|
|
|
4068
4083
|
If progress_handler is specified, it will be called after
|
|
4069
4084
|
each block of a file is successfully uploaded. The arguments
|
|
@@ -4128,7 +4143,7 @@ class SFTPClient:
|
|
|
4128
4143
|
dstpath: Optional[_SFTPPath] = None, *,
|
|
4129
4144
|
preserve: bool = False, recurse: bool = False,
|
|
4130
4145
|
follow_symlinks: bool = False, block_size: int = -1,
|
|
4131
|
-
max_requests: int =
|
|
4146
|
+
max_requests: int = -1,
|
|
4132
4147
|
progress_handler: SFTPProgressHandler = None,
|
|
4133
4148
|
error_handler: SFTPErrorHandler = None,
|
|
4134
4149
|
remote_only: bool = False) -> None:
|
|
@@ -4170,7 +4185,9 @@ class SFTPClient:
|
|
|
4170
4185
|
doesn't advertise limits.
|
|
4171
4186
|
|
|
4172
4187
|
The max_requests argument specifies the maximum number of
|
|
4173
|
-
parallel read or write requests issued, defaulting to
|
|
4188
|
+
parallel read or write requests issued, defaulting to a
|
|
4189
|
+
value between 16 and 128 depending on the selected block
|
|
4190
|
+
size to avoid excessive memory usage.
|
|
4174
4191
|
|
|
4175
4192
|
If progress_handler is specified, it will be called after
|
|
4176
4193
|
each block of a file is successfully copied. The arguments
|
|
@@ -4238,7 +4255,7 @@ class SFTPClient:
|
|
|
4238
4255
|
localpath: Optional[_SFTPPath] = None, *,
|
|
4239
4256
|
preserve: bool = False, recurse: bool = False,
|
|
4240
4257
|
follow_symlinks: bool = False, block_size: int = -1,
|
|
4241
|
-
max_requests: int =
|
|
4258
|
+
max_requests: int = -1,
|
|
4242
4259
|
progress_handler: SFTPProgressHandler = None,
|
|
4243
4260
|
error_handler: SFTPErrorHandler = None) -> None:
|
|
4244
4261
|
"""Download remote files with glob pattern match
|
|
@@ -4261,7 +4278,7 @@ class SFTPClient:
|
|
|
4261
4278
|
remotepath: Optional[_SFTPPath] = None, *,
|
|
4262
4279
|
preserve: bool = False, recurse: bool = False,
|
|
4263
4280
|
follow_symlinks: bool = False, block_size: int = -1,
|
|
4264
|
-
max_requests: int =
|
|
4281
|
+
max_requests: int = -1,
|
|
4265
4282
|
progress_handler: SFTPProgressHandler = None,
|
|
4266
4283
|
error_handler: SFTPErrorHandler = None) -> None:
|
|
4267
4284
|
"""Upload local files with glob pattern match
|
|
@@ -4284,7 +4301,7 @@ class SFTPClient:
|
|
|
4284
4301
|
dstpath: Optional[_SFTPPath] = None, *,
|
|
4285
4302
|
preserve: bool = False, recurse: bool = False,
|
|
4286
4303
|
follow_symlinks: bool = False, block_size: int = -1,
|
|
4287
|
-
max_requests: int =
|
|
4304
|
+
max_requests: int = -1,
|
|
4288
4305
|
progress_handler: SFTPProgressHandler = None,
|
|
4289
4306
|
error_handler: SFTPErrorHandler = None,
|
|
4290
4307
|
remote_only: bool = False) -> None:
|
|
@@ -4586,7 +4603,7 @@ class SFTPClient:
|
|
|
4586
4603
|
attrs: SFTPAttrs = SFTPAttrs(),
|
|
4587
4604
|
encoding: Optional[str] = 'utf-8', errors: str = 'strict',
|
|
4588
4605
|
block_size: int = -1,
|
|
4589
|
-
max_requests: int =
|
|
4606
|
+
max_requests: int = -1) -> SFTPClientFile:
|
|
4590
4607
|
"""Open a remote file
|
|
4591
4608
|
|
|
4592
4609
|
This method opens a remote file and returns an
|
|
@@ -4662,7 +4679,9 @@ class SFTPClient:
|
|
|
4662
4679
|
default of using the server-advertised limits.
|
|
4663
4680
|
|
|
4664
4681
|
The max_requests argument specifies the maximum number of
|
|
4665
|
-
parallel read or write requests issued, defaulting to
|
|
4682
|
+
parallel read or write requests issued, defaulting to a
|
|
4683
|
+
value between 16 and 128 depending on the selected block
|
|
4684
|
+
size to avoid excessive memory usage.
|
|
4666
4685
|
|
|
4667
4686
|
:param path:
|
|
4668
4687
|
The name of the remote file to open
|
|
@@ -4718,7 +4737,7 @@ class SFTPClient:
|
|
|
4718
4737
|
attrs: SFTPAttrs = SFTPAttrs(),
|
|
4719
4738
|
encoding: Optional[str] = 'utf-8', errors: str = 'strict',
|
|
4720
4739
|
block_size: int = -1,
|
|
4721
|
-
max_requests: int =
|
|
4740
|
+
max_requests: int = -1) -> SFTPClientFile:
|
|
4722
4741
|
"""Open a remote file using SFTP v5/v6 flags
|
|
4723
4742
|
|
|
4724
4743
|
This method is very similar to :meth:`open`, but the pflags_or_mode
|
|
@@ -7515,6 +7534,14 @@ class SFTPServer:
|
|
|
7515
7534
|
"""
|
|
7516
7535
|
|
|
7517
7536
|
path = os.readlink(_to_local_path(self.map_path(path)))
|
|
7537
|
+
|
|
7538
|
+
if sys.platform == 'win32' and \
|
|
7539
|
+
path.startswith('\\\\?\\'): # pragma: no cover
|
|
7540
|
+
path = path[4:]
|
|
7541
|
+
|
|
7542
|
+
if self._chroot:
|
|
7543
|
+
path = os.path.realpath(path)
|
|
7544
|
+
|
|
7518
7545
|
return self.reverse_map_path(_from_local_path(path))
|
|
7519
7546
|
|
|
7520
7547
|
def symlink(self, oldpath: bytes, newpath: bytes) -> MaybeAwait[None]:
|
|
@@ -7780,7 +7807,13 @@ class LocalFS:
|
|
|
7780
7807
|
async def readlink(self, path: bytes) -> bytes:
|
|
7781
7808
|
"""Return the target of a local symbolic link"""
|
|
7782
7809
|
|
|
7783
|
-
|
|
7810
|
+
path = os.readlink(_to_local_path(path))
|
|
7811
|
+
|
|
7812
|
+
if sys.platform == 'win32' and \
|
|
7813
|
+
path.startswith('\\\\?\\'): # pragma: no cover
|
|
7814
|
+
path = path[4:]
|
|
7815
|
+
|
|
7816
|
+
return _from_local_path(path)
|
|
7784
7817
|
|
|
7785
7818
|
async def symlink(self, oldpath: bytes, newpath: bytes) -> None:
|
|
7786
7819
|
"""Create a local symbolic link"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: asyncssh
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.20.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
|
|
@@ -3,6 +3,36 @@
|
|
|
3
3
|
Change Log
|
|
4
4
|
==========
|
|
5
5
|
|
|
6
|
+
Release 2.20.0 (17 Feb 2025)
|
|
7
|
+
----------------------------
|
|
8
|
+
|
|
9
|
+
* Added support for specifying an explicit path when configuring
|
|
10
|
+
agent forwarding. Thanks go to Aleksandr Ilin for pointing out
|
|
11
|
+
that this options supports more than just a boolean value.
|
|
12
|
+
|
|
13
|
+
* Added support for environment variable expansion in SSH config,
|
|
14
|
+
for options which support percent expansion.
|
|
15
|
+
|
|
16
|
+
* Added a new begin_auth callback in SSHClient, reporting the
|
|
17
|
+
username being sent during SSH client authentication. This can be
|
|
18
|
+
useful when the user is conditionally set via an SSH config file.
|
|
19
|
+
|
|
20
|
+
* Improved strict-kex interoperability during re-keying. Thanks go
|
|
21
|
+
to GitHub user emeryalden for reporting this issue and helping
|
|
22
|
+
to track down the source of the problem.
|
|
23
|
+
|
|
24
|
+
* Updated SFTP max_requests default to reduce memory usage when
|
|
25
|
+
using large block sizes.
|
|
26
|
+
|
|
27
|
+
* Updated testing to add Python 3.13 and drop Python 3.7, avoiding
|
|
28
|
+
deprecation warnings from the cryptography package.
|
|
29
|
+
|
|
30
|
+
* Fixed unit test issues under Windows, allowing unit tests to run
|
|
31
|
+
on Windows on all supported versions of Python.
|
|
32
|
+
|
|
33
|
+
* Fixed a couple of issues with Python 3.14. Thanks go to Georg
|
|
34
|
+
Sauthoff for initially reporting this.
|
|
35
|
+
|
|
6
36
|
Release 2.19.0 (12 Dec 2024)
|
|
7
37
|
----------------------------
|
|
8
38
|
|
|
@@ -15,12 +15,12 @@ classifiers = [
|
|
|
15
15
|
'License :: OSI Approved',
|
|
16
16
|
'Operating System :: MacOS :: MacOS X',
|
|
17
17
|
'Operating System :: POSIX',
|
|
18
|
-
'Programming Language :: Python :: 3.7',
|
|
19
18
|
'Programming Language :: Python :: 3.8',
|
|
20
19
|
'Programming Language :: Python :: 3.9',
|
|
21
20
|
'Programming Language :: Python :: 3.10',
|
|
22
21
|
'Programming Language :: Python :: 3.11',
|
|
23
22
|
'Programming Language :: Python :: 3.12',
|
|
23
|
+
'Programming Language :: Python :: 3.13',
|
|
24
24
|
'Topic :: Internet',
|
|
25
25
|
'Topic :: Security :: Cryptography',
|
|
26
26
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
|
@@ -26,6 +26,7 @@ import shutil
|
|
|
26
26
|
import signal
|
|
27
27
|
import socket
|
|
28
28
|
import subprocess
|
|
29
|
+
import sys
|
|
29
30
|
|
|
30
31
|
import asyncssh
|
|
31
32
|
from asyncssh.misc import async_context_manager
|
|
@@ -269,17 +270,20 @@ class ServerTestCase(AsyncTestCase):
|
|
|
269
270
|
if 'XAUTHORITY' in os.environ: # pragma: no cover
|
|
270
271
|
del os.environ['XAUTHORITY']
|
|
271
272
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
273
|
+
if sys.platform != 'win32':
|
|
274
|
+
try:
|
|
275
|
+
output = run('ssh-agent -a agent 2>/dev/null')
|
|
276
|
+
except subprocess.CalledProcessError: # pragma: no cover
|
|
277
|
+
cls._agent_pid = None
|
|
278
|
+
else:
|
|
279
|
+
cls._agent_pid = int(output.splitlines()[2].split()[3][:-1])
|
|
278
280
|
|
|
279
|
-
|
|
281
|
+
os.environ['SSH_AUTH_SOCK'] = 'agent'
|
|
280
282
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
+
async with asyncssh.connect_agent() as agent:
|
|
284
|
+
await agent.add_keys([ckey_ecdsa, (ckey, ckey_cert)])
|
|
285
|
+
else: # pragma: no cover
|
|
286
|
+
cls._agent_pid = None
|
|
283
287
|
|
|
284
288
|
with open('ssh-keysign', 'wb'):
|
|
285
289
|
pass
|
|
@@ -288,14 +292,14 @@ class ServerTestCase(AsyncTestCase):
|
|
|
288
292
|
async def asyncTearDownClass(cls):
|
|
289
293
|
"""Shut down test server and agent"""
|
|
290
294
|
|
|
295
|
+
cls._server.close()
|
|
296
|
+
await cls._server.wait_closed()
|
|
297
|
+
|
|
291
298
|
tasks = all_tasks()
|
|
292
299
|
tasks.remove(current_task())
|
|
293
300
|
|
|
294
301
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
295
302
|
|
|
296
|
-
cls._server.close()
|
|
297
|
-
await cls._server.wait_closed()
|
|
298
|
-
|
|
299
303
|
if cls._agent_pid: # pragma: no branch
|
|
300
304
|
os.kill(cls._agent_pid, signal.SIGTERM)
|
|
301
305
|
|
|
@@ -862,14 +862,14 @@ class _TestChannel(ServerTestCase):
|
|
|
862
862
|
chan.close()
|
|
863
863
|
|
|
864
864
|
@asynctest
|
|
865
|
-
async def
|
|
866
|
-
"""Test SSH agent forwarding"""
|
|
865
|
+
async def test_agent_forwarding_explicit(self):
|
|
866
|
+
"""Test SSH agent forwarding with explicit path"""
|
|
867
867
|
|
|
868
868
|
if not self.agent_available(): # pragma: no cover
|
|
869
869
|
self.skipTest('ssh-agent not available')
|
|
870
870
|
|
|
871
871
|
async with self.connect(username='ckey',
|
|
872
|
-
agent_forwarding=
|
|
872
|
+
agent_forwarding='agent') as conn:
|
|
873
873
|
chan, session = await _create_session(conn, 'agent')
|
|
874
874
|
|
|
875
875
|
await chan.wait_closed()
|