@@ -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 @@ def _match(self, option: str, args: List[str]) -> None:
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 @@ def _set_address_family(self, option: str, args: List[str]) -> None:
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 @@ def _set_rekey_limits(self, option: str, args: List[str]) -> None:
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 @@ def get_options(self, reload: bool) -> Dict[str, object]:
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 @@ def _set_request_tty(self, option: str, args: List[str]) -> None:
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 @@ def _set_tokens(self) -> None:
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 @@ def _set_tokens(self) -> None:
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 ),
0 commit comments