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.
Files changed (177) hide show
  1. {asyncssh-2.23.0/asyncssh.egg-info → asyncssh-2.23.1}/PKG-INFO +1 -1
  2. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/config.py +17 -3
  3. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/connection.py +11 -9
  4. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/scp.py +4 -0
  5. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/sftp.py +8 -0
  6. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/version.py +1 -1
  7. {asyncssh-2.23.0 → asyncssh-2.23.1/asyncssh.egg-info}/PKG-INFO +1 -1
  8. {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/changes.rst +15 -1
  9. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_config.py +9 -5
  10. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_sftp.py +14 -0
  11. {asyncssh-2.23.0 → asyncssh-2.23.1}/.coveragerc +0 -0
  12. {asyncssh-2.23.0 → asyncssh-2.23.1}/.github/workflows/run_tests.yml +0 -0
  13. {asyncssh-2.23.0 → asyncssh-2.23.1}/.gitignore +0 -0
  14. {asyncssh-2.23.0 → asyncssh-2.23.1}/.readthedocs.yaml +0 -0
  15. {asyncssh-2.23.0 → asyncssh-2.23.1}/CONTRIBUTING.rst +0 -0
  16. {asyncssh-2.23.0 → asyncssh-2.23.1}/COPYRIGHT +0 -0
  17. {asyncssh-2.23.0 → asyncssh-2.23.1}/LICENSE +0 -0
  18. {asyncssh-2.23.0 → asyncssh-2.23.1}/MANIFEST.in +0 -0
  19. {asyncssh-2.23.0 → asyncssh-2.23.1}/README.rst +0 -0
  20. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/__init__.py +0 -0
  21. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/agent.py +0 -0
  22. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/agent_unix.py +0 -0
  23. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/agent_win32.py +0 -0
  24. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/asn1.py +0 -0
  25. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/auth.py +0 -0
  26. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/auth_keys.py +0 -0
  27. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/channel.py +0 -0
  28. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/client.py +0 -0
  29. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/compression.py +0 -0
  30. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/constants.py +0 -0
  31. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/__init__.py +0 -0
  32. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/chacha.py +0 -0
  33. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/cipher.py +0 -0
  34. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/dh.py +0 -0
  35. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/dsa.py +0 -0
  36. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/ec.py +0 -0
  37. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/ec_params.py +0 -0
  38. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/ed.py +0 -0
  39. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/kdf.py +0 -0
  40. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/misc.py +0 -0
  41. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/pq.py +0 -0
  42. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/rsa.py +0 -0
  43. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/umac.py +0 -0
  44. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/crypto/x509.py +0 -0
  45. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/dsa.py +0 -0
  46. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/ecdsa.py +0 -0
  47. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/eddsa.py +0 -0
  48. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/editor.py +0 -0
  49. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/encryption.py +0 -0
  50. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/forward.py +0 -0
  51. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/gss.py +0 -0
  52. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/gss_unix.py +0 -0
  53. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/gss_win32.py +0 -0
  54. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/kex.py +0 -0
  55. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/kex_dh.py +0 -0
  56. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/kex_rsa.py +0 -0
  57. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/keysign.py +0 -0
  58. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/known_hosts.py +0 -0
  59. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/listener.py +0 -0
  60. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/logging.py +0 -0
  61. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/mac.py +0 -0
  62. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/misc.py +0 -0
  63. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/packet.py +0 -0
  64. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/pattern.py +0 -0
  65. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/pbe.py +0 -0
  66. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/pkcs11.py +0 -0
  67. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/process.py +0 -0
  68. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/public_key.py +0 -0
  69. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/py.typed +0 -0
  70. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/rsa.py +0 -0
  71. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/saslprep.py +0 -0
  72. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/server.py +0 -0
  73. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/session.py +0 -0
  74. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/sk.py +0 -0
  75. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/sk_ecdsa.py +0 -0
  76. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/sk_eddsa.py +0 -0
  77. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/socks.py +0 -0
  78. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/stream.py +0 -0
  79. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/subprocess.py +0 -0
  80. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/tuntap.py +0 -0
  81. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh/x11.py +0 -0
  82. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh.egg-info/SOURCES.txt +0 -0
  83. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh.egg-info/dependency_links.txt +0 -0
  84. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh.egg-info/requires.txt +0 -0
  85. {asyncssh-2.23.0 → asyncssh-2.23.1}/asyncssh.egg-info/top_level.txt +0 -0
  86. {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/_templates/sidebarbottom.html +0 -0
  87. {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/_templates/sidebartop.html +0 -0
  88. {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/api.rst +0 -0
  89. {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/conf.py +0 -0
  90. {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/contributing.rst +0 -0
  91. {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/index.rst +0 -0
  92. {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/requirements.txt +0 -0
  93. {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/rftheme/layout.html +0 -0
  94. {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/rftheme/static/rftheme.css_t +0 -0
  95. {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/rftheme/theme.conf +0 -0
  96. {asyncssh-2.23.0 → asyncssh-2.23.1}/docs/rtd-req.txt +0 -0
  97. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/callback_client.py +0 -0
  98. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/callback_client2.py +0 -0
  99. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/callback_client3.py +0 -0
  100. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/callback_math_server.py +0 -0
  101. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/chat_server.py +0 -0
  102. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/check_exit_status.py +0 -0
  103. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/chroot_sftp_server.py +0 -0
  104. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/direct_client.py +0 -0
  105. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/direct_server.py +0 -0
  106. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/editor.py +0 -0
  107. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/gather_results.py +0 -0
  108. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/listening_client.py +0 -0
  109. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/local_forwarding_client.py +0 -0
  110. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/local_forwarding_client2.py +0 -0
  111. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/local_forwarding_server.py +0 -0
  112. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/math_client.py +0 -0
  113. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/math_server.py +0 -0
  114. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/redirect_input.py +0 -0
  115. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/redirect_local_pipe.py +0 -0
  116. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/redirect_remote_pipe.py +0 -0
  117. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/redirect_server.py +0 -0
  118. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/remote_forwarding_client.py +0 -0
  119. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/remote_forwarding_client2.py +0 -0
  120. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/remote_forwarding_server.py +0 -0
  121. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/reverse_client.py +0 -0
  122. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/reverse_server.py +0 -0
  123. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/scp_client.py +0 -0
  124. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/set_environment.py +0 -0
  125. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/set_terminal.py +0 -0
  126. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/sftp_client.py +0 -0
  127. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/show_environment.py +0 -0
  128. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/show_terminal.py +0 -0
  129. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/simple_cert_server.py +0 -0
  130. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/simple_client.py +0 -0
  131. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/simple_keyed_server.py +0 -0
  132. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/simple_scp_server.py +0 -0
  133. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/simple_server.py +0 -0
  134. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/simple_sftp_server.py +0 -0
  135. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/stream_direct_client.py +0 -0
  136. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/stream_direct_server.py +0 -0
  137. {asyncssh-2.23.0 → asyncssh-2.23.1}/examples/stream_listening_client.py +0 -0
  138. {asyncssh-2.23.0 → asyncssh-2.23.1}/mypy.ini +0 -0
  139. {asyncssh-2.23.0 → asyncssh-2.23.1}/pylintrc +0 -0
  140. {asyncssh-2.23.0 → asyncssh-2.23.1}/pyproject.toml +0 -0
  141. {asyncssh-2.23.0 → asyncssh-2.23.1}/setup.cfg +0 -0
  142. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/__init__.py +0 -0
  143. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/gss_stub.py +0 -0
  144. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/gssapi_stub.py +0 -0
  145. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/keysign_stub.py +0 -0
  146. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/pkcs11_stub.py +0 -0
  147. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/server.py +0 -0
  148. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/sk_stub.py +0 -0
  149. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/sspi_stub.py +0 -0
  150. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_agent.py +0 -0
  151. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_asn1.py +0 -0
  152. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_auth.py +0 -0
  153. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_auth_keys.py +0 -0
  154. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_channel.py +0 -0
  155. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_compression.py +0 -0
  156. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_connection.py +0 -0
  157. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_connection_auth.py +0 -0
  158. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_editor.py +0 -0
  159. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_encryption.py +0 -0
  160. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_forward.py +0 -0
  161. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_kex.py +0 -0
  162. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_known_hosts.py +0 -0
  163. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_logging.py +0 -0
  164. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_mac.py +0 -0
  165. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_packet.py +0 -0
  166. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_pkcs11.py +0 -0
  167. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_process.py +0 -0
  168. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_public_key.py +0 -0
  169. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_saslprep.py +0 -0
  170. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_sk.py +0 -0
  171. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_stream.py +0 -0
  172. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_subprocess.py +0 -0
  173. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_tuntap.py +0 -0
  174. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_x11.py +0 -0
  175. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/test_x509.py +0 -0
  176. {asyncssh-2.23.0 → asyncssh-2.23.1}/tests/util.py +0 -0
  177. {asyncssh-2.23.0 → asyncssh-2.23.1}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asyncssh
3
- Version: 2.23.0
3
+ Version: 2.23.1
4
4
  Summary: AsyncSSH: Asynchronous SSHv2 client and server library
5
5
  Author-email: Ron Frederick <ronf@timeheart.net>
6
6
  License: EPL-2.0 OR GPL-2.0-or-later
@@ -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 self._user == '..' or '/' in self._user or '\\' in self._user:
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 = False, reuse_port: bool = False,
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 or Python versions prior to 3.4.4.
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 = False, reuse_port: bool = False,
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 or Python versions prior to 3.4.4.
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
@@ -26,4 +26,4 @@ __author_email__ = 'ronf@timeheart.net'
26
26
 
27
27
  __url__ = 'http://asyncssh.timeheart.net'
28
28
 
29
- __version__ = '2.23.0'
29
+ __version__ = '2.23.1'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asyncssh
3
- Version: 2.23.0
3
+ Version: 2.23.1
4
4
  Summary: AsyncSSH: Asynchronous SSHv2 client and server library
5
5
  Author-email: Ron Frederick <ronf@timeheart.net>
6
6
  License: EPL-2.0 OR GPL-2.0-or-later
@@ -3,7 +3,21 @@
3
3
  Change Log
4
4
  ==========
5
5
 
6
- Release 2.23.0 (8 Feb 2026)
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('RemoteCommand ${HOME}/.ssh')
550
+ config = self._parse_config(
551
+ 'RemoteCommand ${HOME}/${USERPROFILE}/.ssh')
551
552
 
552
- self.assertEqual(config.get('RemoteCommand'), './.ssh')
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 test_illegal_user(self):
602
- """Test update of match options"""
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