Commit d5e011a3 authored by SamwiseFilmore's avatar SamwiseFilmore

Implement usermod, groupmod, userdel, groupdel; Fix passwd and useradd

Here I've implemented the remainder of the core user utilities present
on most unix systems. They aren't particularly pretty, but they do work
for the most part.

Note that for some reason attempting to move the home directory of a
user via usermod -m does not work.

Also removing user's that have been deleted from groups is not yet
implemented, nor is updating /etc/passwd when a group id is changed.

Note that redox_users as used by Cargo.lock as of this commit is broken,
and may not work properly. If redox-os/users#11 is merged, cargo update
should fix.
parent 7bb8dea1
......@@ -158,7 +158,7 @@ dependencies = [
[[package]]
name = "redox_users"
version = "0.1.0"
source = "git+https://github.com/redox-os/users.git#70b8a69ce6588693d05f3500b481c2620c1ee824"
source = "git+https://github.com/redox-os/users.git#0fc602398887533c947b3f428e9fe0037f829478"
dependencies = [
"argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"extra 0.1.0 (git+https://github.com/redox-os/libextra.git)",
......
......@@ -14,6 +14,14 @@ path = "src/bin/getty.rs"
name = "groupadd"
path = "src/bin/groupadd.rs"
[[bin]]
name = "groupdel"
path = "src/bin/groupdel.rs"
[[bin]]
name = "groupmod"
path = "src/bin/groupmod.rs"
[[bin]]
name = "login"
path = "src/bin/login.rs"
......@@ -34,6 +42,14 @@ path = "src/bin/sudo.rs"
name = "useradd"
path = "src/bin/useradd.rs"
[[bin]]
name = "userdel"
path = "src/bin/userdel.rs"
[[bin]]
name = "usermod"
path = "src/bin/usermod.rs"
[[bin]]
name = "whoami"
path = "src/bin/whoami.rs"
......
#[deny(warnings)]
extern crate arg_parser;
extern crate extra;
extern crate redox_users;
use std::env;
use std::io::{stdout, Write};
use std::process::exit;
use arg_parser::ArgParser;
use extra::option::OptionalExt;
use redox_users::AllGroups;
const MAN_PAGE: &'static str = /* @MANSTART{groupdel} */ r#"
NAME
groupdel - modify system files to delete groups
SYNOPSYS
groupdel [ options ] LOGIN
groupdel [ -h | --help ]
DESCRIPTION
groupdel removes groups from whatever backend is employed by
the system's redox_users.
Note that you should not remove a primary user group before
removing the user. It is also generally wise not to remove
groups that still own files on the system.
OPTIONS
-h, --help
Print this help page and exit.
AUTHORS
Wesley Hershberger.
"#; /* @MANEND */
fn main() {
let mut stdout = stdout();
let mut parser = ArgParser::new(9)
.add_flag(&["h", "help"]);
parser.parse(env::args());
if parser.found("help") {
stdout.write_all(MAN_PAGE.as_bytes()).unwrap();
stdout.flush().unwrap();
exit(0);
}
let group = if parser.args.is_empty() {
eprintln!("groupdel: no login specified");
exit(1);
} else {
&parser.args[0]
};
let mut sys_groups = AllGroups::new().unwrap_or_exit(1);
sys_groups.remove_by_name(group.to_string()).unwrap_or_exit(1);
sys_groups.save().unwrap_or_exit(1);
}
#[deny(warnings)]
extern crate arg_parser;
extern crate extra;
extern crate redox_users;
use std::env;
use std::io::{stdout, Write};
use std::process::exit;
use arg_parser::ArgParser;
use extra::option::OptionalExt;
use redox_users::AllGroups;
const MAN_PAGE: &'static str = /* @MANSTART{groupmod} */ r#"
NAME
groupmod - modify group information
SYNOPSYS
groupmod [ options ] GROUP
groupmod [ -h | --help ]
DESCRIPTION
groupmod modifies a user group GROUP in the system's
redox_users backend.
OPTIONS
-h, --help
Print this help page and exit.
-g, --gid GID
Change GROUP's group id. GID must be a non-negative
decimal integer.
Files with GROUP's old gid will not be updated.
User's who use the old gid as their primary gid will
also not be updated. This is a TODO and will change.
-n, --name NAME
The name of the group will be set to NAME
AUTHORS
Wesley Hershberger.
"#; /* @MANEND */
fn main() {
let mut stdout = stdout();
let mut parser = ArgParser::new(9)
.add_flag(&["h", "help"])
.add_opt("g", "gid")
.add_opt("n", "name");
parser.parse(env::args());
if parser.found("help") {
stdout.write_all(MAN_PAGE.as_bytes()).unwrap();
stdout.flush().unwrap();
exit(0);
}
let groupname = if parser.args.is_empty() {
eprintln!("groupmod: no login specified");
exit(1);
} else {
&parser.args[0]
};
let mut sys_groups = AllGroups::new().unwrap_or_exit(1);
{
let group = sys_groups.get_mut_by_name(groupname).unwrap_or_else(|| {
eprintln!("groupmod: group does not exist: {}", groupname);
exit(1);
});
//TODO: Update user's primary GID, if gid is used as such
if let Some(gid) = parser.get_opt("gid") {
let gid = gid.parse::<usize>().unwrap_or_exit(1);
group.gid = gid;
} else if parser.found("gid") {
eprintln!("groupmod: no gid found");
exit(1);
}
if let Some(name) = parser.get_opt("name") {
group.group = name;
} else if parser.found("name") {
eprintln!("groupmod: no name found");
exit(1);
}
}
sys_groups.save().unwrap_or_exit(1);
}
......@@ -29,10 +29,15 @@ DESCRIPTION
OPTIONS
-h
--help
-h, --help
Display this help and exit.
-l, --lock
Lock the password of the named account. This changes the stored password
hash so that it matches no encrypted value ("!")
Users with locked passwords are not allowed to change their password.
AUTHOR
Written by Jeremy Soller, Jose Narvaez.
"#; /* @MANEND */
......@@ -60,13 +65,21 @@ fn main() {
{
let user = if parser.args.is_empty() {
users.get_mut_by_id(uid).unwrap_or_exit(1)
users.get_mut_by_id(uid).unwrap_or_else(|| {
eprintln!("passwd: you do not exist");
exit(1);
})
} else {
let username = &parser.args[0];
users.get_mut_by_name(username).unwrap_or_exit(1)
users.get_mut_by_name(username).unwrap_or_else(|| {
eprintln!("passwd: user does not exist: {}", username);
exit(1);
})
};
if user.uid == uid || uid == 0 {
if parser.found("lock") {
user.unset_passwd();
} else if user.uid == uid || uid == 0 {
let msg = format!("changing password for '{}' \n", user.user);
stdout.write_all(&msg.as_bytes()).try(&mut stderr);
stdout.flush().try(&mut stderr);
......@@ -74,6 +87,8 @@ fn main() {
let mut verified = false;
if user.is_passwd_blank() {
verified = true;
} else if user.is_passwd_unset() {
verified = false;
} else if user.uid == uid || uid != 0 {
stdout.write_all(b"current password: ").try(&mut stderr);
stdout.flush().try(&mut stderr);
......
......@@ -4,6 +4,7 @@ extern crate arg_parser;
extern crate extra;
extern crate syscall;
extern crate redox_users;
extern crate userutils;
use std::{env, io};
use std::io::Write;
......@@ -11,9 +12,8 @@ use std::process::exit;
use arg_parser::ArgParser;
use extra::option::OptionalExt;
use syscall::call::{open, fchmod, fchown};
use syscall::flag::{O_CREAT, O_DIRECTORY, O_CLOEXEC};
use redox_users::{AllGroups, AllUsers};
use userutils::create_user_dir;
const MAN_PAGE: &'static str = /* @MANSTART{useradd} */ r#"
NAME
......@@ -40,15 +40,16 @@ OPTIONS
-c, --comment
Any text string, usually used as the user's full name.
Historically known as the GECOS field
-d, --home-dir HOME_DIR
The new user will be created with HOME_DIR as their home
directory. The default value is LOGIN prepended with "/home".
This flag DOES NOT create the home directory. See --create-home.
directory. The default value is LOGIN prepended with "/home/".
This flag DOES NOT create the home directory. See --create-home
-g, --gid GID
The group id to use for the default login group. This value must
not be in use and must be non-negative. The default is to pick the
The group id to use when creating the default login group. This value
must not be in use and must be non-negative. The default is to pick the
smallest available group id between values defined in redox_users.
-m, --create-home
......@@ -56,14 +57,14 @@ OPTIONS
This option is not enabled by default. This option must be specified
for a home directory to be created. If not set, the user's home dir is
set to "/".
set to "/"
-N, --no-user-group
Do not attempt to create the user's user group. Instead, the groupid
is set to 99 (should be the "nobody" group).
is set to 99 ("nobody"). -N and -g are mutually exclusive.
-s, --shell SHELL
The path to the user's default login shell. If left blank, the
The path to the user's default login shell. If not specified, the
default shell is set as "/bin/ion"
-u, --uid UID
......@@ -76,7 +77,6 @@ AUTHORS
"#; /* @MANEND */
const DEFAULT_SHELL: &'static str = "/bin/ion";
const DEFAULT_HOME: &'static str = "/home";
const DEFAULT_MODE: u16 = 0o700;
fn main() {
let mut stdout = io::stdout();
......@@ -106,11 +106,18 @@ fn main() {
};
let mut sys_users = AllUsers::new().unwrap_or_exit(1);
let mut sys_groups = AllGroups::new().unwrap_or_exit(1);
let mut sys_groups;
let uid = if parser.found("uid") {
match parser.get_opt("uid") {
Some(uid) => uid.parse::<usize>().unwrap_or_exit(1),
Some(uid) => {
let id = uid.parse::<usize>().unwrap_or_exit(1);
if let Some(_user) = sys_users.get_by_id(id) {
eprintln!("useradd: user already exists with uid: {}", id);
exit(1);
}
id
},
None => {
eprintln!("useradd: missing uid value");
exit(1);
......@@ -129,12 +136,29 @@ fn main() {
//This is a ridiculous mess and could use reworking
let gid: usize;
if parser.found("no-user-group") {
sys_groups = AllGroups::new().unwrap_or_exit(1);
gid = 99;
//TODO: Add this user to the "nobody" group
{
let nobody = sys_groups.get_mut_by_name("nobody").unwrap_or_else(|| {
eprintln!("useradd: group \"nobody\" not found");
exit(1)
});
nobody.users.push(String::from(login.as_str()));
}
sys_groups.save().unwrap_or_exit(1);
} else {
sys_groups = AllGroups::new().unwrap_or_exit(1);
if parser.found("gid") {
gid = match parser.get_opt("gid") {
Some(gid) => gid.parse::<usize>().unwrap_or_exit(1),
Some(gid) => {
let id = gid.parse::<usize>().unwrap_or_exit(1);
if let Some(_group) = sys_groups.get_by_id(id) {
eprintln!("useradd: group already exists with gid: {}", id);
exit(1);
}
id
},
None => {
eprintln!("useradd: missing gid argument");
exit(1);
......@@ -156,6 +180,7 @@ fn main() {
exit(1);
}
}
sys_groups.save().unwrap_or_exit(1);
}
let username = if parser.found("comment") {
......@@ -204,15 +229,16 @@ fn main() {
}
}
println!("Got info: {};{};{};{};{};{}", login, uid, gid, username, userhome, shell);
// Make sure to try and create the user before we create
// their home, that way we get a permissions error that makes
// more sense
sys_users.save().unwrap_or_exit(1);
sys_groups.save().unwrap_or_exit(1);
if parser.found("create-home") {
let fd = open(userhome, O_CREAT | O_DIRECTORY | O_CLOEXEC).unwrap_or_exit(1);
fchmod(fd, DEFAULT_MODE).unwrap_or_exit(1);
fchown(fd, uid as u32, gid as u32).unwrap_or_exit(1);
//Shouldn't ever error...
let user = sys_users.get_by_id(uid).unwrap_or_exit(1);
create_user_dir(user, userhome).unwrap_or_exit(1);
}
}
#[deny(warnings)]
extern crate arg_parser;
extern crate extra;
extern crate redox_users;
use std::env;
use std::io::{stdout, Write};
use std::fs::remove_dir;
use std::process::exit;
use arg_parser::ArgParser;
use extra::option::OptionalExt;
use redox_users::AllUsers;
const MAN_PAGE: &'static str = /* @MANSTART{userdel} */ r#"
NAME
userdel - modify system files to delete users
SYNOPSYS
userdel [ options ] LOGIN
userdel [ -h | --help ]
DESCRIPTION
userdel removes users from whatever backend is employed by
the system's redox_users.
It can also be used to manage removal of home directories.
This utility does not remove the user from any groups! This is
a planned feature and will be implemented at some point.
OPTIONS
-h, --help
Print this help page and exit.
-r, --remove
The user's home directory and all files inside will be
removed.
AUTHORS
Wesley Hershberger.
"#; /* @MANEND */
fn main() {
let mut stdout = stdout();
let mut parser = ArgParser::new(9)
.add_flag(&["h", "help"])
.add_flag(&["r", "remove"]);
parser.parse(env::args());
if parser.found("help") {
stdout.write_all(MAN_PAGE.as_bytes()).unwrap();
stdout.flush().unwrap();
exit(0);
}
let login = if parser.args.is_empty() {
eprintln!("userdel: no login specified");
exit(1);
} else {
&parser.args[0]
};
let mut sys_users = AllUsers::new().unwrap_or_exit(1);
{
let user = sys_users.get_by_name(login).unwrap_or_else(|| {
eprintln!("userdel: user does not exist: {}", login);
exit(1);
});
if parser.found("remove") {
remove_dir(&user.home).unwrap_or_exit(1);
}
}
sys_users.remove_by_name(login.to_string()).unwrap_or_exit(1);
sys_users.save().unwrap_or_exit(1);
}
#![deny(warnings)]
extern crate arg_parser;
extern crate extra;
extern crate redox_users;
extern crate userutils;
use std::env;
use std::io::{stdout, Write};
use std::fs::{remove_dir, rename};
use std::process::exit;
use arg_parser::ArgParser;
use extra::option::OptionalExt;
use redox_users::{AllGroups, AllUsers};
use userutils::create_user_dir;
const MAN_PAGE: &'static str = /* @MANSTART{usermod} */ r#"
NAME
usermod - modify user information
SYNOPSYS
usermod [ options ] LOGIN
usermod [ -h | --help ]
DESCRIPTION
The usermod utility can be used to modify user information.
This utility uses the redox_users API, so the backend is whatever
backend in use on the system for that API at the time.
See passwd for setting user passwords.
OPTIONS
-h, --help
Display this help and exit.
-c, --comment COMMENT
The comment field (or GECOS, historically) for the user. This
is typically the full name of the user, although sometimes it
includes an e-mail.
-d, --home-dir HOME_DIR
Sets the home directory to HOME_DIR and creates the directory.
See -m for move
-m, --move-home
Moves the the user's old home directory into the home directory
specified by --home-dir. Has no effect if passed without --home-dir
-G, --append-groups GROUP[,GROUP, ...]
Add this user to GROUP groups. This does not remove the user from
any group of which they are already a member.
-S, --set-groups GROUP[,GROUP, ...]
Remove the user from all groups of which they are a part and add
them to GROUP groups.
-g, --gid GID
Set the user's primary group id. If the group does not exist,
a warning is issued and no changes are applied.
-l, --login NEW_LOGIN
Set the new login name for the user. Must not be in use.
-s, --shell SHELL
Set the user's login shell as SHELL. This must be a full path.
-u, --uid UID
Set the user's user id. If another user's userid is the same as
UID, a warning is issued and no changes are applied. Note that
changing the value of the user's userid may have unexpected consequences.
AUTHORS
Written by Wesley Hershberger.
"#; /* @MANEND */
fn main() {
let mut stdout = stdout();
let mut parser = ArgParser::new(9)
.add_flag(&["h", "help"])
.add_flag(&["m", "move-home"])
.add_opt("c", "comment")
.add_opt("d", "home-dir")
.add_opt("G", "groups")
.add_opt("g", "gid")
.add_opt("l", "login")
.add_opt("s", "shell")
.add_opt("u", "uid");
parser.parse(env::args());
if parser.found("help") {
stdout.write_all(MAN_PAGE.as_bytes()).unwrap();
stdout.flush().unwrap();
exit(0);
}
let login = if parser.args.is_empty() {
eprintln!("usermod: no login specified");
exit(1);
} else {
&parser.args[0]
};
let mut sys_users = AllUsers::new().unwrap_or_exit(1);
let mut sys_groups;
if parser.found("groups") {
sys_groups = AllGroups::new().unwrap_or_exit(1);
let new_groups = parser.get_opt("groups").unwrap_or_else(|| {
eprintln!("usermod: no groups found");
exit(1);
});
let new_groups = new_groups.split(',');
for groupname in new_groups {
let group = sys_groups.get_mut_by_name(groupname).unwrap_or_else(|| {
eprintln!("usermod: no group found: {}", groupname);
exit(1);
});
group.users.push(String::from(login.as_str()));
}
sys_groups.save().unwrap_or_exit(1);
}
// Nasty to satisfy borrow checker. See line ~174 too
let uid = if let Some(uid) = parser.get_opt("uid") {
let uid = uid.parse::<usize>().unwrap_or_exit(1);
if let Some(_user) = sys_users.get_by_id(uid) {
eprintln!("usermod: userid already in use: {}", uid);
exit(1);
} else {
Some(uid)
}
} else if parser.found("uid") {
eprintln!("usermod: no uid found");
exit(1);
} else {
None
};
{
let user = sys_users.get_mut_by_name(&login).unwrap_or_else(|| {
eprintln!("usermod: user \"{}\" not found", login);
exit(1);
});
if let Some(gecos) = parser.get_opt("comment") {
user.name = gecos;
// If we found it but ^that^ was None, problem
} else if parser.found("comment") {