1use std::os::unix::fs::PermissionsExt;
15use std::path::PathBuf;
16use std::process::Command;
17
18use anyhow::{Context, Result};
19
20struct TempShim {
22 path: PathBuf,
23}
24
25impl Drop for TempShim {
26 fn drop(&mut self) {
27 let _ = std::fs::remove_file(&self.path);
28 }
29}
30
31pub struct Sudo {
33 prefix: Vec<String>,
35 _shim: Option<TempShim>,
36}
37
38fn shell_quote(s: &str) -> String {
40 format!("'{}'", s.replace('\'', "'\\''"))
41}
42
43impl Sudo {
44 pub fn resolve() -> Result<Sudo> {
46 if unsafe { libc::geteuid() } == 0 {
47 return Ok(Sudo {
48 prefix: Vec::new(),
49 _shim: None,
50 });
51 }
52 if std::env::var_os("SUDO_ASKPASS").is_some() {
53 return Ok(Sudo {
54 prefix: vec!["sudo".into(), "-A".into()],
55 _shim: None,
56 });
57 }
58 if let Some(pass_file) = std::env::var_os("SCX_SUDO_PASSWORD_FILE") {
59 let pf = PathBuf::from(&pass_file);
60 if !pf.is_file() {
61 anyhow::bail!("SCX_SUDO_PASSWORD_FILE not found: {}", pf.display());
62 }
63 let pf_abs = std::fs::canonicalize(&pf)
64 .with_context(|| format!("canonicalize {}", pf.display()))?;
65 let shim = std::env::temp_dir().join(format!("scx-askpass-{}.sh", std::process::id()));
67 std::fs::write(
68 &shim,
69 format!(
70 "#!/bin/sh\nexec cat {}\n",
71 shell_quote(&pf_abs.to_string_lossy())
72 ),
73 )
74 .with_context(|| format!("write askpass shim {}", shim.display()))?;
75 std::fs::set_permissions(&shim, std::fs::Permissions::from_mode(0o700))
76 .with_context(|| format!("chmod askpass shim {}", shim.display()))?;
77 std::env::set_var("SUDO_ASKPASS", &shim);
78 return Ok(Sudo {
79 prefix: vec!["sudo".into(), "-A".into()],
80 _shim: Some(TempShim { path: shim }),
81 });
82 }
83 Ok(Sudo {
84 prefix: vec!["sudo".into(), "-n".into()],
85 _shim: None,
86 })
87 }
88
89 pub fn command(&self, program: &str, args: &[String]) -> Command {
91 if self.prefix.is_empty() {
92 let mut c = Command::new(program);
93 c.args(args);
94 c
95 } else {
96 let mut c = Command::new(&self.prefix[0]);
97 c.args(&self.prefix[1..]);
98 c.arg(program);
99 c.args(args);
100 c
101 }
102 }
103
104 pub fn authenticate(&self) -> Result<()> {
107 if self.prefix.is_empty() {
108 return Ok(());
109 }
110 let out = Command::new(&self.prefix[0])
111 .args(&self.prefix[1..])
112 .arg("-v")
113 .output()
114 .context("spawn sudo -v")?;
115 if out.status.success() {
116 Ok(())
117 } else {
118 let err = String::from_utf8_lossy(&out.stderr);
119 anyhow::bail!("{}", err.trim());
120 }
121 }
122}