2022-11-25 17:19:32 -05:00
|
|
|
use std::ffi::OsStr;
|
|
|
|
|
use std::path::Path;
|
2022-11-24 14:15:43 -05:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
|
|
use color_eyre::eyre::Context;
|
|
|
|
|
use color_eyre::eyre::Result;
|
2023-03-13 19:23:37 +00:00
|
|
|
use serde::Deserialize;
|
|
|
|
|
use strum::AsRefStr;
|
2022-11-24 14:15:43 -05:00
|
|
|
|
|
|
|
|
use crate::command::CommandExt;
|
|
|
|
|
use crate::execution_context::ExecutionContext;
|
2022-11-25 17:19:32 -05:00
|
|
|
use crate::executor::Executor;
|
2022-11-24 14:15:43 -05:00
|
|
|
use crate::terminal::print_separator;
|
|
|
|
|
use crate::utils::which;
|
|
|
|
|
|
2022-11-25 17:19:32 -05:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct Sudo {
|
|
|
|
|
/// The path to the `sudo` binary.
|
|
|
|
|
path: PathBuf,
|
|
|
|
|
/// The type of program being used as `sudo`.
|
|
|
|
|
kind: SudoKind,
|
2022-11-24 14:15:43 -05:00
|
|
|
}
|
|
|
|
|
|
2022-11-25 17:19:32 -05:00
|
|
|
impl Sudo {
|
2025-03-06 01:51:40 +01:00
|
|
|
/// Get the `sudo` binary or the `gsudo` binary in the case of `gsudo`
|
|
|
|
|
/// masquerading as the `sudo` binary.
|
|
|
|
|
fn determine_sudo_variant(sudo_p: PathBuf) -> (PathBuf, SudoKind) {
|
|
|
|
|
match which("gsudo") {
|
|
|
|
|
Some(gsudo_p) => {
|
|
|
|
|
match std::fs::canonicalize(&gsudo_p).unwrap() == std::fs::canonicalize(&sudo_p).unwrap() {
|
|
|
|
|
true => (gsudo_p, SudoKind::Gsudo),
|
|
|
|
|
false => (sudo_p, SudoKind::Sudo),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None => (sudo_p, SudoKind::Sudo),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-25 17:19:32 -05:00
|
|
|
/// Get the `sudo` binary for this platform.
|
|
|
|
|
pub fn detect() -> Option<Self> {
|
|
|
|
|
which("doas")
|
|
|
|
|
.map(|p| (p, SudoKind::Doas))
|
2025-03-06 01:51:40 +01:00
|
|
|
.or_else(|| which("sudo").map(Self::determine_sudo_variant))
|
2022-11-25 17:19:32 -05:00
|
|
|
.or_else(|| which("gsudo").map(|p| (p, SudoKind::Gsudo)))
|
|
|
|
|
.or_else(|| which("pkexec").map(|p| (p, SudoKind::Pkexec)))
|
2025-03-12 02:07:37 +01:00
|
|
|
.or_else(|| which("run0").map(|p| (p, SudoKind::Run0)))
|
2023-08-22 09:14:29 +08:00
|
|
|
.or_else(|| which("please").map(|p| (p, SudoKind::Please)))
|
2022-11-25 17:19:32 -05:00
|
|
|
.map(|(path, kind)| Self { path, kind })
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-13 19:23:37 +00:00
|
|
|
/// Create Sudo from SudoKind, if found in the system
|
|
|
|
|
pub fn new(kind: SudoKind) -> Option<Self> {
|
|
|
|
|
which(kind.as_ref()).map(|path| Self { path, kind })
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-25 17:19:32 -05:00
|
|
|
/// Elevate permissions with `sudo`.
|
|
|
|
|
///
|
|
|
|
|
/// This helps prevent blocking `sudo` prompts from stopping the run in the middle of a
|
|
|
|
|
/// step.
|
|
|
|
|
///
|
|
|
|
|
/// See: https://github.com/topgrade-rs/topgrade/issues/205
|
|
|
|
|
pub fn elevate(&self, ctx: &ExecutionContext) -> Result<()> {
|
2022-11-24 14:15:43 -05:00
|
|
|
print_separator("Sudo");
|
2025-06-24 15:20:29 +02:00
|
|
|
let mut cmd = ctx.execute(&self.path);
|
2022-11-25 17:19:32 -05:00
|
|
|
match self.kind {
|
|
|
|
|
SudoKind::Doas => {
|
|
|
|
|
// `doas` 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.
|
|
|
|
|
// See: https://man.openbsd.org/doas
|
|
|
|
|
cmd.arg("echo");
|
|
|
|
|
}
|
|
|
|
|
SudoKind::Sudo => {
|
|
|
|
|
// From `man sudo` on macOS:
|
|
|
|
|
// -v, --validate
|
|
|
|
|
// Update the user's cached credentials, authenticating the user
|
|
|
|
|
// if necessary. For the sudoers plugin, this extends the sudo
|
|
|
|
|
// timeout for another 5 minutes by default, but does not run a
|
|
|
|
|
// command. Not all security policies support cached credentials.
|
|
|
|
|
cmd.arg("-v");
|
|
|
|
|
}
|
|
|
|
|
SudoKind::Gsudo => {
|
2025-03-06 01:51:40 +01:00
|
|
|
// `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.
|
2022-11-25 17:19:32 -05:00
|
|
|
// See: https://gerardog.github.io/gsudo/docs/usage
|
2025-03-06 01:51:40 +01:00
|
|
|
cmd.arg("echo");
|
2022-11-25 17:19:32 -05:00
|
|
|
}
|
|
|
|
|
SudoKind::Pkexec => {
|
|
|
|
|
// I don't think this does anything; `pkexec` usually asks for
|
|
|
|
|
// authentication every time, although it can be configured
|
|
|
|
|
// differently.
|
|
|
|
|
//
|
|
|
|
|
// See the note for `doas` above.
|
|
|
|
|
//
|
|
|
|
|
// See: https://linux.die.net/man/1/pkexec
|
|
|
|
|
cmd.arg("echo");
|
|
|
|
|
}
|
2025-03-12 02:07:37 +01:00
|
|
|
SudoKind::Run0 => {
|
|
|
|
|
// `run0` uses polkit for authentication
|
|
|
|
|
// and thus has the same issues as `pkexec`.
|
|
|
|
|
//
|
|
|
|
|
// See: https://www.freedesktop.org/software/systemd/man/devel/run0.html
|
|
|
|
|
cmd.arg("echo");
|
|
|
|
|
}
|
2023-08-22 09:14:29 +08:00
|
|
|
SudoKind::Please => {
|
|
|
|
|
// From `man please`
|
|
|
|
|
// -w, --warm
|
|
|
|
|
// Warm the access token and exit.
|
|
|
|
|
cmd.arg("-w");
|
|
|
|
|
}
|
2022-11-25 17:19:32 -05:00
|
|
|
}
|
|
|
|
|
cmd.status_checked().wrap_err("Failed to elevate permissions")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Execute a command with `sudo`.
|
|
|
|
|
pub fn execute_elevated(&self, ctx: &ExecutionContext, command: &Path, interactive: bool) -> Executor {
|
2025-06-24 15:20:29 +02:00
|
|
|
let mut cmd = ctx.execute(&self.path);
|
2022-11-25 17:19:32 -05:00
|
|
|
|
|
|
|
|
if let SudoKind::Sudo = self.kind {
|
|
|
|
|
cmd.arg("--preserve-env=DIFFPROG");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if interactive {
|
|
|
|
|
cmd.arg("-i");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd.arg(command);
|
|
|
|
|
|
|
|
|
|
cmd
|
2022-11-24 14:15:43 -05:00
|
|
|
}
|
2022-11-25 17:19:32 -05:00
|
|
|
}
|
2022-11-24 14:15:43 -05:00
|
|
|
|
2023-03-13 19:23:37 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Deserialize, AsRefStr)]
|
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
|
#[strum(serialize_all = "lowercase")]
|
|
|
|
|
pub enum SudoKind {
|
2022-11-25 17:19:32 -05:00
|
|
|
Doas,
|
|
|
|
|
Sudo,
|
|
|
|
|
Gsudo,
|
|
|
|
|
Pkexec,
|
2025-03-12 02:07:37 +01:00
|
|
|
Run0,
|
2023-08-22 09:14:29 +08:00
|
|
|
Please,
|
2022-11-25 17:19:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AsRef<OsStr> for Sudo {
|
|
|
|
|
fn as_ref(&self) -> &OsStr {
|
|
|
|
|
self.path.as_ref()
|
|
|
|
|
}
|
2022-11-24 14:15:43 -05:00
|
|
|
}
|