asyncssh 2.23.0__tar.gz → 2.23.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.23.0/asyncssh.egg-info → asyncssh-2.23.1}/PKG-INFO +1 -1
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/config.py +17 -3
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/connection.py +11 -9
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/scp.py +4 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/sftp.py +8 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/version.py +1 -1
- {asyncssh-2.23.0 → asyncssh-2.23.1/asyncssh.egg-info}/PKG-INFO +1 -1
- {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/changes.rst +15 -1
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_config.py +9 -5
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_sftp.py +14 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/.coveragerc +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/.github/workflows/run_tests.yml +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/.gitignore +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/.readthedocs.yaml +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/CONTRIBUTING.rst +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/COPYRIGHT +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/LICENSE +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/MANIFEST.in +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/README.rst +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/__init__.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/agent.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/agent_unix.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/agent_win32.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/asn1.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/auth.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/auth_keys.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/channel.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/client.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/compression.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/constants.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/__init__.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/chacha.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/cipher.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/dh.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/dsa.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/ec.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/ec_params.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/ed.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/kdf.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/misc.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/pq.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/rsa.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/umac.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/x509.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/dsa.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/ecdsa.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/eddsa.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/editor.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/encryption.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/forward.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/gss.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/gss_unix.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/gss_win32.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/kex.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/kex_dh.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/kex_rsa.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/keysign.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/known_hosts.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/listener.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/logging.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/mac.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/misc.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/packet.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/pattern.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/pbe.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/pkcs11.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/process.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/public_key.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/py.typed +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/rsa.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/saslprep.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/session.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/sk.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/sk_ecdsa.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/sk_eddsa.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/socks.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/stream.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/subprocess.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/tuntap.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/x11.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh.egg-info/SOURCES.txt +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh.egg-info/dependency_links.txt +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh.egg-info/requires.txt +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh.egg-info/top_level.txt +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/_templates/sidebarbottom.html +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/_templates/sidebartop.html +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/api.rst +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/conf.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/contributing.rst +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/index.rst +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/requirements.txt +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/rftheme/layout.html +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/rftheme/static/rftheme.css_t +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/rftheme/theme.conf +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/rtd-req.txt +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/callback_client.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/callback_client2.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/callback_client3.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/callback_math_server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/chat_server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/check_exit_status.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/chroot_sftp_server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/direct_client.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/direct_server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/editor.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/gather_results.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/listening_client.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/local_forwarding_client.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/local_forwarding_client2.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/local_forwarding_server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/math_client.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/math_server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/redirect_input.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/redirect_local_pipe.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/redirect_remote_pipe.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/redirect_server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/remote_forwarding_client.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/remote_forwarding_client2.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/remote_forwarding_server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/reverse_client.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/reverse_server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/scp_client.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/set_environment.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/set_terminal.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/sftp_client.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/show_environment.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/show_terminal.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/simple_cert_server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/simple_client.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/simple_keyed_server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/simple_scp_server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/simple_server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/simple_sftp_server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/stream_direct_client.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/stream_direct_server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/stream_listening_client.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/mypy.ini +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/pylintrc +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/pyproject.toml +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/setup.cfg +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/__init__.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/gss_stub.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/gssapi_stub.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/keysign_stub.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/pkcs11_stub.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/server.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/sk_stub.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/sspi_stub.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_agent.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_asn1.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_auth.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_auth_keys.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_channel.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_compression.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_connection.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_connection_auth.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_editor.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_encryption.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_forward.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_kex.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_known_hosts.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_logging.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_mac.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_packet.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_pkcs11.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_process.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_public_key.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_saslprep.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_sk.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_stream.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_subprocess.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_tuntap.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_x11.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_x509.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/util.py +0 -0
- {asyncssh-2.23.0 → asyncssh-2.23.1}/tox.ini +0 -0
|
@@ -48,7 +48,8 @@ ConfigPaths = Union[None, FilePath, Sequence[FilePath]]
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
_token_pattern = re.compile(r'%(.)')
|
|
51
|
-
_env_pattern = re.compile(r'\${(
|
|
51
|
+
_env_pattern = re.compile(r'\${(.*?)}')
|
|
52
|
+
_unsafe_user_pattern = re.compile(r'^\.\.$|^~|^[A-Za-z]:|[/\\]|\$\{.*?\}')
|
|
52
53
|
|
|
53
54
|
|
|
54
55
|
def _exec(cmd: str) -> bool:
|
|
@@ -710,9 +711,22 @@ class SSHServerConfig(SSHConfig):
|
|
|
710
711
|
return None
|
|
711
712
|
|
|
712
713
|
def _set_tokens(self) -> None:
|
|
713
|
-
"""Set the tokens available for percent expansion
|
|
714
|
+
"""Set the tokens available for percent expansion
|
|
715
|
+
|
|
716
|
+
Only allow "safe" username substitutions. Unsafe usernames are:
|
|
717
|
+
|
|
718
|
+
- a username of exactly ".."
|
|
719
|
+
- a username beginning with a "~"
|
|
720
|
+
- a username beginning with a Windows drive letter and a ":"
|
|
721
|
+
- a username containing forward or backward slashes
|
|
722
|
+
- a username containing an env substitution like "${...}"
|
|
723
|
+
|
|
724
|
+
Note: this code assumes that saslprep has already been performed
|
|
725
|
+
on the username before it is accessed here.
|
|
726
|
+
|
|
727
|
+
"""
|
|
714
728
|
|
|
715
|
-
if
|
|
729
|
+
if _unsafe_user_pattern.search(self._user):
|
|
716
730
|
raise IllegalUserName('Unsafe username substitution')
|
|
717
731
|
|
|
718
732
|
self._tokens.update({'u': self._user})
|
|
@@ -553,7 +553,7 @@ async def _connect(options: _Options, config: DefTuple[ConfigPaths],
|
|
|
553
553
|
async def _listen(options: _Options, config: DefTuple[ConfigPaths],
|
|
554
554
|
loop: asyncio.AbstractEventLoop, flags: int,
|
|
555
555
|
backlog: int, sock: Optional[socket.socket],
|
|
556
|
-
reuse_address: bool, reuse_port: bool,
|
|
556
|
+
reuse_address: Optional[bool], reuse_port: Optional[bool],
|
|
557
557
|
conn_factory: Callable[[], _Conn],
|
|
558
558
|
msg: str) -> 'SSHAcceptor':
|
|
559
559
|
"""Make inbound TCP or SSH tunneled listener"""
|
|
@@ -9353,7 +9353,8 @@ async def listen(host = '', port: DefTuple[int] = (), *,
|
|
|
9353
9353
|
tunnel: DefTuple[_TunnelListener] = (),
|
|
9354
9354
|
family: DefTuple[int] = (), flags:int = socket.AI_PASSIVE,
|
|
9355
9355
|
backlog: int = 100, sock: Optional[socket.socket] = None,
|
|
9356
|
-
reuse_address: bool =
|
|
9356
|
+
reuse_address: Optional[bool] = None,
|
|
9357
|
+
reuse_port: Optional[bool] = None,
|
|
9357
9358
|
acceptor: _AcceptHandler = None,
|
|
9358
9359
|
error_handler: _ErrorHandler = None,
|
|
9359
9360
|
config: DefTuple[ConfigPaths] = (),
|
|
@@ -9411,7 +9412,7 @@ async def listen(host = '', port: DefTuple[int] = (), *,
|
|
|
9411
9412
|
port other existing sockets are bound to, so long as they all
|
|
9412
9413
|
set this flag when being created. If not specified, the
|
|
9413
9414
|
default is to not allow this. This option is not supported
|
|
9414
|
-
on Windows
|
|
9415
|
+
on Windows.
|
|
9415
9416
|
:param acceptor: (optional)
|
|
9416
9417
|
A `callable` or coroutine which will be called when the
|
|
9417
9418
|
SSH handshake completes on an accepted connection, taking
|
|
@@ -9441,8 +9442,8 @@ async def listen(host = '', port: DefTuple[int] = (), *,
|
|
|
9441
9442
|
:type flags: flags to pass to :meth:`getaddrinfo() <socket.getaddrinfo>`
|
|
9442
9443
|
:type backlog: `int`
|
|
9443
9444
|
:type sock: :class:`socket.socket` or `None`
|
|
9444
|
-
:type reuse_address: `bool`
|
|
9445
|
-
:type reuse_port: `bool`
|
|
9445
|
+
:type reuse_address: `bool` or `None`
|
|
9446
|
+
:type reuse_port: `bool` or `None`
|
|
9446
9447
|
:type acceptor: `callable` or coroutine
|
|
9447
9448
|
:type error_handler: `callable`
|
|
9448
9449
|
:type config: `list` of `str`
|
|
@@ -9478,7 +9479,8 @@ async def listen_reverse(host = '', port: DefTuple[int] = (), *,
|
|
|
9478
9479
|
family: DefTuple[int] = (),
|
|
9479
9480
|
flags: int = socket.AI_PASSIVE, backlog: int = 100,
|
|
9480
9481
|
sock: Optional[socket.socket] = None,
|
|
9481
|
-
reuse_address: bool =
|
|
9482
|
+
reuse_address: Optional[bool] = None,
|
|
9483
|
+
reuse_port: Optional[bool] = None,
|
|
9482
9484
|
acceptor: _AcceptHandler = None,
|
|
9483
9485
|
error_handler: _ErrorHandler = None,
|
|
9484
9486
|
config: DefTuple[ConfigPaths] = (),
|
|
@@ -9545,7 +9547,7 @@ async def listen_reverse(host = '', port: DefTuple[int] = (), *,
|
|
|
9545
9547
|
port other existing sockets are bound to, so long as they all
|
|
9546
9548
|
set this flag when being created. If not specified, the
|
|
9547
9549
|
default is to not allow this. This option is not supported
|
|
9548
|
-
on Windows
|
|
9550
|
+
on Windows.
|
|
9549
9551
|
:param acceptor: (optional)
|
|
9550
9552
|
A `callable` or coroutine which will be called when the
|
|
9551
9553
|
SSH handshake completes on an accepted connection, taking
|
|
@@ -9580,8 +9582,8 @@ async def listen_reverse(host = '', port: DefTuple[int] = (), *,
|
|
|
9580
9582
|
:type flags: flags to pass to :meth:`getaddrinfo() <socket.getaddrinfo>`
|
|
9581
9583
|
:type backlog: `int`
|
|
9582
9584
|
:type sock: :class:`socket.socket` or `None`
|
|
9583
|
-
:type reuse_address: `bool`
|
|
9584
|
-
:type reuse_port: `bool`
|
|
9585
|
+
:type reuse_address: `bool` or `None`
|
|
9586
|
+
:type reuse_port: `bool` or `None`
|
|
9585
9587
|
:type acceptor: `callable` or coroutine
|
|
9586
9588
|
:type error_handler: `callable`
|
|
9587
9589
|
:type config: `list` of `str`
|
|
@@ -136,6 +136,10 @@ def _parse_cd_args(args: bytes) -> Tuple[int, int, bytes]:
|
|
|
136
136
|
|
|
137
137
|
try:
|
|
138
138
|
permissions, size, name = args.split(None, 2)
|
|
139
|
+
|
|
140
|
+
if b'/' in name or b'\\' in name or name == b'..':
|
|
141
|
+
raise _scp_error(SFTPBadMessage, 'Invalid filename')
|
|
142
|
+
|
|
139
143
|
return int(permissions, 8), int(size), name
|
|
140
144
|
except ValueError:
|
|
141
145
|
raise _scp_error(SFTPBadMessage,
|
|
@@ -7009,6 +7009,14 @@ class SFTPServer:
|
|
|
7009
7009
|
tree. This will also affect path names returned by the
|
|
7010
7010
|
:meth:`realpath` and :meth:`readlink` methods.
|
|
7011
7011
|
|
|
7012
|
+
|
|
7013
|
+
.. note:: AsyncSSH prevents creation of links to files outside
|
|
7014
|
+
of the selected chroot directory. However, pre-existing
|
|
7015
|
+
links inside the chroot which point outside of it will
|
|
7016
|
+
be followed. To completely isolate access to only the
|
|
7017
|
+
specified chroot, pre-existing links like this should
|
|
7018
|
+
be avoided.
|
|
7019
|
+
|
|
7012
7020
|
"""
|
|
7013
7021
|
|
|
7014
7022
|
# The default implementation of a number of these methods don't need self
|
|
@@ -3,7 +3,21 @@
|
|
|
3
3
|
Change Log
|
|
4
4
|
==========
|
|
5
5
|
|
|
6
|
-
Release 2.23.
|
|
6
|
+
Release 2.23.1 (6 Jun 2026)
|
|
7
|
+
---------------------------
|
|
8
|
+
|
|
9
|
+
* Fixed an SCP path traversal issue. Thanks go to Jaden Furtado for
|
|
10
|
+
reporting this issue.
|
|
11
|
+
|
|
12
|
+
* Expanded previous fix to block unsafe user substitutions in server
|
|
13
|
+
config. Thanks go to GitHub user cesabici-bit for reporting this
|
|
14
|
+
issue.
|
|
15
|
+
|
|
16
|
+
* Fixed default value for reuse_address and reuse_port, matching
|
|
17
|
+
the behaavior of asyncio.create_server(). Thanks go to Alexander
|
|
18
|
+
Shlemin for reporting the inconsistency.
|
|
19
|
+
|
|
20
|
+
Release 2.23.0 (8 May 2026)
|
|
7
21
|
---------------------------
|
|
8
22
|
|
|
9
23
|
* Added support for "Match localnetwork". Thanks go to Théophile Bastian
|
|
@@ -547,9 +547,10 @@ class _TestClientConfig(_TestConfig):
|
|
|
547
547
|
def test_env_expansion(self):
|
|
548
548
|
"""Test environment variable expansion"""
|
|
549
549
|
|
|
550
|
-
config = self._parse_config(
|
|
550
|
+
config = self._parse_config(
|
|
551
|
+
'RemoteCommand ${HOME}/${USERPROFILE}/.ssh')
|
|
551
552
|
|
|
552
|
-
self.assertEqual(config.get('RemoteCommand'), '
|
|
553
|
+
self.assertEqual(config.get('RemoteCommand'), '././.ssh')
|
|
553
554
|
|
|
554
555
|
def test_invalid_env_expansion(self):
|
|
555
556
|
"""Test invalid environment variable expansion"""
|
|
@@ -598,10 +599,13 @@ class _TestServerConfig(_TestConfig):
|
|
|
598
599
|
config = self._parse_config('Match address 127.0.0.0/8\nPermitTTY no')
|
|
599
600
|
self.assertEqual(config.get('PermitTTY'), False)
|
|
600
601
|
|
|
601
|
-
def
|
|
602
|
-
"""Test
|
|
602
|
+
def test_unsafe_user(self):
|
|
603
|
+
"""Test unsafe characters in username"""
|
|
604
|
+
|
|
605
|
+
for user in ('xxx..yyy', 'xxx~yyy', 'Cxxx:'):
|
|
606
|
+
self._parse_config('AuthorizedKeysFile %u', user=user)
|
|
603
607
|
|
|
604
|
-
for user in ('..', '/xxx', '\\xxx'):
|
|
608
|
+
for user in ('..', '~xxx', 'C:xxx', '/xxx', '\\xxx', '${xxx}'):
|
|
605
609
|
with self.assertRaises(asyncssh.IllegalUserName):
|
|
606
610
|
self._parse_config('AuthorizedKeysFile %u', user=user)
|
|
607
611
|
|
|
@@ -5737,6 +5737,9 @@ class _TestSCPErrors(_CheckSCP):
|
|
|
5737
5737
|
|
|
5738
5738
|
if command.endswith('get_connection_lost'):
|
|
5739
5739
|
pass
|
|
5740
|
+
elif command.endswith('get_invalid_filename_response'):
|
|
5741
|
+
await process.stdin.read(1)
|
|
5742
|
+
process.stdout.write('C0644 0 ../src\n')
|
|
5740
5743
|
elif command.endswith('get_dir_no_recurse'):
|
|
5741
5744
|
await process.stdin.read(1)
|
|
5742
5745
|
process.stdout.write('D0755 0 src\n')
|
|
@@ -5772,6 +5775,17 @@ class _TestSCPErrors(_CheckSCP):
|
|
|
5772
5775
|
|
|
5773
5776
|
return await cls.create_server(process_factory=_handle_client)
|
|
5774
5777
|
|
|
5778
|
+
@asynctest
|
|
5779
|
+
async def test_get_invalid_filename_response(self):
|
|
5780
|
+
"""Test receiving directory when recurse wasn't requested"""
|
|
5781
|
+
|
|
5782
|
+
try:
|
|
5783
|
+
with self.assertRaises((SFTPBadMessage, SFTPConnectionLost)):
|
|
5784
|
+
await scp((self._scp_server, 'get_invalid_filename_response'),
|
|
5785
|
+
'dst')
|
|
5786
|
+
finally:
|
|
5787
|
+
remove('dst')
|
|
5788
|
+
|
|
5775
5789
|
@asynctest
|
|
5776
5790
|
async def test_get_directory_without_recurse(self):
|
|
5777
5791
|
"""Test receiving directory when recurse wasn't requested"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|