asyncssh 2.18.0__tar.gz → 2.19.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.
Files changed (178) hide show
  1. {asyncssh-2.18.0/asyncssh.egg-info → asyncssh-2.19.0}/PKG-INFO +6 -7
  2. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/config.py +86 -32
  3. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/connection.py +255 -37
  4. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/misc.py +1 -1
  5. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/public_key.py +7 -4
  6. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/sftp.py +199 -32
  7. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/sk.py +90 -19
  8. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/sk_ecdsa.py +45 -22
  9. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/sk_eddsa.py +11 -12
  10. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/version.py +1 -1
  11. {asyncssh-2.18.0 → asyncssh-2.19.0/asyncssh.egg-info}/PKG-INFO +6 -7
  12. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh.egg-info/SOURCES.txt +1 -1
  13. {asyncssh-2.18.0 → asyncssh-2.19.0}/docs/api.rst +30 -6
  14. {asyncssh-2.18.0 → asyncssh-2.19.0}/docs/changes.rst +33 -0
  15. asyncssh-2.19.0/pyproject.toml +59 -0
  16. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/sk_stub.py +100 -18
  17. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_config.py +41 -5
  18. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_connection.py +123 -1
  19. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_public_key.py +10 -0
  20. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_sftp.py +114 -12
  21. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_sk.py +18 -2
  22. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/util.py +25 -0
  23. asyncssh-2.18.0/setup.py +0 -92
  24. {asyncssh-2.18.0 → asyncssh-2.19.0}/.coveragerc +0 -0
  25. {asyncssh-2.18.0 → asyncssh-2.19.0}/.github/workflows/run_tests.yml +0 -0
  26. {asyncssh-2.18.0 → asyncssh-2.19.0}/.gitignore +0 -0
  27. {asyncssh-2.18.0 → asyncssh-2.19.0}/.readthedocs.yaml +0 -0
  28. {asyncssh-2.18.0 → asyncssh-2.19.0}/CONTRIBUTING.rst +0 -0
  29. {asyncssh-2.18.0 → asyncssh-2.19.0}/COPYRIGHT +0 -0
  30. {asyncssh-2.18.0 → asyncssh-2.19.0}/LICENSE +0 -0
  31. {asyncssh-2.18.0 → asyncssh-2.19.0}/MANIFEST.in +0 -0
  32. {asyncssh-2.18.0 → asyncssh-2.19.0}/README.rst +0 -0
  33. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/__init__.py +0 -0
  34. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/agent.py +0 -0
  35. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/agent_unix.py +0 -0
  36. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/agent_win32.py +0 -0
  37. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/asn1.py +0 -0
  38. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/auth.py +0 -0
  39. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/auth_keys.py +0 -0
  40. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/channel.py +0 -0
  41. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/client.py +0 -0
  42. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/compression.py +0 -0
  43. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/constants.py +0 -0
  44. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/crypto/__init__.py +0 -0
  45. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/crypto/chacha.py +0 -0
  46. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/crypto/cipher.py +0 -0
  47. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/crypto/dh.py +0 -0
  48. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/crypto/dsa.py +0 -0
  49. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/crypto/ec.py +0 -0
  50. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/crypto/ec_params.py +0 -0
  51. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/crypto/ed.py +0 -0
  52. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/crypto/kdf.py +0 -0
  53. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/crypto/misc.py +0 -0
  54. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/crypto/pq.py +0 -0
  55. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/crypto/rsa.py +0 -0
  56. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/crypto/umac.py +0 -0
  57. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/crypto/x509.py +0 -0
  58. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/dsa.py +0 -0
  59. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/ecdsa.py +0 -0
  60. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/eddsa.py +0 -0
  61. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/editor.py +0 -0
  62. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/encryption.py +0 -0
  63. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/forward.py +0 -0
  64. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/gss.py +0 -0
  65. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/gss_unix.py +0 -0
  66. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/gss_win32.py +0 -0
  67. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/kex.py +0 -0
  68. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/kex_dh.py +0 -0
  69. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/kex_rsa.py +0 -0
  70. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/keysign.py +0 -0
  71. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/known_hosts.py +0 -0
  72. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/listener.py +0 -0
  73. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/logging.py +0 -0
  74. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/mac.py +0 -0
  75. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/packet.py +0 -0
  76. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/pattern.py +0 -0
  77. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/pbe.py +0 -0
  78. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/pkcs11.py +0 -0
  79. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/process.py +0 -0
  80. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/py.typed +0 -0
  81. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/rsa.py +0 -0
  82. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/saslprep.py +0 -0
  83. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/scp.py +0 -0
  84. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/server.py +0 -0
  85. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/session.py +0 -0
  86. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/socks.py +0 -0
  87. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/stream.py +0 -0
  88. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/subprocess.py +0 -0
  89. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/tuntap.py +0 -0
  90. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh/x11.py +0 -0
  91. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh.egg-info/dependency_links.txt +0 -0
  92. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh.egg-info/requires.txt +0 -0
  93. {asyncssh-2.18.0 → asyncssh-2.19.0}/asyncssh.egg-info/top_level.txt +0 -0
  94. {asyncssh-2.18.0 → asyncssh-2.19.0}/docs/_templates/sidebarbottom.html +0 -0
  95. {asyncssh-2.18.0 → asyncssh-2.19.0}/docs/_templates/sidebartop.html +0 -0
  96. {asyncssh-2.18.0 → asyncssh-2.19.0}/docs/conf.py +0 -0
  97. {asyncssh-2.18.0 → asyncssh-2.19.0}/docs/contributing.rst +0 -0
  98. {asyncssh-2.18.0 → asyncssh-2.19.0}/docs/index.rst +0 -0
  99. {asyncssh-2.18.0 → asyncssh-2.19.0}/docs/requirements.txt +0 -0
  100. {asyncssh-2.18.0 → asyncssh-2.19.0}/docs/rftheme/layout.html +0 -0
  101. {asyncssh-2.18.0 → asyncssh-2.19.0}/docs/rftheme/static/rftheme.css_t +0 -0
  102. {asyncssh-2.18.0 → asyncssh-2.19.0}/docs/rftheme/theme.conf +0 -0
  103. {asyncssh-2.18.0 → asyncssh-2.19.0}/docs/rtd-req.txt +0 -0
  104. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/callback_client.py +0 -0
  105. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/callback_client2.py +0 -0
  106. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/callback_client3.py +0 -0
  107. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/callback_math_server.py +0 -0
  108. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/chat_server.py +0 -0
  109. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/check_exit_status.py +0 -0
  110. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/chroot_sftp_server.py +0 -0
  111. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/direct_client.py +0 -0
  112. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/direct_server.py +0 -0
  113. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/editor.py +0 -0
  114. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/gather_results.py +0 -0
  115. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/listening_client.py +0 -0
  116. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/local_forwarding_client.py +0 -0
  117. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/local_forwarding_client2.py +0 -0
  118. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/local_forwarding_server.py +0 -0
  119. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/math_client.py +0 -0
  120. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/math_server.py +0 -0
  121. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/redirect_input.py +0 -0
  122. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/redirect_local_pipe.py +0 -0
  123. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/redirect_remote_pipe.py +0 -0
  124. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/redirect_server.py +0 -0
  125. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/remote_forwarding_client.py +0 -0
  126. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/remote_forwarding_client2.py +0 -0
  127. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/remote_forwarding_server.py +0 -0
  128. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/reverse_client.py +0 -0
  129. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/reverse_server.py +0 -0
  130. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/scp_client.py +0 -0
  131. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/set_environment.py +0 -0
  132. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/set_terminal.py +0 -0
  133. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/sftp_client.py +0 -0
  134. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/show_environment.py +0 -0
  135. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/show_terminal.py +0 -0
  136. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/simple_cert_server.py +0 -0
  137. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/simple_client.py +0 -0
  138. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/simple_keyed_server.py +0 -0
  139. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/simple_scp_server.py +0 -0
  140. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/simple_server.py +0 -0
  141. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/simple_sftp_server.py +0 -0
  142. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/stream_direct_client.py +0 -0
  143. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/stream_direct_server.py +0 -0
  144. {asyncssh-2.18.0 → asyncssh-2.19.0}/examples/stream_listening_client.py +0 -0
  145. {asyncssh-2.18.0 → asyncssh-2.19.0}/mypy.ini +0 -0
  146. {asyncssh-2.18.0 → asyncssh-2.19.0}/pylintrc +0 -0
  147. {asyncssh-2.18.0 → asyncssh-2.19.0}/setup.cfg +0 -0
  148. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/__init__.py +0 -0
  149. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/gss_stub.py +0 -0
  150. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/gssapi_stub.py +0 -0
  151. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/keysign_stub.py +0 -0
  152. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/pkcs11_stub.py +0 -0
  153. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/server.py +0 -0
  154. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/sspi_stub.py +0 -0
  155. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_agent.py +0 -0
  156. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_asn1.py +0 -0
  157. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_auth.py +0 -0
  158. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_auth_keys.py +0 -0
  159. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_channel.py +0 -0
  160. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_compression.py +0 -0
  161. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_connection_auth.py +0 -0
  162. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_editor.py +0 -0
  163. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_encryption.py +0 -0
  164. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_forward.py +0 -0
  165. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_kex.py +0 -0
  166. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_known_hosts.py +0 -0
  167. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_logging.py +0 -0
  168. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_mac.py +0 -0
  169. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_packet.py +0 -0
  170. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_pkcs11.py +0 -0
  171. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_process.py +0 -0
  172. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_saslprep.py +0 -0
  173. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_stream.py +0 -0
  174. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_subprocess.py +0 -0
  175. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_tuntap.py +0 -0
  176. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_x11.py +0 -0
  177. {asyncssh-2.18.0 → asyncssh-2.19.0}/tests/test_x509.py +0 -0
  178. {asyncssh-2.18.0 → asyncssh-2.19.0}/tox.ini +0 -0
@@ -1,15 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: asyncssh
3
- Version: 2.18.0
3
+ Version: 2.19.0
4
4
  Summary: AsyncSSH: Asynchronous SSHv2 client and server library
5
- Home-page: http://asyncssh.timeheart.net
6
- Author: Ron Frederick
7
- Author-email: ronf@timeheart.net
8
- License: Eclipse Public License v2.0
5
+ Author-email: Ron Frederick <ronf@timeheart.net>
6
+ License: EPL-2.0 OR GPL-2.0-or-later
7
+ Project-URL: Homepage, http://asyncssh.timeheart.net
9
8
  Project-URL: Documentation, https://asyncssh.readthedocs.io
10
9
  Project-URL: Source, https://github.com/ronf/asyncssh
11
10
  Project-URL: Tracker, https://github.com/ronf/asyncssh/issues
12
- Platform: Any
13
11
  Classifier: Development Status :: 5 - Production/Stable
14
12
  Classifier: Environment :: Console
15
13
  Classifier: Intended Audience :: Developers
@@ -26,7 +24,8 @@ Classifier: Topic :: Internet
26
24
  Classifier: Topic :: Security :: Cryptography
27
25
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
26
  Classifier: Topic :: System :: Networking
29
- Requires-Python: >= 3.6
27
+ Requires-Python: >=3.6
28
+ Description-Content-Type: text/x-rst
30
29
  License-File: LICENSE
31
30
  Requires-Dist: cryptography>=39.0
32
31
  Requires-Dist: typing_extensions>=4.0.0
@@ -60,12 +60,15 @@ class SSHConfig:
60
60
  _percent_expand = {'AuthorizedKeysFile'}
61
61
  _handlers: Dict[str, Tuple[str, Callable]] = {}
62
62
 
63
- def __init__(self, last_config: Optional['SSHConfig'], reload: bool):
63
+ def __init__(self, last_config: Optional['SSHConfig'], reload: bool,
64
+ canonical: bool, final: bool):
64
65
  if last_config:
65
66
  self._last_options = last_config.get_options(reload)
66
67
  else:
67
68
  self._last_options = {}
68
69
 
70
+ self._canonical = canonical
71
+ self._final = True if final else None
69
72
  self._default_path = Path('~', '.ssh').expanduser()
70
73
  self._path = Path()
71
74
  self._line_no = 0
@@ -153,35 +156,53 @@ class SSHConfig:
153
156
 
154
157
  # pylint: disable=unused-argument
155
158
 
159
+ matching = True
160
+
156
161
  while args:
157
162
  match = args.pop(0).lower()
158
163
 
164
+ if match[0] == '!':
165
+ match = match[1:]
166
+ negated = True
167
+ else:
168
+ negated = False
169
+
170
+ if match == 'final' and self._final is None:
171
+ self._final = False
172
+
159
173
  if match == 'all':
160
- self._matching = True
161
- continue
174
+ result = True
175
+ elif match == 'canonical':
176
+ result = self._canonical
177
+ elif match == 'final':
178
+ result = cast(bool, self._final)
179
+ else:
180
+ match_val = self._match_val(match)
162
181
 
163
- match_val = self._match_val(match)
182
+ if match != 'exec' and match_val is None:
183
+ self._error(f'Invalid match condition {match}')
164
184
 
165
- if match != 'exec' and match_val is None:
166
- self._error('Invalid match condition')
185
+ try:
186
+ arg = args.pop(0)
187
+ except IndexError:
188
+ self._error(f'Missing {match} match pattern')
189
+
190
+ if matching:
191
+ if match == 'exec':
192
+ result = _exec(arg)
193
+ elif match in ('address', 'localaddress'):
194
+ host_pat = HostPatternList(arg)
195
+ ip = ip_address(cast(str, match_val)) \
196
+ if match_val else None
197
+ result = host_pat.matches(None, match_val, ip)
198
+ else:
199
+ wild_pat = WildcardPatternList(arg)
200
+ result = wild_pat.matches(match_val)
167
201
 
168
- try:
169
- if match == 'exec':
170
- self._matching = _exec(args.pop(0))
171
- elif match in ('address', 'localaddress'):
172
- host_pat = HostPatternList(args.pop(0))
173
- ip = ip_address(cast(str, match_val)) \
174
- if match_val else None
175
- self._matching = host_pat.matches(None, match_val, ip)
176
- else:
177
- wild_pat = WildcardPatternList(args.pop(0))
178
- self._matching = wild_pat.matches(match_val)
179
- except IndexError:
180
- self._error(f'Missing {match} match pattern')
202
+ if matching and result == negated:
203
+ matching = False
181
204
 
182
- if not self._matching:
183
- args.clear()
184
- break
205
+ self._matching = matching
185
206
 
186
207
  def _set_bool(self, option: str, args: List[str]) -> None:
187
208
  """Set a boolean config option"""
@@ -276,6 +297,23 @@ class SSHConfig:
276
297
  if option not in self._options:
277
298
  self._options[option] = value
278
299
 
300
+ def _set_canonicalize_host(self, option: str, args: List[str]) -> None:
301
+ """Set a canonicalize host config option"""
302
+
303
+ value_str = args.pop(0).lower()
304
+
305
+ if value_str in ('yes', 'true'):
306
+ value: Union[bool, str] = True
307
+ elif value_str in ('no', 'false'):
308
+ value = False
309
+ elif value_str == 'always':
310
+ value = value_str
311
+ else:
312
+ self._error(f'Invalid {option} value: {value_str}')
313
+
314
+ if option not in self._options:
315
+ self._options[option] = value
316
+
279
317
  def _set_rekey_limits(self, option: str, args: List[str]) -> None:
280
318
  """Set rekey limits config option"""
281
319
 
@@ -295,6 +333,11 @@ class SSHConfig:
295
333
  if option not in self._options:
296
334
  self._options[option] = byte_limit, time_limit
297
335
 
336
+ def has_match_final(self) -> bool:
337
+ """Return whether this config includes a 'Match final' block"""
338
+
339
+ return self._final is not None
340
+
298
341
  def parse(self, path: Path) -> None:
299
342
  """Parse an OpenSSH config file and return matching declarations"""
300
343
 
@@ -384,10 +427,10 @@ class SSHConfig:
384
427
  @classmethod
385
428
  def load(cls, last_config: Optional['SSHConfig'],
386
429
  config_paths: ConfigPaths, reload: bool,
387
- *args: object) -> 'SSHConfig':
430
+ canonical: bool, final: bool, *args: object) -> 'SSHConfig':
388
431
  """Load a list of OpenSSH config files into a config object"""
389
432
 
390
- config = cls(last_config, reload, *args)
433
+ config = cls(last_config, reload, canonical, final, *args)
391
434
 
392
435
  if config_paths:
393
436
  if isinstance(config_paths, (str, PurePath)):
@@ -429,8 +472,9 @@ class SSHClientConfig(SSHConfig):
429
472
  'IdentityFile', 'ProxyCommand', 'RemoteCommand'}
430
473
 
431
474
  def __init__(self, last_config: 'SSHConfig', reload: bool,
432
- local_user: str, user: str, host: str, port: int) -> None:
433
- super().__init__(last_config, reload)
475
+ canonical: bool, final: bool, local_user: str,
476
+ user: str, host: str, port: int) -> None:
477
+ super().__init__(last_config, reload, canonical, final)
434
478
 
435
479
  self._local_user = local_user
436
480
  self._orig_host = host
@@ -485,10 +529,10 @@ class SSHClientConfig(SSHConfig):
485
529
  value: Union[bool, str] = True
486
530
  elif value_str in ('no', 'false'):
487
531
  value = False
488
- elif value_str not in ('force', 'auto'):
489
- self._error(f'Invalid {option} value: {value_str}')
490
- else:
532
+ elif value_str in ('force', 'auto'):
491
533
  value = value_str
534
+ else:
535
+ self._error(f'Invalid {option} value: {value_str}')
492
536
 
493
537
  if option not in self._options:
494
538
  self._options[option] = value
@@ -531,6 +575,11 @@ class SSHClientConfig(SSHConfig):
531
575
 
532
576
  ('AddressFamily', SSHConfig._set_address_family),
533
577
  ('BindAddress', SSHConfig._set_string),
578
+ ('CanonicalDomains', SSHConfig._set_string_list),
579
+ ('CanonicalizeFallbackLocal', SSHConfig._set_bool),
580
+ ('CanonicalizeHostname', SSHConfig._set_canonicalize_host),
581
+ ('CanonicalizeMaxDots', SSHConfig._set_int),
582
+ ('CanonicalizePermittedCNAMEs', SSHConfig._set_string_list),
534
583
  ('CASignatureAlgorithms', SSHConfig._set_string),
535
584
  ('CertificateFile', SSHConfig._append_string),
536
585
  ('ChallengeResponseAuthentication', SSHConfig._set_bool),
@@ -579,9 +628,9 @@ class SSHServerConfig(SSHConfig):
579
628
  """Settings from an OpenSSH server config file"""
580
629
 
581
630
  def __init__(self, last_config: 'SSHConfig', reload: bool,
582
- local_addr: str, local_port: int, user: str,
583
- host: str, addr: str) -> None:
584
- super().__init__(last_config, reload)
631
+ canonical: bool, final: bool, local_addr: str,
632
+ local_port: int, user: str, host: str, addr: str) -> None:
633
+ super().__init__(last_config, reload, canonical, final)
585
634
 
586
635
  self._local_addr = local_addr
587
636
  self._local_port = local_port
@@ -618,6 +667,11 @@ class SSHServerConfig(SSHConfig):
618
667
  ('AuthorizedKeysFile', SSHConfig._set_string_list),
619
668
  ('AllowAgentForwarding', SSHConfig._set_bool),
620
669
  ('BindAddress', SSHConfig._set_string),
670
+ ('CanonicalDomains', SSHConfig._set_string_list),
671
+ ('CanonicalizeFallbackLocal', SSHConfig._set_bool),
672
+ ('CanonicalizeHostname', SSHConfig._set_canonicalize_host),
673
+ ('CanonicalizeMaxDots', SSHConfig._set_int),
674
+ ('CanonicalizePermittedCNAMEs', SSHConfig._set_string_list),
621
675
  ('CASignatureAlgorithms', SSHConfig._set_string),
622
676
  ('ChallengeResponseAuthentication', SSHConfig._set_bool),
623
677
  ('Ciphers', SSHConfig._set_string),