fix(sudo): set sudo flags depending on kind
This commit is contained in:
@@ -1136,6 +1136,14 @@ _version: 2
|
|||||||
zh_CN: "找不到权限管理程序(sudo 等),跳过"
|
zh_CN: "找不到权限管理程序(sudo 等),跳过"
|
||||||
zh_TW: "找不到權限管理程式(sudo 等),略過"
|
zh_TW: "找不到權限管理程式(sudo 等),略過"
|
||||||
de: "Benötigt sudo oder Äquivalent, aber nicht gefunden, überspringe"
|
de: "Benötigt sudo oder Äquivalent, aber nicht gefunden, überspringe"
|
||||||
|
"{sudo_kind} does not support the {option} option":
|
||||||
|
en: "%{sudo_kind} does not support the %{option} option"
|
||||||
|
lt: "%{sudo_kind} nepalaiko parinkties %{option}"
|
||||||
|
es: "%{sudo_kind} no admite la opción %{option}"
|
||||||
|
fr: "%{sudo_kind} ne prend pas en charge l’option %{option}"
|
||||||
|
zh_CN: "%{sudo_kind} 不支持 %{option} 选项"
|
||||||
|
zh_TW: "%{sudo_kind} 不支援 %{option} 選項"
|
||||||
|
de: "%{sudo_kind} unterstützt die Option %{option} nicht"
|
||||||
"sudo as user '{user}'":
|
"sudo as user '{user}'":
|
||||||
en: "sudo as user '%{user}'"
|
en: "sudo as user '%{user}'"
|
||||||
lt: "sudo kaip vartotojas '%{user}'"
|
lt: "sudo kaip vartotojas '%{user}'"
|
||||||
|
|||||||
22
src/error.rs
22
src/error.rs
@@ -3,6 +3,8 @@ use std::{fmt::Display, process::ExitStatus};
|
|||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::sudo::SudoKind;
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq, Eq)]
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
pub enum TopgradeError {
|
pub enum TopgradeError {
|
||||||
ProcessFailed(String, ExitStatus),
|
ProcessFailed(String, ExitStatus),
|
||||||
@@ -68,6 +70,26 @@ impl Display for StepFailed {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub struct UnsupportedSudo<'a> {
|
||||||
|
pub sudo_kind: SudoKind,
|
||||||
|
pub option: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for UnsupportedSudo<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
t!(
|
||||||
|
"{sudo_kind} does not support the {option} option",
|
||||||
|
sudo_kind = self.sudo_kind,
|
||||||
|
option = self.option
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub struct DryRun();
|
pub struct DryRun();
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::executor::DryCommand;
|
|||||||
use crate::powershell::Powershell;
|
use crate::powershell::Powershell;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use crate::steps::linux::Distribution;
|
use crate::steps::linux::Distribution;
|
||||||
use crate::sudo::Sudo;
|
use crate::sudo::{Sudo, SudoExecuteOpts};
|
||||||
use crate::utils::{get_require_sudo_string, require_option};
|
use crate::utils::{get_require_sudo_string, require_option};
|
||||||
use crate::{config::Config, executor::Executor};
|
use crate::{config::Config, executor::Executor};
|
||||||
|
|
||||||
@@ -91,7 +91,14 @@ impl<'a> ExecutionContext<'a> {
|
|||||||
/// using sudo to elevate privileges.
|
/// using sudo to elevate privileges.
|
||||||
pub fn execute_elevated(&self, command: &Path, interactive: bool) -> Result<Executor> {
|
pub fn execute_elevated(&self, command: &Path, interactive: bool) -> Result<Executor> {
|
||||||
let sudo = require_option(self.sudo.as_ref(), get_require_sudo_string())?;
|
let sudo = require_option(self.sudo.as_ref(), get_require_sudo_string())?;
|
||||||
Ok(sudo.execute_elevated(self, command, interactive))
|
sudo.execute_opts(
|
||||||
|
self,
|
||||||
|
command,
|
||||||
|
SudoExecuteOpts {
|
||||||
|
interactive,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_type(&self) -> RunType {
|
pub fn run_type(&self) -> RunType {
|
||||||
|
|||||||
160
src/sudo.rs
160
src/sudo.rs
@@ -1,13 +1,14 @@
|
|||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::path::Path;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use color_eyre::eyre::Context;
|
use color_eyre::eyre::Context;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use strum::AsRefStr;
|
use strum::AsRefStr;
|
||||||
|
use strum::Display;
|
||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
|
use crate::error::UnsupportedSudo;
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
use crate::executor::Executor;
|
use crate::executor::Executor;
|
||||||
use crate::terminal::print_separator;
|
use crate::terminal::print_separator;
|
||||||
@@ -21,6 +22,21 @@ pub struct Sudo {
|
|||||||
kind: SudoKind,
|
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 {
|
impl Sudo {
|
||||||
/// Get the `sudo` binary or the `gsudo` binary in the case of `gsudo`
|
/// Get the `sudo` binary or the `gsudo` binary in the case of `gsudo`
|
||||||
/// masquerading as the `sudo` binary.
|
/// masquerading as the `sudo` binary.
|
||||||
@@ -70,7 +86,7 @@ impl Sudo {
|
|||||||
// See: https://man.openbsd.org/doas
|
// See: https://man.openbsd.org/doas
|
||||||
cmd.arg("echo");
|
cmd.arg("echo");
|
||||||
}
|
}
|
||||||
SudoKind::Sudo => {
|
SudoKind::Sudo if cfg!(not(target_os = "windows")) => {
|
||||||
// From `man sudo` on macOS:
|
// From `man sudo` on macOS:
|
||||||
// -v, --validate
|
// -v, --validate
|
||||||
// Update the user's cached credentials, authenticating the user
|
// Update the user's cached credentials, authenticating the user
|
||||||
@@ -79,12 +95,19 @@ impl Sudo {
|
|||||||
// command. Not all security policies support cached credentials.
|
// command. Not all security policies support cached credentials.
|
||||||
cmd.arg("-v");
|
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 => {
|
SudoKind::Gsudo => {
|
||||||
// `gsudo` doesn't have anything like `sudo -v` to cache credentials,
|
// `gsudo` doesn't have anything like `sudo -v` to cache credentials,
|
||||||
// so we just execute a dummy `echo` command so we have something
|
// so we just execute a dummy command - the easiest on Windows is
|
||||||
// unobtrusive to run.
|
// `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
|
// See: https://gerardog.github.io/gsudo/docs/usage
|
||||||
cmd.arg("echo");
|
cmd.args(["-d", "cmd.exe", "/c", "rem"]);
|
||||||
}
|
}
|
||||||
SudoKind::Pkexec => {
|
SudoKind::Pkexec => {
|
||||||
// I don't think this does anything; `pkexec` usually asks for
|
// I don't think this does anything; `pkexec` usually asks for
|
||||||
@@ -114,24 +137,137 @@ impl Sudo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a command with `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);
|
let mut cmd = ctx.execute(&self.path);
|
||||||
|
|
||||||
if let SudoKind::Sudo = self.kind {
|
if opts.interactive {
|
||||||
cmd.arg("--preserve-env=DIFFPROG");
|
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 {
|
if let Some(preserve_env) = opts.preserve_env {
|
||||||
cmd.arg("-i");
|
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.arg(command);
|
||||||
|
|
||||||
cmd
|
Ok(cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, AsRefStr)]
|
#[derive(Clone, Copy, Debug, Display, Deserialize, AsRefStr)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
#[strum(serialize_all = "lowercase")]
|
#[strum(serialize_all = "lowercase")]
|
||||||
pub enum SudoKind {
|
pub enum SudoKind {
|
||||||
|
|||||||
Reference in New Issue
Block a user