fix(sudo): set sudo flags depending on kind
This commit is contained in:
160
src/sudo.rs
160
src/sudo.rs
@@ -1,13 +1,14 @@
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
use serde::Deserialize;
|
||||
use strum::AsRefStr;
|
||||
use strum::Display;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::error::UnsupportedSudo;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::executor::Executor;
|
||||
use crate::terminal::print_separator;
|
||||
@@ -21,6 +22,21 @@ pub struct Sudo {
|
||||
kind: SudoKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
/// Generic sudo options, translated into flags to pass to `sudo`. Depending on the sudo kind, OS
|
||||
/// and system config, some options might be specified by default or unsupported.
|
||||
pub struct SudoExecuteOpts<'a> {
|
||||
/// Run the command "interactively", i.e. inside a login shell.
|
||||
pub interactive: bool,
|
||||
/// Preserve environment variables across the sudo call. If an empty list is given, preserves
|
||||
/// all existing environment variables.
|
||||
pub preserve_env: Option<&'a [&'a str]>,
|
||||
/// Set the HOME environment variable to the target user's home directory.
|
||||
pub set_home: bool,
|
||||
/// Run the command as a user other than the root user.
|
||||
pub user: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl Sudo {
|
||||
/// Get the `sudo` binary or the `gsudo` binary in the case of `gsudo`
|
||||
/// masquerading as the `sudo` binary.
|
||||
@@ -70,7 +86,7 @@ impl Sudo {
|
||||
// See: https://man.openbsd.org/doas
|
||||
cmd.arg("echo");
|
||||
}
|
||||
SudoKind::Sudo => {
|
||||
SudoKind::Sudo if cfg!(not(target_os = "windows")) => {
|
||||
// From `man sudo` on macOS:
|
||||
// -v, --validate
|
||||
// Update the user's cached credentials, authenticating the user
|
||||
@@ -79,12 +95,19 @@ impl Sudo {
|
||||
// command. Not all security policies support cached credentials.
|
||||
cmd.arg("-v");
|
||||
}
|
||||
SudoKind::Sudo => {
|
||||
// Windows `sudo` doesn't cache credentials, so we just execute a
|
||||
// dummy command - the easiest on Windows is `rem` in cmd.
|
||||
// See: https://learn.microsoft.com/en-us/windows/advanced-settings/sudo/
|
||||
cmd.args(["cmd.exe", "/c", "rem"]);
|
||||
}
|
||||
SudoKind::Gsudo => {
|
||||
// `gsudo` doesn't have anything like `sudo -v` to cache credentials,
|
||||
// so we just execute a dummy `echo` command so we have something
|
||||
// unobtrusive to run.
|
||||
// so we just execute a dummy command - the easiest on Windows is
|
||||
// `rem` in cmd. `-d` tells it to run the command directly, without
|
||||
// going through a shell (which could be powershell) first.
|
||||
// See: https://gerardog.github.io/gsudo/docs/usage
|
||||
cmd.arg("echo");
|
||||
cmd.args(["-d", "cmd.exe", "/c", "rem"]);
|
||||
}
|
||||
SudoKind::Pkexec => {
|
||||
// I don't think this does anything; `pkexec` usually asks for
|
||||
@@ -114,24 +137,137 @@ impl Sudo {
|
||||
}
|
||||
|
||||
/// Execute a command with `sudo`.
|
||||
pub fn execute_elevated(&self, ctx: &ExecutionContext, command: &Path, interactive: bool) -> Executor {
|
||||
pub fn execute<S: AsRef<OsStr>>(&self, ctx: &ExecutionContext, command: S) -> Result<Executor> {
|
||||
self.execute_opts(ctx, command, SudoExecuteOpts::default())
|
||||
}
|
||||
|
||||
/// Execute a command with `sudo`, with custom options.
|
||||
pub fn execute_opts<S: AsRef<OsStr>>(
|
||||
&self,
|
||||
ctx: &ExecutionContext,
|
||||
command: S,
|
||||
opts: SudoExecuteOpts,
|
||||
) -> Result<Executor> {
|
||||
let mut cmd = ctx.execute(&self.path);
|
||||
|
||||
if let SudoKind::Sudo = self.kind {
|
||||
cmd.arg("--preserve-env=DIFFPROG");
|
||||
if opts.interactive {
|
||||
match self.kind {
|
||||
SudoKind::Sudo if cfg!(not(target_os = "windows")) => {
|
||||
cmd.arg("-i");
|
||||
}
|
||||
SudoKind::Gsudo => {
|
||||
// By default, gsudo runs all commands inside a shell, so it's effectively
|
||||
// always "interactive". If interactive is *not* specified, we add `-d`
|
||||
// to run outside of a shell - see below.
|
||||
}
|
||||
SudoKind::Doas | SudoKind::Sudo | SudoKind::Pkexec | SudoKind::Run0 | SudoKind::Please => {
|
||||
return Err(UnsupportedSudo {
|
||||
sudo_kind: self.kind,
|
||||
option: "interactive",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
} else if let SudoKind::Gsudo = self.kind {
|
||||
// The `-d` (direct) flag disables shell detection, running the command directly
|
||||
// rather than through the current shell, making it "non-interactive".
|
||||
// Additionally, if the current shell is pwsh >= 7.3.0, then not including this
|
||||
// gives errors if the command to run has spaces in it: see
|
||||
// https://github.com/gerardog/gsudo/issues/297
|
||||
cmd.arg("-d");
|
||||
}
|
||||
|
||||
if interactive {
|
||||
cmd.arg("-i");
|
||||
if let Some(preserve_env) = opts.preserve_env {
|
||||
if preserve_env.is_empty() {
|
||||
match self.kind {
|
||||
SudoKind::Sudo => {
|
||||
cmd.arg("-E");
|
||||
}
|
||||
SudoKind::Gsudo => {
|
||||
cmd.arg("--copyEV");
|
||||
}
|
||||
SudoKind::Doas | SudoKind::Pkexec | SudoKind::Run0 | SudoKind::Please => {
|
||||
return Err(UnsupportedSudo {
|
||||
sudo_kind: self.kind,
|
||||
option: "preserve_env",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match self.kind {
|
||||
SudoKind::Sudo if cfg!(not(target_os = "windows")) => {
|
||||
cmd.arg(format!("--preserve_env={}", preserve_env.join(",")));
|
||||
}
|
||||
SudoKind::Run0 => {
|
||||
for env in preserve_env {
|
||||
cmd.arg(format!("--setenv={}", env));
|
||||
}
|
||||
}
|
||||
SudoKind::Please => {
|
||||
cmd.arg("-a");
|
||||
cmd.arg(preserve_env.join(","));
|
||||
}
|
||||
SudoKind::Doas | SudoKind::Sudo | SudoKind::Gsudo | SudoKind::Pkexec => {
|
||||
return Err(UnsupportedSudo {
|
||||
sudo_kind: self.kind,
|
||||
option: "preserve_env list",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.set_home {
|
||||
match self.kind {
|
||||
SudoKind::Sudo if cfg!(not(target_os = "windows")) => {
|
||||
cmd.arg("-H");
|
||||
}
|
||||
SudoKind::Doas
|
||||
| SudoKind::Sudo
|
||||
| SudoKind::Gsudo
|
||||
| SudoKind::Pkexec
|
||||
| SudoKind::Run0
|
||||
| SudoKind::Please => {
|
||||
return Err(UnsupportedSudo {
|
||||
sudo_kind: self.kind,
|
||||
option: "set_home",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(user) = opts.user {
|
||||
match self.kind {
|
||||
SudoKind::Sudo if cfg!(not(target_os = "windows")) => {
|
||||
cmd.args(["-u", user]);
|
||||
}
|
||||
SudoKind::Doas | SudoKind::Gsudo | SudoKind::Run0 | SudoKind::Please => {
|
||||
cmd.args(["-u", user]);
|
||||
}
|
||||
SudoKind::Pkexec => {
|
||||
cmd.args(["--user", user]);
|
||||
}
|
||||
SudoKind::Sudo => {
|
||||
// Windows sudo is the only one that doesn't have a `-u` flag
|
||||
return Err(UnsupportedSudo {
|
||||
sudo_kind: self.kind,
|
||||
option: "user",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd.arg(command);
|
||||
|
||||
cmd
|
||||
Ok(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, AsRefStr)]
|
||||
#[derive(Clone, Copy, Debug, Display, Deserialize, AsRefStr)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum SudoKind {
|
||||
|
||||
Reference in New Issue
Block a user