put: expand ~ in ssh_config IdentityFile paths
IdentityFile values were stored verbatim, so the near-universal 'IdentityFile ~/.ssh/key' spelling never matched an existing file and the key was silently skipped during authentication. Expand a leading ~ (only that form; ~user and embedded tildes stay verbatim) against the user's home directory when parsing.
This commit is contained in:
+62
-5
@@ -120,7 +120,7 @@ fn apply_config_file(config: &mut SshConfig, content: &str, host: &str) {
|
|||||||
config.port = Some(port);
|
config.port = Some(port);
|
||||||
}
|
}
|
||||||
} else if keyword.eq_ignore_ascii_case("identityfile") {
|
} else if keyword.eq_ignore_ascii_case("identityfile") {
|
||||||
let path = PathBuf::from(rest);
|
let path = expand_tilde(rest);
|
||||||
if !config.identity_files.contains(&path) {
|
if !config.identity_files.contains(&path) {
|
||||||
config.identity_files.push(path);
|
config.identity_files.push(path);
|
||||||
}
|
}
|
||||||
@@ -128,6 +128,37 @@ fn apply_config_file(config: &mut SshConfig, content: &str, host: &str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The current user's home directory: the `directories` crate first (which
|
||||||
|
/// consults `$HOME` on Unix), with a direct `$HOME` fallback.
|
||||||
|
fn current_home() -> Option<PathBuf> {
|
||||||
|
directories::UserDirs::new()
|
||||||
|
.map(|dirs| dirs.home_dir().to_path_buf())
|
||||||
|
.or_else(|| std::env::var_os("HOME").map(PathBuf::from))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expand a leading `~` or `~/` in `path` against `home`: the near-universal
|
||||||
|
/// `IdentityFile ~/.ssh/key` spelling. Only a leading tilde is handled —
|
||||||
|
/// `~user/...` (another user's home) and tildes appearing anywhere else are
|
||||||
|
/// kept verbatim — and without a known home directory the path is returned
|
||||||
|
/// unchanged.
|
||||||
|
fn expand_tilde_with(path: &str, home: Option<&Path>) -> PathBuf {
|
||||||
|
let Some(home) = home else {
|
||||||
|
return PathBuf::from(path);
|
||||||
|
};
|
||||||
|
if path == "~" {
|
||||||
|
home.to_path_buf()
|
||||||
|
} else if let Some(rest) = path.strip_prefix("~/") {
|
||||||
|
home.join(rest)
|
||||||
|
} else {
|
||||||
|
PathBuf::from(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`expand_tilde_with`] against the current user's home directory
|
||||||
|
fn expand_tilde(path: &str) -> PathBuf {
|
||||||
|
expand_tilde_with(path, current_home().as_deref())
|
||||||
|
}
|
||||||
|
|
||||||
/// One `Host` block pattern against a host name: exact match, `*` (any
|
/// One `Host` block pattern against a host name: exact match, `*` (any
|
||||||
/// run), `?` (one character) and `!pattern` negation (a matching negated
|
/// run), `?` (one character) and `!pattern` negation (a matching negated
|
||||||
/// pattern excludes the host from the block).
|
/// pattern excludes the host from the block).
|
||||||
@@ -557,17 +588,43 @@ Host *
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(config.user.as_deref(), Some("myuser"));
|
assert_eq!(config.user.as_deref(), Some("myuser"));
|
||||||
|
// A leading tilde is expanded to the home directory, built from the
|
||||||
|
// same lookup the helper uses so no specific username is assumed
|
||||||
|
let home = current_home().expect("tests require a home directory");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config.identity_files,
|
config.identity_files,
|
||||||
vec![
|
vec![home.join(".ssh/lp_key"), home.join(".ssh/other_key")]
|
||||||
PathBuf::from("~/.ssh/lp_key"),
|
|
||||||
PathBuf::from("~/.ssh/other_key")
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
assert_eq!(config.host_name, None);
|
assert_eq!(config.host_name, None);
|
||||||
assert_eq!(config.port, None);
|
assert_eq!(config.port, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Only a leading `~`/`~/` expands to the home directory; `~user/...`
|
||||||
|
/// and non-tilde paths are kept verbatim, and nothing is expanded
|
||||||
|
/// without a known home directory
|
||||||
|
#[test]
|
||||||
|
fn expand_tilde_handles_leading_tilde_only() {
|
||||||
|
let home = Some(Path::new("/home/testuser"));
|
||||||
|
assert_eq!(
|
||||||
|
expand_tilde_with("~/x", home),
|
||||||
|
PathBuf::from("/home/testuser/x")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
expand_tilde_with("~", home),
|
||||||
|
PathBuf::from("/home/testuser")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
expand_tilde_with("~other/x", home),
|
||||||
|
PathBuf::from("~other/x")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
expand_tilde_with("relative", home),
|
||||||
|
PathBuf::from("relative")
|
||||||
|
);
|
||||||
|
assert_eq!(expand_tilde_with("/abs", home), PathBuf::from("/abs"));
|
||||||
|
assert_eq!(expand_tilde_with("~/x", None), PathBuf::from("~/x"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn config_wildcard_and_question_marks_match() {
|
fn config_wildcard_and_question_marks_match() {
|
||||||
let config = apply_config_file_all(
|
let config = apply_config_file_all(
|
||||||
|
|||||||
Reference in New Issue
Block a user