use super::tokio_client::ServerCheckMethod;
use std::path::PathBuf;
use std::str::FromStr;
pub fn get_default_known_hosts_path() -> Option<PathBuf> {
dirs::home_dir().map(|home| home.join(".ssh").join("known_hosts"))
}
pub fn get_check_method(strict_mode: StrictHostKeyChecking) -> ServerCheckMethod {
match strict_mode {
StrictHostKeyChecking::Yes => match get_default_known_hosts_path() {
Some(known_hosts_path) => {
tracing::debug!(
"Using known_hosts file: {:?} (strict mode)",
known_hosts_path
);
ServerCheckMethod::KnownHostsFile(known_hosts_path.to_string_lossy().into_owned())
}
None => {
tracing::warn!(
"Could not determine known_hosts path; strict host key checking will fail closed"
);
ServerCheckMethod::DefaultKnownHostsFile
}
},
StrictHostKeyChecking::No => {
tracing::debug!("Host key checking disabled (strict mode = no)");
ServerCheckMethod::NoCheck
}
StrictHostKeyChecking::AcceptNew => match get_default_known_hosts_path() {
Some(known_hosts_path) => {
tracing::debug!(
"Using known_hosts file: {:?} (accept-new/TOFU mode)",
known_hosts_path
);
ServerCheckMethod::AcceptNewKnownHostsFile(
known_hosts_path.to_string_lossy().into_owned(),
)
}
None => {
tracing::warn!(
"Could not determine known_hosts path; host keys will be pinned only for this bssh process"
);
eprintln!(
"Warning: could not determine the known_hosts path; host keys will be pinned only for this bssh process"
);
ServerCheckMethod::AcceptNewInMemory
}
},
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum StrictHostKeyChecking {
Yes,
No,
#[default]
AcceptNew,
}
impl StrictHostKeyChecking {
pub fn to_bool(&self) -> bool {
matches!(self, Self::Yes)
}
}
impl FromStr for StrictHostKeyChecking {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s.to_lowercase().as_str() {
"yes" | "true" => Self::Yes,
"no" | "false" => Self::No,
"accept-new" | "tofu" => Self::AcceptNew,
_ => Self::AcceptNew, })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_strict_host_key_checking_from_str() {
assert_eq!(
StrictHostKeyChecking::from_str("yes").unwrap(),
StrictHostKeyChecking::Yes
);
assert_eq!(
StrictHostKeyChecking::from_str("true").unwrap(),
StrictHostKeyChecking::Yes
);
assert_eq!(
StrictHostKeyChecking::from_str("no").unwrap(),
StrictHostKeyChecking::No
);
assert_eq!(
StrictHostKeyChecking::from_str("false").unwrap(),
StrictHostKeyChecking::No
);
assert_eq!(
StrictHostKeyChecking::from_str("accept-new").unwrap(),
StrictHostKeyChecking::AcceptNew
);
assert_eq!(
StrictHostKeyChecking::from_str("tofu").unwrap(),
StrictHostKeyChecking::AcceptNew
);
assert_eq!(
StrictHostKeyChecking::from_str("invalid").unwrap(),
StrictHostKeyChecking::AcceptNew
);
}
#[test]
fn test_strict_host_key_checking_to_bool() {
assert!(StrictHostKeyChecking::Yes.to_bool());
assert!(!StrictHostKeyChecking::No.to_bool());
assert!(!StrictHostKeyChecking::AcceptNew.to_bool());
}
#[test]
fn test_strict_host_key_checking_default() {
assert_eq!(
StrictHostKeyChecking::default(),
StrictHostKeyChecking::AcceptNew
);
}
#[test]
fn test_get_default_known_hosts_path() {
let path = get_default_known_hosts_path();
assert!(path.is_some());
if let Some(p) = path {
assert!(p.to_str().unwrap().contains(".ssh/known_hosts"));
}
}
#[test]
fn test_get_check_method() {
let method = get_check_method(StrictHostKeyChecking::No);
assert!(matches!(method, ServerCheckMethod::NoCheck));
let method = get_check_method(StrictHostKeyChecking::AcceptNew);
match method {
ServerCheckMethod::AcceptNewKnownHostsFile(path) => {
assert!(
path.ends_with("known_hosts"),
"expected the default known_hosts path, got: {path}"
);
}
other => panic!("accept-new must map to AcceptNewKnownHostsFile, got {other:?}"),
}
let method = get_check_method(StrictHostKeyChecking::Yes);
match method {
ServerCheckMethod::KnownHostsFile(path) => {
assert!(
path.ends_with("known_hosts"),
"expected the default known_hosts path, got: {path}"
);
}
ServerCheckMethod::DefaultKnownHostsFile => {}
other => panic!("strict mode must keep verification enabled, got {other:?}"),
}
}
}