@@ -17,7 +17,6 @@ use crate::canonical_properties::CanonicalProperty;
1717use crate :: error:: SshdConfigError ;
1818use crate :: inputs:: { CommandInfo , SSHD_CONFIG_FILEPATH } ;
1919use crate :: parser:: parse_text_to_map;
20- use crate :: repeat_keyword:: MULTI_ARG_KEYWORDS_SPACE_SEP ;
2120use crate :: util:: {
2221 build_command_info,
2322 extract_sshd_defaults,
@@ -134,11 +133,11 @@ pub fn get_sshd_settings(cmd_info: &CommandInfo, is_get: bool) -> Result<Map<Str
134133 result. insert ( "match" . to_string ( ) , match_value. clone ( ) ) ;
135134 }
136135
137- // sshd -T normalizes space-separated list keywords by stripping the quotes that preserve
138- // values containing spaces (e.g. Windows group names like "openssh users"), splitting them
139- // into separate entries . Prefer the value parsed directly from the config file, which retains
140- // the quoting , for these keywords when they are explicitly set .
141- prefer_explicit_space_sep_lists ( & mut result, & explicit_settings) ;
136+ // sshd -T strips the quotes that preserve values containing spaces (e.g. Windows group names
137+ // like "openssh users" or paths like "C:\Program Files\ssh\banner.txt") and normalizes their
138+ // casing . Prefer the value parsed directly from the config file, which retains the quoting and
139+ // the original casing , for any keyword whose explicit value contains whitespace .
140+ prefer_explicit_values_with_spaces ( & mut result, & explicit_settings) ;
142141
143142 if cmd_info. include_defaults {
144143 // get default from SSHD -T with empty config
@@ -179,14 +178,24 @@ pub fn get_sshd_settings(cmd_info: &CommandInfo, is_get: bool) -> Result<Map<Str
179178 Ok ( result)
180179}
181180
182- fn prefer_explicit_space_sep_lists ( result : & mut Map < String , Value > , explicit_settings : & Map < String , Value > ) {
183- for keyword in MULTI_ARG_KEYWORDS_SPACE_SEP {
184- if let Some ( value) = explicit_settings . get ( keyword ) {
185- result. insert ( ( * keyword) . to_string ( ) , value. clone ( ) ) ;
181+ fn prefer_explicit_values_with_spaces ( result : & mut Map < String , Value > , explicit_settings : & Map < String , Value > ) {
182+ for ( keyword, value ) in explicit_settings {
183+ if contains_whitespace ( value) {
184+ result. insert ( keyword. clone ( ) , value. clone ( ) ) ;
186185 }
187186 }
188187}
189188
189+ /// Whether the value, or any value nested within it, is a string containing whitespace.
190+ fn contains_whitespace ( value : & Value ) -> bool {
191+ match value {
192+ Value :: String ( s) => s. contains ( char:: is_whitespace) ,
193+ Value :: Array ( items) => items. iter ( ) . any ( contains_whitespace) ,
194+ Value :: Object ( map) => map. values ( ) . any ( contains_whitespace) ,
195+ _ => false
196+ }
197+ }
198+
190199#[ cfg( test) ]
191200mod tests {
192201 use super :: * ;
@@ -202,14 +211,46 @@ mod tests {
202211 let mut explicit_settings = Map :: new ( ) ;
203212 explicit_settings. insert ( "allowgroups" . to_string ( ) , json ! ( [ "administrators" , "openssh users" ] ) ) ;
204213
205- prefer_explicit_space_sep_lists ( & mut result, & explicit_settings) ;
214+ prefer_explicit_values_with_spaces ( & mut result, & explicit_settings) ;
206215
207216 assert_eq ! (
208217 result. get( "allowgroups" ) . unwrap( ) ,
209218 & json!( [ "administrators" , "openssh users" ] )
210219 ) ;
211220 }
212221
222+ #[ test]
223+ fn overrides_single_value_keyword_with_quoted_file_value ( ) {
224+ let mut result = Map :: new ( ) ;
225+ result. insert ( "banner" . to_string ( ) , json ! ( "c:\\ program files\\ ssh\\ sample_banner.txt" ) ) ;
226+
227+ let mut explicit_settings = Map :: new ( ) ;
228+ explicit_settings. insert ( "banner" . to_string ( ) , json ! ( "C:\\ Program Files\\ ssh\\ sample_banner.txt" ) ) ;
229+
230+ prefer_explicit_values_with_spaces ( & mut result, & explicit_settings) ;
231+
232+ assert_eq ! (
233+ result. get( "banner" ) . unwrap( ) ,
234+ & json!( "C:\\ Program Files\\ ssh\\ sample_banner.txt" )
235+ ) ;
236+ }
237+
238+ #[ test]
239+ fn overrides_nested_value_with_spaces ( ) {
240+ let mut result = Map :: new ( ) ;
241+ result. insert ( "subsystem" . to_string ( ) , json ! ( [ { "name" : "sftp" , "value" : "c:/program files/openssh/sftp-server.exe" } ] ) ) ;
242+
243+ let mut explicit_settings = Map :: new ( ) ;
244+ explicit_settings. insert ( "subsystem" . to_string ( ) , json ! ( [ { "name" : "sftp" , "value" : "C:/Program Files/OpenSSH/sftp-server.exe" } ] ) ) ;
245+
246+ prefer_explicit_values_with_spaces ( & mut result, & explicit_settings) ;
247+
248+ assert_eq ! (
249+ result. get( "subsystem" ) . unwrap( ) ,
250+ & json!( [ { "name" : "sftp" , "value" : "C:/Program Files/OpenSSH/sftp-server.exe" } ] )
251+ ) ;
252+ }
253+
213254 #[ test]
214255 fn leaves_keyword_absent_from_file_untouched ( ) {
215256 // allowgroups is present in sshd -T output but not explicitly set in the config file.
@@ -218,7 +259,7 @@ mod tests {
218259
219260 let explicit_settings = Map :: new ( ) ;
220261
221- prefer_explicit_space_sep_lists ( & mut result, & explicit_settings) ;
262+ prefer_explicit_values_with_spaces ( & mut result, & explicit_settings) ;
222263
223264 assert_eq ! (
224265 result. get( "allowgroups" ) . unwrap( ) ,
@@ -227,16 +268,18 @@ mod tests {
227268 }
228269
229270 #[ test]
230- fn leaves_non_space_sep_keyword_untouched ( ) {
231- // port is not a space-separated list keyword and must not be overridden.
271+ fn leaves_value_without_spaces_untouched ( ) {
232272 let mut result = Map :: new ( ) ;
233273 result. insert ( "port" . to_string ( ) , json ! ( [ 22 ] ) ) ;
274+ result. insert ( "allowgroups" . to_string ( ) , json ! ( [ "administrators" ] ) ) ;
234275
235276 let mut explicit_settings = Map :: new ( ) ;
236277 explicit_settings. insert ( "port" . to_string ( ) , json ! ( [ 2222 ] ) ) ;
278+ explicit_settings. insert ( "allowgroups" . to_string ( ) , json ! ( [ "openssh" ] ) ) ;
237279
238- prefer_explicit_space_sep_lists ( & mut result, & explicit_settings) ;
280+ prefer_explicit_values_with_spaces ( & mut result, & explicit_settings) ;
239281
240282 assert_eq ! ( result. get( "port" ) . unwrap( ) , & json!( [ 22 ] ) ) ;
283+ assert_eq ! ( result. get( "allowgroups" ) . unwrap( ) , & json!( [ "administrators" ] ) ) ;
241284 }
242285}
0 commit comments