Skip to main content

horto_os_ui_shared/remote/
askpass.rs

1//! Resolve an OpenSSH askpass binary for Desktop / non-TTY SSH login.
2
3use crate::error::{HortoError, Result};
4use std::path::PathBuf;
5
6/// Resolve `$SSH_ASKPASS`, else a common askpass name on `PATH`.
7///
8/// Used only to populate OpenSSH's environment for SSH *login* when
9/// [`super::ssh::SshEnv::force_askpass`] is set. Sudo reboot does not use this.
10///
11/// # Errors
12///
13/// Returns [`HortoError`] when neither the env var nor a known binary is found.
14pub fn resolve_askpass() -> Result<PathBuf> {
15    if let Ok(ask) = std::env::var("SSH_ASKPASS") {
16        let ask = ask.trim();
17        if !ask.is_empty() {
18            return Ok(PathBuf::from(ask));
19        }
20    }
21    for name in [
22        "ssh-askpass",
23        "x11-ssh-askpass",
24        "gnome-ssh-askpass",
25        "ksshaskpass",
26        "lxqt-openssh-askpass",
27    ] {
28        if let Ok(path) = which::which(name) {
29            return Ok(path);
30        }
31    }
32    Err(HortoError::msg(
33        "No system password helper found for SSH login",
34    ))
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40    use crate::remote::ENV_LOCK;
41    use std::fs;
42    use std::os::unix::fs::PermissionsExt;
43
44    #[test]
45    fn resolve_askpass_skips_blank_env() {
46        let _g = ENV_LOCK.lock().expect("env lock");
47        std::env::set_var("SSH_ASKPASS", "   ");
48        let old_path = std::env::var_os("PATH");
49        std::env::set_var("PATH", "");
50        let err = resolve_askpass().unwrap_err().to_string();
51        match old_path {
52            Some(p) => std::env::set_var("PATH", p),
53            None => std::env::remove_var("PATH"),
54        }
55        std::env::remove_var("SSH_ASKPASS");
56        assert!(err.contains("No system password helper found"));
57    }
58
59    #[test]
60    fn resolve_askpass_errors_when_unset_and_no_path() {
61        let _g = ENV_LOCK.lock().expect("env lock");
62        std::env::remove_var("SSH_ASKPASS");
63        let old_path = std::env::var_os("PATH");
64        std::env::set_var("PATH", "");
65        let err = resolve_askpass().unwrap_err().to_string();
66        match old_path {
67            Some(p) => std::env::set_var("PATH", p),
68            None => std::env::remove_var("PATH"),
69        }
70        assert!(err.contains("No system password helper found"));
71    }
72
73    #[test]
74    fn resolve_askpass_prefers_env() {
75        let _g = ENV_LOCK.lock().expect("env lock");
76        let dir = tempfile::tempdir().expect("tmpdir");
77        let script = dir.path().join("askpass.sh");
78        fs::write(&script, "#!/bin/sh\n").expect("write");
79        let mut perms = fs::metadata(&script).expect("meta").permissions();
80        perms.set_mode(0o755);
81        fs::set_permissions(&script, perms).expect("chmod");
82        std::env::set_var("SSH_ASKPASS", &script);
83        let got = resolve_askpass().expect("resolve");
84        std::env::remove_var("SSH_ASKPASS");
85        assert_eq!(got, script);
86    }
87
88    #[test]
89    fn resolve_askpass_finds_binary_on_path() {
90        let _g = ENV_LOCK.lock().expect("env lock");
91        std::env::remove_var("SSH_ASKPASS");
92        let dir = tempfile::tempdir().expect("tmpdir");
93        let bin = dir.path().join("ssh-askpass");
94        fs::write(&bin, "#!/bin/sh\n").expect("write");
95        let mut perms = fs::metadata(&bin).expect("meta").permissions();
96        perms.set_mode(0o755);
97        fs::set_permissions(&bin, perms).expect("chmod");
98        let old_path = std::env::var_os("PATH");
99        std::env::set_var("PATH", dir.path());
100        let got = resolve_askpass().expect("resolve");
101        match old_path {
102            Some(p) => std::env::set_var("PATH", p),
103            None => std::env::remove_var("PATH"),
104        }
105        assert_eq!(got, bin);
106    }
107}