From 687b0e16f79be3a411507bfca4ceb8ea498c2a27 Mon Sep 17 00:00:00 2001 From: nibon7 Date: Thu, 6 Jul 2023 06:07:07 +0800 Subject: [PATCH] Replace `users` with `nix` crate (#9610) # Description The `users` crate hasn't been updated for a long time, this PR tries to replace `users` with `nix`. See [advisory page](https://rustsec.org/advisories/RUSTSEC-2023-0040.html) for additional details. --- Cargo.lock | 14 +--- crates/nu-command/Cargo.toml | 2 +- crates/nu-command/src/filesystem/cd.rs | 72 ++----------------- crates/nu-command/src/filesystem/ls.rs | 5 +- crates/nu-command/src/filesystem/util.rs | 89 ++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7cab31b9b..69568f71cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1915,7 +1915,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04a4202a60e86f1c9702706bb42270dadd333f2db7810157563c86f17af3c873" dependencies = [ - "users 0.10.0", + "users", "winapi", ] @@ -2788,6 +2788,7 @@ dependencies = [ "mime_guess", "mockito", "native-tls", + "nix", "notify-debouncer-full", "nu-ansi-term", "nu-cmd-base", @@ -2842,7 +2843,6 @@ dependencies = [ "unicode-segmentation", "ureq", "url", - "users 0.11.0", "uuid", "wax", "which", @@ -5596,16 +5596,6 @@ dependencies = [ "log", ] -[[package]] -name = "users" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" -dependencies = [ - "libc", - "log", -] - [[package]] name = "utf-8" version = "0.7.6" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 3d9141772f..a7226f28c0 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -104,7 +104,7 @@ winreg = "0.50" [target.'cfg(unix)'.dependencies] libc = "0.2" umask = "2.1" -users = "0.11" +nix = { version = "0.26", default-features = false, features = ["user"] } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash] optional = true diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index 60300baaa6..8f597e4611 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -220,6 +220,8 @@ fn have_permission(dir: impl AsRef) -> PermissionResult<'static> { #[cfg(unix)] fn have_permission(dir: impl AsRef) -> PermissionResult<'static> { + use crate::filesystem::util::users; + match dir.as_ref().metadata() { Ok(metadata) => { use std::os::unix::fs::MetadataExt; @@ -272,75 +274,13 @@ fn have_permission(dir: impl AsRef) -> PermissionResult<'static> { } } -// NOTE: it's a re-export of https://github.com/ogham/rust-users crate -// we need to use it because the upstream pr isn't merged: https://github.com/ogham/rust-users/pull/45 -// once it's merged, we can remove this module -#[cfg(unix)] -mod nu_users { - use libc::c_int; - use std::ffi::{CString, OsStr}; - use std::os::unix::ffi::OsStrExt; - use users::{gid_t, Group}; - /// Returns groups for a provided user name and primary group id. - /// - /// # libc functions used - /// - /// - [`getgrouplist`](https://docs.rs/libc/*/libc/fn.getgrouplist.html) - /// - /// # Examples - /// - /// ```no_run - /// use users::get_user_groups; - /// - /// for group in get_user_groups("stevedore", 1001).expect("Error looking up groups") { - /// println!("User is a member of group #{} ({:?})", group.gid(), group.name()); - /// } - /// ``` - pub fn get_user_groups + ?Sized>( - username: &S, - gid: gid_t, - ) -> Option> { - // MacOS uses i32 instead of gid_t in getgrouplist for unknown reasons - #[cfg(all(unix, target_os = "macos"))] - let mut buff: Vec = vec![0; 1024]; - #[cfg(all(unix, not(target_os = "macos")))] - let mut buff: Vec = vec![0; 1024]; - let name = CString::new(username.as_ref().as_bytes()).expect("OsStr is guaranteed to be zero-free, which is the condition for CString::new to succeed"); - let mut count = buff.len() as c_int; - // MacOS uses i32 instead of gid_t in getgrouplist for unknown reasons - // SAFETY: - // int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups); - // - // `name` is valid CStr to be `const char*` for `user` - // every valid value will be accepted for `group` - // The capacity for `*groups` is passed in as `*ngroups` which is the buffer max length/capacity (as we initialize with 0) - // Following reads from `*groups`/`buff` will only happen after `buff.truncate(*ngroups)` - #[cfg(all(unix, target_os = "macos"))] - let res = - unsafe { libc::getgrouplist(name.as_ptr(), gid as i32, buff.as_mut_ptr(), &mut count) }; - #[cfg(all(unix, not(target_os = "macos")))] - let res = unsafe { libc::getgrouplist(name.as_ptr(), gid, buff.as_mut_ptr(), &mut count) }; - if res < 0 { - None - } else { - buff.truncate(count as usize); - buff.sort_unstable(); - buff.dedup(); - // allow trivial cast: on macos i is i32, on linux it's already gid_t - #[allow(trivial_numeric_casts)] - buff.into_iter() - .filter_map(|i| users::get_group_by_gid(i as gid_t)) - .collect::>() - .into() - } - } -} - #[cfg(unix)] fn any_group(current_user_gid: gid_t, owner_group: u32) -> bool { + use crate::filesystem::util::users; + users::get_current_username() - .map(|name| nu_users::get_user_groups(&name, current_user_gid).unwrap_or_default()) + .and_then(|name| users::get_user_groups(&name, current_user_gid)) .unwrap_or_default() .into_iter() - .any(|group| group.gid() == owner_group) + .any(|gid| gid.as_raw() == owner_group) } diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 1f7a10b782..0d7509a056 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -484,6 +484,7 @@ pub(crate) fn dir_entry_dict( #[cfg(unix)] { + use crate::filesystem::util::users; use std::os::unix::fs::MetadataExt; let mode = md.permissions().mode(); cols.push("mode".into()); @@ -509,7 +510,7 @@ pub(crate) fn dir_entry_dict( cols.push("user".into()); if let Some(user) = users::get_user_by_uid(md.uid()) { vals.push(Value::String { - val: user.name().to_string_lossy().into(), + val: user.name, span, }); } else { @@ -522,7 +523,7 @@ pub(crate) fn dir_entry_dict( cols.push("group".into()); if let Some(group) = users::get_group_by_gid(md.gid()) { vals.push(Value::String { - val: group.name().to_string_lossy().into(), + val: group.name, span, }); } else { diff --git a/crates/nu-command/src/filesystem/util.rs b/crates/nu-command/src/filesystem/util.rs index 14b1ada926..4c22094efd 100644 --- a/crates/nu-command/src/filesystem/util.rs +++ b/crates/nu-command/src/filesystem/util.rs @@ -160,3 +160,92 @@ pub fn is_older(src: &Path, dst: &Path) -> bool { src_ctime <= dst_ctime } } + +#[cfg(unix)] +pub mod users { + use libc::{c_int, gid_t, uid_t}; + use nix::unistd::{Gid, Group, Uid, User}; + use std::ffi::CString; + + pub fn get_user_by_uid(uid: uid_t) -> Option { + User::from_uid(Uid::from_raw(uid)).ok().flatten() + } + + pub fn get_group_by_gid(gid: gid_t) -> Option { + Group::from_gid(Gid::from_raw(gid)).ok().flatten() + } + + pub fn get_current_uid() -> uid_t { + Uid::current().as_raw() + } + + pub fn get_current_gid() -> gid_t { + Gid::current().as_raw() + } + + pub fn get_current_username() -> Option { + User::from_uid(Uid::current()) + .ok() + .flatten() + .map(|user| user.name) + } + + /// Returns groups for a provided user name and primary group id. + /// + /// # libc functions used + /// + /// - [`getgrouplist`](https://docs.rs/libc/*/libc/fn.getgrouplist.html) + /// + /// # Examples + /// + /// ```ignore + /// use users::get_user_groups; + /// + /// for group in get_user_groups("stevedore", 1001).expect("Error looking up groups") { + /// println!("User is a member of group #{group}"); + /// } + /// ``` + pub fn get_user_groups(username: &str, gid: gid_t) -> Option> { + // MacOS uses i32 instead of gid_t in getgrouplist for unknown reasons + #[cfg(target_os = "macos")] + let mut buff: Vec = vec![0; 1024]; + #[cfg(not(target_os = "macos"))] + let mut buff: Vec = vec![0; 1024]; + + let Ok(name) = CString::new(username.as_bytes()) else { + return None; + }; + + let mut count = buff.len() as c_int; + + // MacOS uses i32 instead of gid_t in getgrouplist for unknown reasons + // SAFETY: + // int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups); + // + // `name` is valid CStr to be `const char*` for `user` + // every valid value will be accepted for `group` + // The capacity for `*groups` is passed in as `*ngroups` which is the buffer max length/capacity (as we initialize with 0) + // Following reads from `*groups`/`buff` will only happen after `buff.truncate(*ngroups)` + #[cfg(target_os = "macos")] + let res = + unsafe { libc::getgrouplist(name.as_ptr(), gid as i32, buff.as_mut_ptr(), &mut count) }; + + #[cfg(not(target_os = "macos"))] + let res = unsafe { libc::getgrouplist(name.as_ptr(), gid, buff.as_mut_ptr(), &mut count) }; + + if res < 0 { + None + } else { + buff.truncate(count as usize); + buff.sort_unstable(); + buff.dedup(); + // allow trivial cast: on macos i is i32, on linux it's already gid_t + #[allow(trivial_numeric_casts)] + buff.into_iter() + .filter_map(|i| get_group_by_gid(i as gid_t)) + .map(|group| group.gid) + .collect::>() + .into() + } + } +}