diff --git a/russh-config/src/lib.rs b/russh-config/src/lib.rs index d88cba20..2637f290 100644 --- a/russh-config/src/lib.rs +++ b/russh-config/src/lib.rs @@ -37,6 +37,8 @@ pub struct Config { pub proxy_command: Option, pub proxy_jump: Option, pub add_keys_to_agent: AddKeysToAgent, + pub user_known_hosts_file: Option, + pub strict_host_key_checking: bool, } impl Config { @@ -49,6 +51,8 @@ impl Config { proxy_command: None, proxy_jump: None, add_keys_to_agent: AddKeysToAgent::default(), + user_known_hosts_file: None, + strict_host_key_checking: true, } } } @@ -138,26 +142,8 @@ pub fn parse(file: &str, host: &str) -> Result { } } "identityfile" => { - let id = value.trim_start(); - if id.starts_with("~/") { - if let Some(mut home) = home::home_dir() { - home.push(id.split_at(2).1); - config.identity_file = Some( - home.to_str() - .ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::Other, - "Failed to convert home directory to string", - ) - })? - .to_string(), - ); - } else { - return Err(Error::NoHome); - } - } else { - config.identity_file = Some(id.to_string()) - } + config.identity_file = + Some(value.trim_start().strip_quotes().expand_home()?); } "proxycommand" => config.proxy_command = Some(value.trim_start().to_string()), "proxyjump" => config.proxy_jump = Some(value.trim_start().to_string()), @@ -167,6 +153,14 @@ pub fn parse(file: &str, host: &str) -> Result { "ask" => config.add_keys_to_agent = AddKeysToAgent::Ask, _ => config.add_keys_to_agent = AddKeysToAgent::No, }, + "userknownhostsfile" => { + config.user_known_hosts_file = + Some(value.trim_start().strip_quotes().expand_home()?); + } + "stricthostkeychecking" => match value.to_lowercase().as_str() { + "no" => config.strict_host_key_checking = false, + _ => config.strict_host_key_checking = true, + }, key => { debug!("{:?}", key); } @@ -183,3 +177,128 @@ fn check_host_against_glob_pattern(candidate: &str, glob_pattern: &str) -> bool _ => false, } } + +trait SshConfigStrExt { + fn strip_quotes(&self) -> Self; + fn expand_home(&self) -> Result; +} + +impl SshConfigStrExt for &str { + fn strip_quotes(&self) -> Self { + if self.len() > 1 + && ((self.starts_with('\'') && self.ends_with('\'')) + || (self.starts_with('\"') && self.ends_with('\"'))) + { + &self[1..self.len() - 1] + } else { + self + } + } + + fn expand_home(&self) -> Result { + if self.starts_with("~/") { + if let Some(mut home) = home::home_dir() { + home.push(self.split_at(2).1); + Ok(home + .to_str() + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to convert home directory to string", + ) + })? + .to_string()) + } else { + Err(Error::NoHome) + } + } else { + Ok(self.to_string()) + } + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::expect_used)] + use crate::{parse, AddKeysToAgent, Config, SshConfigStrExt}; + + #[test] + fn strip_quotes() { + let value = "'this is a test'"; + assert_eq!("this is a test", value.strip_quotes()); + let value = "\"this is a test\""; + assert_eq!("this is a test", value.strip_quotes()); + let value = "'this is a test\""; + assert_eq!("'this is a test\"", value.strip_quotes()); + let value = "'this is a test"; + assert_eq!("'this is a test", value.strip_quotes()); + let value = "this is a test'"; + assert_eq!("this is a test'", value.strip_quotes()); + let value = "this is a test"; + assert_eq!("this is a test", value.strip_quotes()); + let value = ""; + assert_eq!("", value.strip_quotes()); + let value = "'"; + assert_eq!("'", value.strip_quotes()); + let value = "''"; + assert_eq!("", value.strip_quotes()); + } + + #[test] + fn expand_home() { + let value = "~/some/folder".expand_home().expect("expand_home"); + assert_eq!( + format!( + "{}{}", + home::home_dir().expect("homedir").to_str().expect("to_str"), + "/some/folder" + ), + value + ); + } + + #[test] + fn default_config() { + let config: Config = Config::default("some_host"); + assert_eq!(whoami::username(), config.user); + assert_eq!("some_host", config.host_name); + assert_eq!(22, config.port); + assert_eq!(None, config.identity_file); + assert_eq!(None, config.proxy_command); + assert_eq!(None, config.proxy_jump); + assert_eq!(AddKeysToAgent::No, config.add_keys_to_agent); + assert_eq!(None, config.user_known_hosts_file); + assert!(config.strict_host_key_checking); + } + + #[test] + fn basic_config() { + let value = r"# +Host test_host + IdentityFile '~/.ssh/id_ed25519' + User trinity + Hostname foo.com + Port 23 + UserKnownHostsFile /some/special/host_file + StrictHostKeyChecking no +#"; + let identity_file = format!( + "{}{}", + home::home_dir().expect("homedir").to_str().expect("to_str"), + "/.ssh/id_ed25519" + ); + let config = parse(value, "test_host").expect("parse"); + assert_eq!("trinity", config.user); + assert_eq!("foo.com", config.host_name); + assert_eq!(23, config.port); + assert_eq!(Some(identity_file), config.identity_file); + assert_eq!(None, config.proxy_command); + assert_eq!(None, config.proxy_jump); + assert_eq!(AddKeysToAgent::No, config.add_keys_to_agent); + assert_eq!( + Some("/some/special/host_file"), + config.user_known_hosts_file.as_deref() + ); + assert!(!config.strict_host_key_checking); + } +}