From 2f58e82de2c7ac2a7059e31ad9c93392bd42ed8e Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Thu, 12 Jan 2023 18:33:41 -0500 Subject: [PATCH] Added `groups` and `clear` applets --- src/cmd/clear/mod.rs | 28 ++++++++++++++++ src/cmd/groups/mod.rs | 55 +++++++++++++++++++++++++++++++ src/cmd/mod.rs | 14 +++++--- src/cmd/which/mod.rs | 4 +-- src/pw/mod.rs | 77 +++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 169 insertions(+), 9 deletions(-) create mode 100644 src/cmd/clear/mod.rs create mode 100644 src/cmd/groups/mod.rs diff --git a/src/cmd/clear/mod.rs b/src/cmd/clear/mod.rs new file mode 100644 index 0000000..5969845 --- /dev/null +++ b/src/cmd/clear/mod.rs @@ -0,0 +1,28 @@ +use clap::Command; +use super::Cmd; + +#[derive(Debug, Default)] +pub struct Clear; + +impl Cmd for Clear { + fn name(&self) -> &str { + "clear" + } + + fn cli(&self) -> Command { + Command::new("clear") + .about("clear the terminal's screen") + .author("Nathan Fisher") + .version(env!("CARGO_PKG_VERSION")) + } + + fn run(&self, _matches: Option<&clap::ArgMatches>) -> Result<(), Box> { + print!("\x1b[2J\x1b[H"); + print!("\x1b[3J\x1b[H"); + Ok(()) + } + + fn path(&self) -> Option { + Some(crate::Path::UsrBin) + } +} diff --git a/src/cmd/groups/mod.rs b/src/cmd/groups/mod.rs new file mode 100644 index 0000000..cc9bf8c --- /dev/null +++ b/src/cmd/groups/mod.rs @@ -0,0 +1,55 @@ +use super::Cmd; +use crate::pw; +use clap::{Arg, Command}; +use std::io; + +#[derive(Debug, Default)] +pub struct Groups; + +impl Cmd for Groups { + fn name(&self) -> &str { + "groups" + } + + fn cli(&self) -> clap::Command { + Command::new("groups") + .about("display current group names") + .author("Nathan Fisher") + .version(env!("CARGO_PKG_VERSION")) + .after_help( + "The groups command displays the current group names or ID values. \ + If the value does not have a corresponding entry in /etc/group, the \ + value will be displayed as the numerical group value. The optional \ + user parameter will display the groups for the named user.", + ) + .arg(Arg::new("user")) + } + + fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { + let Some(matches) = matches else { + return Err(Box::new(io::Error::new(io::ErrorKind::Other, "no input"))) + }; + let groups = match matches.get_one::("user") { + Some(u) => { + pw::get_group_names_for_name(&u)? + }, + None => { + pw::get_group_names()? + }, + }; + let len = groups.len(); + for (idx, group) in groups.into_iter().enumerate() { + if idx < len - 1 { + print!("{group} "); + } else { + println!("{group}"); + } + } + + Ok(()) + } + + fn path(&self) -> Option { + Some(crate::Path::UsrBin) + } +} diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index c245191..eba7f46 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -7,6 +7,7 @@ pub mod basename; pub mod bootstrap; mod cat; mod chmod; +pub mod clear; mod cp; mod date; mod dd; @@ -16,6 +17,7 @@ pub mod factor; pub mod r#false; pub mod fold; mod getty; +pub mod groups; pub mod head; pub mod hostname; mod ln; @@ -38,9 +40,9 @@ pub mod yes; pub use { self::hostname::Hostname, base32::Base32, base64::Base64, basename::Basename, - bootstrap::Bootstrap, dirname::Dirname, echo::Echo, factor::Factor, fold::Fold, head::Head, - mountpoint::Mountpoint, nologin::Nologin, nproc::Nproc, r#false::False, r#true::True, rev::Rev, - shitbox::Shitbox, sleep::Sleep, which::Which, whoami::Whoami, yes::Yes, + bootstrap::Bootstrap, clear::Clear, dirname::Dirname, echo::Echo, factor::Factor, fold::Fold, groups::Groups, + head::Head, mountpoint::Mountpoint, nologin::Nologin, nproc::Nproc, r#false::False, + r#true::True, rev::Rev, shitbox::Shitbox, sleep::Sleep, which::Which, whoami::Whoami, yes::Yes, }; pub trait Cmd: fmt::Debug + Sync { @@ -56,11 +58,13 @@ pub fn get(name: &str) -> Option> { "base32" => Some(Box::new(Base32::default())), "basename" => Some(Box::new(Basename::default())), "bootstrap" => Some(Box::new(Bootstrap::default())), + "clear" => Some(Box::new(Clear::default())), "dirname" => Some(Box::new(Dirname::default())), "echo" => Some(Box::new(Echo::default())), "factor" => Some(Box::new(Factor::default())), "false" => Some(Box::new(False::default())), "fold" => Some(Box::new(Fold::default())), + "groups" => Some(Box::new(Groups::default())), "head" => Some(Box::new(Head::default())), "mountpoint" => Some(Box::new(Mountpoint::default())), "nologin" => Some(Box::new(Nologin::default())), @@ -76,16 +80,18 @@ pub fn get(name: &str) -> Option> { } } -pub static COMMANDS: [&'static str; 21] = [ +pub static COMMANDS: [&'static str; 23] = [ "base32", "base64", "basename", "bootstrap", + "clear", "dirname", "echo", "false", "factor", "fold", + "groups", "head", "hostname", "mountpoint", diff --git a/src/cmd/which/mod.rs b/src/cmd/which/mod.rs index 943c6a3..7a4d711 100644 --- a/src/cmd/which/mod.rs +++ b/src/cmd/which/mod.rs @@ -62,9 +62,7 @@ fn which(command: &str, path: &[&str]) -> Option { if let Ok(exe) = File::open(&file) { if let Ok(meta) = exe.metadata() { let mode = meta.mode(); - let myuid = unsafe { - libc::geteuid() - }; + let myuid = unsafe { libc::geteuid() }; let mygroups = crate::pw::get_gids(); // we own the file and it has u+x if myuid == meta.uid() && mode & 0o100 != 0 { diff --git a/src/pw/mod.rs b/src/pw/mod.rs index 4884be1..c8c7f96 100644 --- a/src/pw/mod.rs +++ b/src/pw/mod.rs @@ -1,4 +1,4 @@ -use std::{error::Error, ffi::CStr, num}; +use std::{error::Error, ffi::{CStr, CString, c_int}, num, io}; pub fn get_username<'a>() -> Result<&'a str, std::str::Utf8Error> { let user = unsafe { @@ -20,6 +20,38 @@ pub fn get_eusername<'a>() -> Result<&'a str, std::str::Utf8Error> { user.to_str() } +pub fn get_uid_for_name(name: &str) -> Option { + let user = match CString::new(name.as_bytes()) { + Ok(n) => n, + Err(_) => return None, + }; + let uid = unsafe { + let pw = libc::getpwnam(user.as_ptr()); + if pw.is_null() { + None + } else { + Some((*pw).pw_uid) + } + }; + uid +} + +pub fn get_pgid_for_name(name: &str) -> Option { + let user = match CString::new(name.as_bytes()) { + Ok(n) => n, + Err(_) => return None, + }; + let gid = unsafe { + let pw = libc::getpwnam(user.as_ptr()); + if pw.is_null() { + None + } else { + Some((*pw).pw_gid) + } + }; + gid +} + pub fn get_grpname<'a>() -> Result<&'a str, std::str::Utf8Error> { let group = unsafe { let gid = libc::getgid(); @@ -53,16 +85,57 @@ pub fn get_gids() -> Result, num::TryFromIntError> { Ok(buf) } +pub fn get_gids_for_name(name: &str) -> Result, Box> { + let gid = match get_pgid_for_name(name) { + Some(g) => g as libc::gid_t, + None => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "cannot get primary group"))), + }; + let mut buf: Vec = vec![0; 1024]; + let name = CString::new(name.as_bytes())?; + let mut ct: c_int = 1024; + unsafe { + let res = libc::getgrouplist(name.as_ptr(), gid, buf.as_mut_ptr(), &mut ct); + if res < 0 { + return Err(Box::new(io::Error::new(io::ErrorKind::Other, "too many groups"))) + } + } + Ok(buf[0..ct as usize].iter().map(|x| *x as u32).collect()) +} + pub fn get_group_names() -> Result, Box> { let gids = get_gids()?; let mut names = vec![]; for id in gids { let name = unsafe { - let gr = libc::getgrgid(id); + let gr = libc::getgrgid(id); + if gr.is_null() { + id.to_string() + } else { let name = (*gr).gr_name; // if we don't take ownership here the OS can (and will) // reuse the memory CStr::from_ptr(name).to_str()?.to_string() + } + }; + names.push(name); + } + Ok(names) +} + +pub fn get_group_names_for_name(name: &str) -> Result, Box> { + let gids = get_gids_for_name(name)?; + let mut names = vec![]; + for id in gids { + let name = unsafe { + let gr = libc::getgrgid(id); + if gr.is_null() { + id.to_string() + } else { + let name = (*gr).gr_name; + // if we don't take ownership here the OS can (and will) + // reuse the memory + CStr::from_ptr(name).to_str()?.to_string() + } }; names.push(name); }