Improved id and whoami.

Details

- New implementation of `id` inspired by BSD's one.
- Refactoring on `whoami`.
- Added few utility functions in `userutils` for getting processes user and group
real/effective ID and names.

Added docs do `getty`, `login`, `passwd`, `su`, and `sudo`.

Details

- The docs are from BSD's ones.
- Also added `ArgParser` to all of them and added a -h/--help flag
to all of them.
- This is the start of a revision of all of those commands.
This commit is contained in:
Jose Narvaez
2017-07-17 18:20:58 +01:00
parent e70be16a48
commit ef5fdc82a0
8 changed files with 617 additions and 29 deletions
+37
View File
@@ -1,11 +1,35 @@
#![deny(warnings)]
extern crate syscall;
extern crate arg_parser;
use std::process::Command;
use std::{env, process, str};
use std::io::{self, Write};
use arg_parser::ArgParser;
const MAN_PAGE: &'static str = /* @MANSTART{login} */ r#"
NAME
getty - set terminal mode
SYNOPSIS
getty
DESCRIPTION
The getty utility is called by init(8) to open and initialize the tty line,
read a login name, and invoke login(1).
OPTIONS
-h
--help
Display this help and exit.
AUTHOR
Written by Jeremy Soller.
"#;
fn set_tty(tty: &str) -> syscall::Result<()> {
let stdin = syscall::open(tty, syscall::flag::O_RDONLY)?;
let stdout = syscall::open(tty, syscall::flag::O_WRONLY)?;
@@ -39,6 +63,19 @@ fn daemon(clear: bool) {
}
pub fn main() {
let mut stdout = io::stdout();
let mut parser = ArgParser::new(1)
.add_flag(&["h", "help"]);
parser.parse(env::args());
// Shows the help
if parser.found("help") {
let _ = stdout.write_all(MAN_PAGE.as_bytes());
let _ = stdout.flush();
process::exit(0);
}
let mut tty_option = None;
let mut clear = true;
for arg in env::args().skip(1) {
+215 -3
View File
@@ -1,9 +1,221 @@
extern crate arg_parser;
extern crate extra;
extern crate syscall;
extern crate userutils;
use std::borrow::Borrow;
use std::hash::Hash;
use std::env;
use std::io::{self, Write, Stderr, StdoutLock};
use std::process::exit;
use extra::io::fail;
use extra::option::OptionalExt;
use arg_parser::{ArgParser, Param};
use userutils::{get_egid, get_gid, get_euid, get_uid, get_user, get_group};
const HELP_INFO: &'static str = "Try id --help for more information.\n";
const MAN_PAGE: &'static str = /* @MANSTART{id} */ r#"
NAME
id - display user identity
SYNOPSIS
id
id -g [-nr]
id -u [-nr]
id [ -h | --help ]
DESCRIPTION
The id utility displays the user and group names and numeric IDs,
of the calling process, to the standard output.
OPTIONS
-G
Display the different group IDs (effective and real)
as white-space separated numbers, in no particular order.
-g
Display the effective group ID as a number.
-n Display the name of the user or group ID for the -g and -u
options instead of the number.
-u
Display the effective user ID as a number.
-a
Ignored for compatibility with other id implementations.
-h
--help
Display this help and exit.
EXIT STATUS
The whoami utility exits 0 on success, and >0 if an error occurs.
AUTHOR
Written by Jose Narvaez.
"#; /* @MANEND */
pub fn main() {
let uid = syscall::getuid().expect("id: failed to get UID");
let gid = syscall::getgid().expect("id: failed to get GID");
println!("uid={}({}) gid={}({})", uid, env::var("USER").unwrap_or(String::new()), gid, "");
let stdout = io::stdout();
let mut stdout = stdout.lock();
let mut stderr = io::stderr();
let mut parser = ArgParser::new(1)
.add_flag(&["h", "help"])
.add_flag(&["a"])
.add_flag(&["G"])
.add_flag(&["g"])
.add_flag(&["u"])
.add_flag(&["n"])
.add_flag(&["r"]);
parser.parse(env::args());
// Shows the help
if parser.found("help") {
print_msg(MAN_PAGE, &mut stdout, &mut stderr);
exit(0);
}
// Unrecognized flags
if let Err(err) = parser.found_invalid() {
stderr.write_all(err.as_bytes()).try(&mut stderr);
print_msg(HELP_INFO, &mut stdout, &mut stderr);
exit(1);
}
// Display the different group IDs (effective and real)
// as white-space separated numbers, in no particular order.
if parser.found(&'G') {
if any_of_found(&parser, &[&'g', &'u']) {
let msg = "id: -G option must be used without others\n";
print_msg(msg, &mut stdout, &mut stderr);
print_msg(HELP_INFO, &mut stdout, &mut stderr);
exit(1);
}
let egid = get_egid(&mut stderr);
let gid = get_gid(&mut stderr);
print_msg(&format!("{} {}\n", egid, gid), &mut stdout, &mut stderr);
exit(0);
}
// Check if people passed both -g -u which are mutually exclusive
if parser.found(&'u') && parser.found(&'g') {
let msg = "id: specify either -u or -g but not both\n";
print_msg(msg, &mut stdout, &mut stderr);
print_msg(HELP_INFO, &mut stdout, &mut stderr);
exit(1);
}
// Display effective/real process user ID UNIX user name
if parser.found(&'u') && parser.found(&'n') {
// Did they pass -r? F so, we show the real
let uid = if parser.found(&'r') {
get_uid(&mut stderr)
} else {
get_euid(&mut stderr)
};
get_user(uid, &mut stderr).map(|user| {
print_msg(&format!("{}\n", user), &mut stdout, &mut stderr);
exit(0);
}).or_else(|| {
fail(&format!("id: no user found for uid: {}", uid), &mut stderr)
});
}
// Display real user ID
if parser.found(&'u') && parser.found(&'r') {
let uid = get_uid(&mut stderr);
print_msg(&format!("{}\n", uid), &mut stdout, &mut stderr);
exit(0);
}
// Display effective user ID
if parser.found(&'u') {
let euid = get_euid(&mut stderr);
print_msg(&format!("{}\n", euid), &mut stdout, &mut stderr);
exit(0);
}
// Display effective/real process group ID UNIX group name
if parser.found(&'g') && parser.found(&'n') {
// Did they pass -r? If so we show the real one
let gid = if parser.found(&'r') {
get_gid(&mut stderr)
} else {
get_egid(&mut stderr)
};
get_group(gid, &mut stderr).map(|group| {
print_msg(&format!("{}\n", group), &mut stdout, &mut stderr);
exit(0);
}).or_else(|| {
fail(&format!("id: no group found for gid: {}", gid), &mut stderr)
});
}
// Display the real group ID
if parser.found(&'g') && parser.found(&'r') {
let gid = get_gid(&mut stderr);
print_msg(&format!("{}\n", gid), &mut stdout, &mut stderr);
exit(0);
}
// Display effective group ID
if parser.found(&'g') {
let egid = get_egid(&mut stderr);
print_msg(&format!("{}\n", egid), &mut stdout, &mut stderr);
exit(0);
}
// -n does not apply if there is no -u or -g
if parser.found(&'n') && none_of_found(&parser, &[&'u', &'g']) {
let msg = "id: the -n option must be used with either -u or -g\n";
fail(msg, &mut stderr);
}
// -r does not apply if there is no -u or -g
if parser.found(&'r') && none_of_found(&parser, &[&'u', &'g']) {
let msg = "id: the -r option must be used with either -u or -g\n";
fail(msg, &mut stderr);
}
// We get everything we can and show that
let euid = get_euid(&mut stderr);
let egid = get_egid(&mut stderr);
let user = get_user(euid, &mut stderr).unwrap_or_else(|| {
fail(&format!("id: no user found for uid: {}", euid), &mut stderr);
});
let group = get_group(egid, &mut stderr).unwrap_or_else(|| {
fail(&format!("id: no group found for gid: {}", euid), &mut stderr);
});
let msg = format!("uid={}({}) gid={}({})\n", euid, user, egid, group);
print_msg(&msg, &mut stdout, &mut stderr);
exit(0);
}
pub fn any_of_found<P: Hash + Eq + ?Sized>(parser: &ArgParser, flags: &[&P]) -> bool
where Param: Borrow<P>
{
for flag in flags {
if parser.found(*flag) { return true }
}
false
}
fn none_of_found<P: Hash + Eq + ?Sized>(parser: &ArgParser, flags: &[&P]) -> bool
where Param: Borrow<P>
{
!any_of_found(parser, flags)
}
fn print_msg(msg: &str, stdout: &mut StdoutLock, stderr: &mut Stderr) {
stdout.write_all(msg.as_bytes()).try(stderr);
stdout.flush().try(stderr);
}
+35 -1
View File
@@ -3,19 +3,53 @@
extern crate liner;
extern crate termion;
extern crate userutils;
extern crate arg_parser;
use std::fs::File;
use std::io::{self, Read, Write};
use std::os::unix::process::CommandExt;
use std::process::Command;
use std::process::{self, Command};
use std::env;
use std::str;
use arg_parser::ArgParser;
use termion::input::TermRead;
use userutils::Passwd;
const MAN_PAGE: &'static str = /* @MANSTART{login} */ r#"
NAME
login - log into the computer
SYNOPSIS
login
DESCRIPTION
The login utility logs users (and pseudo-users) into the computer system.
OPTIONS
-h
--help
Display this help and exit.
AUTHOR
Written by Jeremy Soller.
"#;
pub fn main() {
let mut stdout = io::stdout();
let mut parser = ArgParser::new(1)
.add_flag(&["h", "help"]);
parser.parse(env::args());
// Shows the help
if parser.found("help") {
let _ = stdout.write_all(MAN_PAGE.as_bytes());
let _ = stdout.flush();
process::exit(0);
}
if let Ok(mut issue) = File::open("/etc/issue") {
io::copy(&mut issue, &mut stdout).unwrap();
let _ = stdout.flush();
+39 -1
View File
@@ -1,3 +1,4 @@
extern crate arg_parser;
extern crate rand;
extern crate syscall;
extern crate termion;
@@ -7,15 +8,52 @@ use rand::{Rng, OsRng};
use std::{env, io};
use std::fs::File;
use std::io::{Read, Write};
use std::process;
use arg_parser::ArgParser;
use termion::input::TermRead;
use userutils::Passwd;
const MAN_PAGE: &'static str = /* @MANSTART{passwd} */ r#"
NAME
passwd - modify a user's password
SYNOPSIS
passwd
passwd [ -h | --help ]
DESCRIPTION
The passwd utility changes the user's local password. If the user is not
the super-user, passwd first prompts for the current password and will
not continue unless the correct password is entered.
OPTIONS
-h
--help
Display this help and exit.
AUTHOR
Written by Jeremy Soller.
"#;
fn main() {
let stdin = io::stdin();
let mut stdin = stdin.lock();
let stdout = io::stdout();
let mut stdout = stdout.lock();
let mut parser = ArgParser::new(1)
.add_flag(&["h", "help"]);
parser.parse(env::args());
// Shows the help
if parser.found("help") {
let _ = stdout.write_all(MAN_PAGE.as_bytes());
let _ = stdout.flush();
process::exit(0);
}
let uid = syscall::getuid().unwrap() as u32;
let mut passwd_string = String::new();
@@ -87,7 +125,7 @@ fn main() {
if let Some(confirm_password) = stdin.read_passwd(&mut stdout).unwrap() {
stdout.write(b"\n").unwrap();
let _ = stdout.flush();
if new_password == confirm_password {
let salt = format!("{:X}", OsRng::new().unwrap().next_u64());
writeln!(stdout, "{}", userutils::Passwd::encode(&new_password, &salt)).unwrap();
+37 -1
View File
@@ -1,23 +1,59 @@
extern crate syscall;
extern crate termion;
extern crate userutils;
extern crate arg_parser;
use std::env;
use std::fs::File;
use std::io::{self, Read, Write};
use std::os::unix::process::CommandExt;
use std::process::Command;
use std::process::{self, Command};
use std::str;
use arg_parser::ArgParser;
use termion::input::TermRead;
use userutils::Passwd;
const MAN_PAGE: &'static str = /* @MANSTART{su} */ r#"
NAME
su - substitute user identity
SYNOPSIS
su
su command
su [ -h | --help ]
DESCRIPTION
The su utility requests appropriate user credentials via PAM and switches to
that user ID (the default user is the superuser). A shell is then executed.
OPTIONS
-h
--help
Display this help and exit.
AUTHOR
Written by Jeremy Soller.
"#;
pub fn main() {
let stdin = io::stdin();
let mut stdin = stdin.lock();
let stdout = io::stdout();
let mut stdout = stdout.lock();
let mut parser = ArgParser::new(1)
.add_flag(&["h", "help"]);
parser.parse(env::args());
// Shows the help
if parser.found("help") {
let _ = stdout.write_all(MAN_PAGE.as_bytes());
let _ = stdout.flush();
process::exit(0);
}
let mut user = env::args().nth(1).unwrap_or(String::new());
if user.is_empty() {
user = String::from("root");
+41
View File
@@ -1,3 +1,5 @@
extern crate arg_parser;
extern crate extra;
extern crate syscall;
extern crate termion;
extern crate userutils;
@@ -8,9 +10,37 @@ use std::io::{self, Read, Write};
use std::os::unix::process::CommandExt;
use std::process::{self, Command};
use arg_parser::ArgParser;
use termion::input::TermRead;
use userutils::{Passwd, Group};
const MAN_PAGE: &'static str = /* @MANSTART{sudo} */ r#"
NAME
sudo - execute a command as another user
SYNOPSIS
sudo command
sudo [ -h | --help ]
DESCRIPTION
The sudo utility allows a permitted user to execute a command as the
superuser or another user, as specified by the security policy.
OPTIONS
-h
--help
Display this help and exit.
EXIT STATUS
Upon successful execution of a command, the exit status from sudo will
be the exit status of the program that was executed. In case of error
the exit status will be >0.
AUTHOR
Written by Jeremy Soller.
"#;
pub fn main() {
let stdin = io::stdin();
let mut stdin = stdin.lock();
@@ -19,6 +49,17 @@ pub fn main() {
let stderr = io::stderr();
let mut stderr = stderr.lock();
let mut parser = ArgParser::new(1)
.add_flag(&["h", "help"]);
parser.parse(env::args());
// Shows the help
if parser.found("help") {
let _ = stdout.write_all(MAN_PAGE.as_bytes());
let _ = stdout.flush();
process::exit(0);
}
let mut args = env::args().skip(1);
match args.next() {
None => {
+15 -23
View File
@@ -5,13 +5,12 @@ extern crate extra;
extern crate syscall;
extern crate userutils;
use std::io::{self, Read, Write};
use std::io::{self, Write};
use std::process::exit;
use std::fs::File;
use std::env;
use arg_parser::ArgParser;
use extra::option::OptionalExt;
use userutils::Passwd;
use userutils::{get_euid, get_user};
const MAN_PAGE: &'static str = /* @MANSTART{whoami} */ r#"
NAME
@@ -35,8 +34,6 @@ AUTHOR
Written by Jose Narvaez.
"#; /* @MANEND */
const PASSWD_FILE: &'static str = "/etc/passwd";
fn main() {
let stdout = io::stdout();
let mut stdout = stdout.lock();
@@ -52,23 +49,18 @@ fn main() {
exit(0);
}
match syscall::geteuid() {
Ok(euid) => {
let mut passwd_string = String::new();
if let Ok(mut file) = File::open(PASSWD_FILE) {
let _ = file.read_to_string(&mut passwd_string);
}
for line in passwd_string.lines() {
if let Ok(passwd) = Passwd::parse(line) {
if euid == passwd.uid as usize {
stdout.write_all(format!("{}\n", passwd.name).as_bytes()).try(&mut stderr);
stdout.flush().try(&mut stderr);
exit(0);
}
}
}
},
Err(_) => exit(1)
let euid = get_euid(&mut stderr);
let user = match get_user(euid, &mut stderr) {
Some(user) => user,
None => {
let msg = format!("whoami: no user found for uid: {}", euid);
stdout.write_all(msg.as_bytes()).try(&mut stderr);
stdout.flush().try(&mut stderr);
exit(1);
}
};
stdout.write_all(format!("{}\n", user).as_bytes()).try(&mut stderr);
stdout.flush().try(&mut stderr);
exit(0);
}
+198
View File
@@ -1,8 +1,19 @@
extern crate argon2rs;
extern crate extra;
extern crate syscall;
use std::io::{Read, Stderr, Write};
use std::fs::File;
use std::process::exit;
use argon2rs::verifier::Encoded;
use argon2rs::{Argon2, Variant};
use extra::option::OptionalExt;
const PASSWD_FILE: &'static str = "/etc/passwd";
const GROUP_FILE: &'static str = "/etc/group";
/// A struct representing a UNIX /etc/passwd file entry
pub struct Passwd<'a> {
pub user: &'a str,
pub hash: &'a str,
@@ -36,6 +47,18 @@ impl<'a> Passwd<'a> {
})
}
pub fn parse_file(file_data: &'a str) -> Result<Vec<Passwd<'a>>, ()> {
let mut entries: Vec<Passwd<'a>> = Vec::new();
for line in file_data.lines() {
if let Ok(passwd) = Passwd::parse(line) {
entries.push(passwd);
}
}
Ok(entries)
}
pub fn encode(password: &str, salt: &str) -> String {
let a2 = Argon2::new(10, 1, 4096, Variant::Argon2i).unwrap();
let e = Encoded::new(a2, password.as_bytes(), salt.as_bytes(), &[], &[]);
@@ -48,6 +71,7 @@ impl<'a> Passwd<'a> {
}
}
/// A struct representing a UNIX /etc/group file entry
pub struct Group<'a> {
pub group: &'a str,
pub gid: u32,
@@ -68,4 +92,178 @@ impl<'a> Group<'a> {
users: users
})
}
pub fn parse_file(file_data: &'a str) -> Result<Vec<Group<'a>>, ()> {
let mut entries: Vec<Group<'a>> = Vec::new();
for line in file_data.lines() {
if let Ok(group) = Group::parse(line) {
entries.push(group);
}
}
Ok(entries)
}
}
/// Gets the current process effective user id aborting the caller on error.
///
/// This function issues the `geteuid` system call returning the process effective
/// user id. In case of an error it will log message to `stderr` and then abort
/// the caller process with an non-zero exit code.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// let euid = get_euid(&mut stderr);
///
/// ```
pub fn get_euid(stderr: &mut Stderr) -> usize {
match syscall::geteuid() {
Ok(euid) => euid,
Err(_) => {
let mut stderr = stderr.lock();
let _ = stderr.write_all(b"failed to get effective UID\n");
let _ = stderr.flush();
exit(1)
}
}
}
/// Gets the current process real user id aborting the caller on error.
///
/// This function issues the `getuid` system call returning the process real
/// user id. In case of an error it will log message to `stderr` and then abort
/// the caller process with an non-zero exit code.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// let uid = get_uid(&mut stderr);
///
/// ```
pub fn get_uid(stderr: &mut Stderr) -> usize {
match syscall::getuid() {
Ok(euid) => euid,
Err(_) => {
let mut stderr = stderr.lock();
let _ = stderr.write_all(b"failed to get real UID\n");
let _ = stderr.flush();
exit(1)
}
}
}
/// Gets the current process effective group id aborting the caller on error.
///
/// This function issues the `getegid` system call returning the process effective
/// group id. In case of an error it will log message to `stderr` and then abort
/// the caller process with an non-zero exit code.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// let egid = get_egid(&mut stderr);
///
/// ```
pub fn get_egid(stderr: &mut Stderr) -> usize {
match syscall::getegid() {
Ok(euid) => euid,
Err(_) => {
let mut stderr = stderr.lock();
let _ = stderr.write_all(b"failed to get effective GID\n");
let _ = stderr.flush();
exit(1)
}
}
}
/// Gets the current process real group id aborting the caller on error.
///
/// This function issues the `getegid` system call returning the process real
/// group id. In case of an error it will log message to `stderr` and then abort
/// the caller process with an non-zero exit code.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// let gid = get_gid(&mut stderr);
///
/// ```
pub fn get_gid(stderr: &mut Stderr) -> usize {
match syscall::getgid() {
Ok(euid) => euid,
Err(_) => {
let mut stderr = stderr.lock();
let _ = stderr.write_all(b"failed to get real GID\n");
let _ = stderr.flush();
exit(1)
}
}
}
/// Gets the user name for a given user id.
///
/// This function will read `/etc/passwd` looking for an entry for the provided
/// user ID, returning its UNIX username. In case of an error it will log message
/// to `stderr` and then will the caller process with an non-zero exit code.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// let user = get_user(1, &mut stderr);
///
/// ```
pub fn get_user(uid: usize, stderr: &mut Stderr) -> Option<String> {
let mut passwd_string = String::new();
let mut file = File::open(PASSWD_FILE).try(stderr);
file.read_to_string(&mut passwd_string).try(stderr);
let passwd_file_entries = Passwd::parse_file(&passwd_string).unwrap();
let passwd = passwd_file_entries.iter()
.find(|passwd| passwd.uid as usize == uid);
match passwd {
Some(passwd) => Some(String::from(passwd.user)),
None => None
}
}
/// Gets the UNIX group name for a given group ID.
///
/// This function will read `/etc/group` file looking for an entry for the provided
/// group ID, returning its UNIX group name. In case of an error it will log message
/// to `stderr` and then will the caller process with an non-zero exit code.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// let group = get_group(1, &mut stderr);
///
/// ```
pub fn get_group(gid: usize, stderr: &mut Stderr) -> Option<String> {
let mut group_string = String::new();
let mut file = File::open(GROUP_FILE).try(stderr);
file.read_to_string(&mut group_string).try(stderr);
let group_file_entries = Group::parse_file(&group_string).unwrap();
let group = group_file_entries.iter()
.find(|group| group.gid as usize == gid);
match group {
Some(group) => Some(String::from(group.group)),
None => None
}
}