@@ -37,6 +37,8 @@ pub struct Config {
3737 pub proxy_command : Option < String > ,
3838 pub proxy_jump : Option < String > ,
3939 pub add_keys_to_agent : AddKeysToAgent ,
40+ pub user_known_hosts_file : Option < String > ,
41+ pub strict_host_key_checking : bool ,
4042}
4143
4244impl Config {
@@ -49,6 +51,8 @@ impl Config {
4951 proxy_command : None ,
5052 proxy_jump : None ,
5153 add_keys_to_agent : AddKeysToAgent :: default ( ) ,
54+ user_known_hosts_file : None ,
55+ strict_host_key_checking : true ,
5256 }
5357 }
5458}
@@ -138,26 +142,8 @@ pub fn parse(file: &str, host: &str) -> Result<Config, Error> {
138142 }
139143 }
140144 "identityfile" => {
141- let id = value. trim_start ( ) ;
142- if id. starts_with ( "~/" ) {
143- if let Some ( mut home) = home:: home_dir ( ) {
144- home. push ( id. split_at ( 2 ) . 1 ) ;
145- config. identity_file = Some (
146- home. to_str ( )
147- . ok_or_else ( || {
148- std:: io:: Error :: new (
149- std:: io:: ErrorKind :: Other ,
150- "Failed to convert home directory to string" ,
151- )
152- } ) ?
153- . to_string ( ) ,
154- ) ;
155- } else {
156- return Err ( Error :: NoHome ) ;
157- }
158- } else {
159- config. identity_file = Some ( id. to_string ( ) )
160- }
145+ config. identity_file =
146+ Some ( value. trim_start ( ) . strip_quotes ( ) . expand_home ( ) ?) ;
161147 }
162148 "proxycommand" => config. proxy_command = Some ( value. trim_start ( ) . to_string ( ) ) ,
163149 "proxyjump" => config. proxy_jump = Some ( value. trim_start ( ) . to_string ( ) ) ,
@@ -167,6 +153,14 @@ pub fn parse(file: &str, host: &str) -> Result<Config, Error> {
167153 "ask" => config. add_keys_to_agent = AddKeysToAgent :: Ask ,
168154 _ => config. add_keys_to_agent = AddKeysToAgent :: No ,
169155 } ,
156+ "userknownhostsfile" => {
157+ config. user_known_hosts_file =
158+ Some ( value. trim_start ( ) . strip_quotes ( ) . expand_home ( ) ?) ;
159+ }
160+ "stricthostkeychecking" => match value. to_lowercase ( ) . as_str ( ) {
161+ "no" => config. strict_host_key_checking = false ,
162+ _ => config. strict_host_key_checking = true ,
163+ } ,
170164 key => {
171165 debug ! ( "{:?}" , key) ;
172166 }
@@ -183,3 +177,128 @@ fn check_host_against_glob_pattern(candidate: &str, glob_pattern: &str) -> bool
183177 _ => false ,
184178 }
185179}
180+
181+ trait SshConfigStrExt {
182+ fn strip_quotes ( & self ) -> Self ;
183+ fn expand_home ( & self ) -> Result < String , Error > ;
184+ }
185+
186+ impl SshConfigStrExt for & str {
187+ fn strip_quotes ( & self ) -> Self {
188+ if self . len ( ) > 1
189+ && ( ( self . starts_with ( '\'' ) && self . ends_with ( '\'' ) )
190+ || ( self . starts_with ( '\"' ) && self . ends_with ( '\"' ) ) )
191+ {
192+ & self [ 1 ..self . len ( ) - 1 ]
193+ } else {
194+ self
195+ }
196+ }
197+
198+ fn expand_home ( & self ) -> Result < String , Error > {
199+ if self . starts_with ( "~/" ) {
200+ if let Some ( mut home) = home:: home_dir ( ) {
201+ home. push ( self . split_at ( 2 ) . 1 ) ;
202+ Ok ( home
203+ . to_str ( )
204+ . ok_or_else ( || {
205+ std:: io:: Error :: new (
206+ std:: io:: ErrorKind :: Other ,
207+ "Failed to convert home directory to string" ,
208+ )
209+ } ) ?
210+ . to_string ( ) )
211+ } else {
212+ Err ( Error :: NoHome )
213+ }
214+ } else {
215+ Ok ( self . to_string ( ) )
216+ }
217+ }
218+ }
219+
220+ #[ cfg( test) ]
221+ mod tests {
222+ #![ allow( clippy:: expect_used) ]
223+ use crate :: { parse, AddKeysToAgent , Config , SshConfigStrExt } ;
224+
225+ #[ test]
226+ fn strip_quotes ( ) {
227+ let value = "'this is a test'" ;
228+ assert_eq ! ( "this is a test" , value. strip_quotes( ) ) ;
229+ let value = "\" this is a test\" " ;
230+ assert_eq ! ( "this is a test" , value. strip_quotes( ) ) ;
231+ let value = "'this is a test\" " ;
232+ assert_eq ! ( "'this is a test\" " , value. strip_quotes( ) ) ;
233+ let value = "'this is a test" ;
234+ assert_eq ! ( "'this is a test" , value. strip_quotes( ) ) ;
235+ let value = "this is a test'" ;
236+ assert_eq ! ( "this is a test'" , value. strip_quotes( ) ) ;
237+ let value = "this is a test" ;
238+ assert_eq ! ( "this is a test" , value. strip_quotes( ) ) ;
239+ let value = "" ;
240+ assert_eq ! ( "" , value. strip_quotes( ) ) ;
241+ let value = "'" ;
242+ assert_eq ! ( "'" , value. strip_quotes( ) ) ;
243+ let value = "''" ;
244+ assert_eq ! ( "" , value. strip_quotes( ) ) ;
245+ }
246+
247+ #[ test]
248+ fn expand_home ( ) {
249+ let value = "~/some/folder" . expand_home ( ) . expect ( "expand_home" ) ;
250+ assert_eq ! (
251+ format!(
252+ "{}{}" ,
253+ home:: home_dir( ) . expect( "homedir" ) . to_str( ) . expect( "to_str" ) ,
254+ "/some/folder"
255+ ) ,
256+ value
257+ ) ;
258+ }
259+
260+ #[ test]
261+ fn default_config ( ) {
262+ let config: Config = Config :: default ( "some_host" ) ;
263+ assert_eq ! ( whoami:: username( ) , config. user) ;
264+ assert_eq ! ( "some_host" , config. host_name) ;
265+ assert_eq ! ( 22 , config. port) ;
266+ assert_eq ! ( None , config. identity_file) ;
267+ assert_eq ! ( None , config. proxy_command) ;
268+ assert_eq ! ( None , config. proxy_jump) ;
269+ assert_eq ! ( AddKeysToAgent :: No , config. add_keys_to_agent) ;
270+ assert_eq ! ( None , config. user_known_hosts_file) ;
271+ assert ! ( config. strict_host_key_checking) ;
272+ }
273+
274+ #[ test]
275+ fn basic_config ( ) {
276+ let value = r"#
277+ Host test_host
278+ IdentityFile '~/.ssh/id_ed25519'
279+ User trinity
280+ Hostname foo.com
281+ Port 23
282+ UserKnownHostsFile /some/special/host_file
283+ StrictHostKeyChecking no
284+ #" ;
285+ let identity_file = format ! (
286+ "{}{}" ,
287+ home:: home_dir( ) . expect( "homedir" ) . to_str( ) . expect( "to_str" ) ,
288+ "/.ssh/id_ed25519"
289+ ) ;
290+ let config = parse ( value, "test_host" ) . expect ( "parse" ) ;
291+ assert_eq ! ( "trinity" , config. user) ;
292+ assert_eq ! ( "foo.com" , config. host_name) ;
293+ assert_eq ! ( 23 , config. port) ;
294+ assert_eq ! ( Some ( identity_file) , config. identity_file) ;
295+ assert_eq ! ( None , config. proxy_command) ;
296+ assert_eq ! ( None , config. proxy_jump) ;
297+ assert_eq ! ( AddKeysToAgent :: No , config. add_keys_to_agent) ;
298+ assert_eq ! (
299+ Some ( "/some/special/host_file" ) ,
300+ config. user_known_hosts_file. as_deref( )
301+ ) ;
302+ assert ! ( !config. strict_host_key_checking) ;
303+ }
304+ }
0 commit comments