diff --git a/README.md b/README.md index cbd8bd037..859ed2a91 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,11 @@ pacman -S sudo-rs ``` This will offer the functionality using the commands `sudo-rs`, `sudoedit-rs`, `visudo-rs` and `su-rs` to avoid conflicts. +**Warning**: Due to a packaging issue, uninstalling sudo after installing sudo-rs will result in the deletion of `/etc/sudoers` and `/etc/pam.d/sudo`, rendering sudo-rs non-functional. To avoid this issue, either: + +- Back up `/etc/sudoers` and `/etc/pam.d/sudo` before uninstalling sudo, then restore these files afterward. +- Uninstall sudo before installing sudo-rs and add back `/etc/pam.d/sudo` manually afterwards (of course, ensure you have another way to gain root access first). + The sudo-rs package on Arch Linux is typically up-to-date. ### Fedora diff --git a/src/common/error.rs b/src/common/error.rs index 2077c7f8b..ef797454e 100644 --- a/src/common/error.rs +++ b/src/common/error.rs @@ -24,7 +24,10 @@ pub enum Error { Authorization(String), InteractionRequired, EnvironmentVar(Vec), - Configuration(String), + Configuration { + message: String, + path: Option, + }, Options(String), Pam(PamError), Io(Option, std::io::Error), @@ -78,7 +81,13 @@ impl fmt::Display for Error { } Ok(()) } - Error::Configuration(e) => write!(f, "invalid configuration: {e}"), + Error::Configuration { message, path } => { + if let Some(path) = path { + write!(f, "invalid configuration at {}: {message}", path.display()) + } else { + write!(f, "invalid configuration: {message}") + } + } Error::Options(e) => write!(f, "{e}"), Error::Pam(e) => write!(f, "{e}"), Error::Io(location, e) => { diff --git a/src/sudo/pam.rs b/src/sudo/pam.rs index b74592d7f..3c4525a2a 100644 --- a/src/sudo/pam.rs +++ b/src/sudo/pam.rs @@ -1,11 +1,53 @@ -use std::ffi::OsString; +use std::{ffi::OsString, path::PathBuf}; use crate::common::context::LaunchType; use crate::common::error::Error; use crate::log::{dev_info, user_warn}; -use crate::pam::{PamContext, PamError, PamErrorType, PamResult}; +use crate::pam::{PamContext, PamError, PamErrorType}; use crate::system::{term::current_tty_name, time::Duration}; +#[cfg(target_os = "freebsd")] +const PAM_SERVICE_DIRS: &[&str] = &["/usr/local/etc/pam.d", "/etc/pam.d"]; + +#[cfg(not(target_os = "freebsd"))] +const PAM_SERVICE_DIRS: &[&str] = &["/etc/pam.d"]; + +fn pam_service_candidates(service_name: &str) -> Vec { + PAM_SERVICE_DIRS + .iter() + .map(|dir| PathBuf::from(dir).join(service_name)) + .collect() +} + +fn ensure_pam_service_config(service_name: &str) -> Result<(), Error> { + let candidates = pam_service_candidates(service_name); + + if candidates.iter().any(|path| path.exists()) { + return Ok(()); + } + + let message = if candidates.len() == 1 { + format!( + "PAM configuration for service '{service_name}' not found at {}; This file is needed to run sudo-rs. Uninstalling sudo after installing sudo-rs might cause this problem on some distributions like arch linux due to a packaging issue. See https://github.com/trifectatechfoundation/sudo-rs/issues/1182", + candidates[0].display() + ) + } else { + let searched = candidates + .iter() + .map(|path| path.display().to_string()) + .collect::>() + .join(", "); + format!( + "PAM configuration for service '{service_name}' not found in any of: {searched}; This file is needed to run sudo-rs. Uninstalling sudo after installing sudo-rs might cause this problem on some distributions like arch linux due to a packaging issue. See https://github.com/trifectatechfoundation/sudo-rs/issues/1182" + ) + }; + + Err(Error::Configuration { + message, + path: candidates.first().cloned(), + }) +} + pub(super) struct InitPamArgs<'a> { pub(super) launch: LaunchType, pub(super) use_stdin: bool, @@ -34,11 +76,13 @@ pub(super) fn init_pam( target_user, hostname, }: InitPamArgs, -) -> PamResult { +) -> Result { let service_name = match launch { LaunchType::Login if cfg!(feature = "pam-login") => "sudo-i", LaunchType::Login | LaunchType::Shell | LaunchType::Direct => "sudo", }; + + ensure_pam_service_config(service_name)?; let mut pam = PamContext::new_cli( "sudo", service_name, @@ -48,7 +92,8 @@ pub(super) fn init_pam( password_feedback, password_timeout, Some(auth_user), - )?; + ) + .map_err(Error::from)?; pam.mark_silent(matches!(launch, LaunchType::Direct)); pam.mark_allow_null_auth_token(false); pam.set_requesting_user(requesting_user)?; diff --git a/src/sudo/pipeline.rs b/src/sudo/pipeline.rs index a8a3b73b0..9ba43a168 100644 --- a/src/sudo/pipeline.rs +++ b/src/sudo/pipeline.rs @@ -27,10 +27,20 @@ mod edit; pub(super) use edit::run_edit; fn read_sudoers() -> Result { - let sudoers_path = &super::candidate_sudoers_file(); - - let (sudoers, syntax_errors) = - Sudoers::open(sudoers_path).map_err(|e| Error::Configuration(format!("{e}")))?; + let sudoers_path = super::candidate_sudoers_file(); + + let (sudoers, syntax_errors) = Sudoers::open(&sudoers_path).map_err(|e| { + let mut message = e.to_string(); + if e.kind() == std::io::ErrorKind::NotFound { + message = format!( + "{message}; sudo-rs looks for /etc/sudoers-rs first and falls back to /etc/sudoers. Create one of these files before running sudo-rs. Uninstalling sudo after installing sudo-rs might cause this problem on some distributions like arch linux due to a packaging issue." + ); + } + Error::Configuration { + message, + path: Some(sudoers_path.clone()), + } + })?; for crate::sudoers::Error { source, @@ -38,7 +48,7 @@ fn read_sudoers() -> Result { message, } in syntax_errors { - let path = source.as_deref().unwrap_or(sudoers_path); + let path = source.as_deref().unwrap_or(&sudoers_path); diagnostic::diagnostic!("{message}", path @ location); }