Skip to content

Commit 68fff93

Browse files
authored
Add support for StrictHostKeyChecking and UserKnownHostsFile (#386)
1 parent 33e6b72 commit 68fff93

File tree

1 file changed

+139
-20
lines changed

1 file changed

+139
-20
lines changed

russh-config/src/lib.rs

Lines changed: 139 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -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

4244
impl 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

Comments
 (0)