Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/shadow-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

pub mod cli;
pub mod error;
pub mod os_error;
pub mod passwd;
pub mod validate;

Expand Down
44 changes: 44 additions & 0 deletions src/shadow-core/src/os_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// This file is part of the shadow-rs package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

//! Error text sourced from the operating system instead of hardcoded.
//!
//! Wording for conditions that map to a libc `errno` is taken from
//! `strerror` (via [`std::io::Error`]) rather than carried as a string
//! literal in our tree. This keeps the text matching the host OS and lets
//! glibc translate it on localized systems — the same way GNU coreutils
//! renders system errors (e.g. `cat: /tmp: Is a directory`). See issue #159.

/// The operating system's message for a raw `errno`.
///
/// For example `EACCES` renders as "Permission denied" on English locales
/// and the translated equivalent elsewhere. On targets whose libc does not
/// translate (musl), this is the untranslated English text.
#[must_use]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — switched to uucore::error::strip_errno (it's already in 0.8, so no version bump). Added uucore to shadow-core's deps; it was already in the tree via the tool crates. Thanks!

pub fn strerror(errno: i32) -> String {
std::io::Error::from_raw_os_error(errno).to_string()
}
Comment thread
pierre-warnier marked this conversation as resolved.
Outdated

/// The OS message for `EACCES` ("Permission denied"), sourced from libc.
#[must_use]
pub fn permission_denied() -> String {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i am not sure we need this function either, just call the previous line

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to — one consideration: permission_denied() is called from 13 sites across 9 tools (the root-check guard in chage, chpasswd, groupadd, groupdel, groupmod, passwd, useradd, userdel, usermod). Inlining means repeating uucore::error::strip_errno(&std::io::Error::from_raw_os_error(libc::EACCES)) at each one.

Do you still prefer inlining it everywhere, or would you rather a different shape — e.g. argument-less PermissionDenied error variants that render the OS message in their own Display? Happy to go whichever way you think reads best.

strerror(libc::EACCES)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn permission_denied_is_nonempty_and_os_sourced() {
// We assert the shape, not the exact text: the wording comes from the
// host libc and may be localized, so hardcoding it would defeat the
// purpose of this module.
let msg = permission_denied();
assert!(!msg.is_empty());
// Same value libc would give for EACCES directly.
assert_eq!(msg, strerror(libc::EACCES));
}
Comment thread
pierre-warnier marked this conversation as resolved.
Outdated
}
9 changes: 7 additions & 2 deletions src/uu/chage/src/chage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,15 +274,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let current_user = shadow_core::hardening::current_username()
.map_err(|e| ChageError::UnexpectedFailure(e.to_string()))?;
if current_user != *login {
return Err(ChageError::PermissionDenied("Permission denied.".into()).into());
return Err(ChageError::PermissionDenied(
shadow_core::os_error::permission_denied(),
)
.into());
}
}
return cmd_list(&root, login);
}

// All modification flags require root.
if !shadow_core::hardening::caller_is_root() {
return Err(ChageError::PermissionDenied("Permission denied.".into()).into());
return Err(
ChageError::PermissionDenied(shadow_core::os_error::permission_denied()).into(),
);
}

if !has_modifications {
Expand Down
4 changes: 3 additions & 1 deletion src/uu/chpasswd/src/chpasswd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {

// chpasswd always requires root.
if !shadow_core::hardening::caller_is_root() {
return Err(ChpasswdError::PermissionDenied("Permission denied.".into()).into());
return Err(
ChpasswdError::PermissionDenied(shadow_core::os_error::permission_denied()).into(),
);
}

let is_encrypted = matches.get_flag(options::ENCRYPTED);
Expand Down
2 changes: 1 addition & 1 deletion src/uu/groupadd/src/groupadd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
};

if !shadow_core::hardening::caller_is_root() {
uucore::show_error!("Permission denied.");
uucore::show_error!("{}", shadow_core::os_error::permission_denied());
return Err(GroupaddError::AlreadyPrinted(1).into());
}

Expand Down
2 changes: 1 addition & 1 deletion src/uu/groupdel/src/groupdel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
};

if !shadow_core::hardening::caller_is_root() {
uucore::show_error!("Permission denied.");
uucore::show_error!("{}", shadow_core::os_error::permission_denied());
return Err(GroupdelError::AlreadyPrinted(1).into());
}

Expand Down
2 changes: 1 addition & 1 deletion src/uu/groupmod/src/groupmod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
};

if !shadow_core::hardening::caller_is_root() {
uucore::show_error!("Permission denied.");
uucore::show_error!("{}", shadow_core::os_error::permission_denied());
return Err(GroupmodError::AlreadyPrinted(1).into());
}

Expand Down
14 changes: 11 additions & 3 deletions src/uu/passwd/src/passwd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,12 +188,18 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
// Non-root users can only view their own status.
if !shadow_core::hardening::caller_is_root() {
if show_all {
return Err(PasswdError::PermissionDenied("Permission denied.".into()).into());
return Err(PasswdError::PermissionDenied(
shadow_core::os_error::permission_denied(),
)
.into());
}
let current_user = shadow_core::hardening::current_username()
.map_err(|e| PasswdError::UnexpectedFailure(e.to_string()))?;
if current_user != target_user {
return Err(PasswdError::PermissionDenied("Permission denied.".into()).into());
return Err(PasswdError::PermissionDenied(
shadow_core::os_error::permission_denied(),
)
.into());
}
}

Expand All @@ -218,7 +224,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
// caller to be root. Non-root users can only change their own password
// (the default PAM path below).
if (has_mutation || has_aging) && !shadow_core::hardening::caller_is_root() {
return Err(PasswdError::PermissionDenied("Permission denied.".into()).into());
return Err(
PasswdError::PermissionDenied(shadow_core::os_error::permission_denied()).into(),
);
}

// When a mutation flag and aging flags are both present, apply both in a
Expand Down
2 changes: 1 addition & 1 deletion src/uu/useradd/src/useradd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {

// Only root can add users.
if !shadow_core::hardening::caller_is_root() {
uucore::show_error!("Permission denied.");
uucore::show_error!("{}", shadow_core::os_error::permission_denied());
return Err(UseraddError::AlreadyPrinted(1).into());
}

Expand Down
4 changes: 3 additions & 1 deletion src/uu/userdel/src/userdel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {

// Must be root.
if !rustix::process::getuid().is_root() {
return Err(UserdelError::CantUpdatePasswd("Permission denied.".into()).into());
return Err(
UserdelError::CantUpdatePasswd(shadow_core::os_error::permission_denied()).into(),
);
}

// Read the user's home directory and UID from /etc/passwd BEFORE removing
Expand Down
2 changes: 1 addition & 1 deletion src/uu/usermod/src/usermod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let root = SysRoot::new(prefix);

if !rustix::process::getuid().is_root() {
return Err(UsermodError::CantUpdate("Permission denied.".into()).into());
return Err(UsermodError::CantUpdate(shadow_core::os_error::permission_denied()).into());
}

// Block signals for the passwd lock→write critical section only.
Expand Down