diff --git a/Cargo.lock b/Cargo.lock index 82e2a58061..410787f082 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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)", diff --git a/Cargo.toml b/Cargo.toml index 3d48fc87f6..e3db1c22aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/bin/groupdel.rs b/src/bin/groupdel.rs new file mode 100644 index 0000000000..01bfcb27aa --- /dev/null +++ b/src/bin/groupdel.rs @@ -0,0 +1,64 @@ +#[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); +} diff --git a/src/bin/groupmod.rs b/src/bin/groupmod.rs new file mode 100644 index 0000000000..ad8590b0b1 --- /dev/null +++ b/src/bin/groupmod.rs @@ -0,0 +1,94 @@ +#[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::().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); +} diff --git a/src/bin/passwd.rs b/src/bin/passwd.rs index 58b311ad3f..3316fbd678 100644 --- a/src/bin/passwd.rs +++ b/src/bin/passwd.rs @@ -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); diff --git a/src/bin/useradd.rs b/src/bin/useradd.rs index f5a9f9d575..1a8301c829 100644 --- a/src/bin/useradd.rs +++ b/src/bin/useradd.rs @@ -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::().unwrap_or_exit(1), + Some(uid) => { + let id = uid.parse::().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::().unwrap_or_exit(1), + Some(gid) => { + let id = gid.parse::().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); } } diff --git a/src/bin/userdel.rs b/src/bin/userdel.rs new file mode 100644 index 0000000000..5d11b25ccd --- /dev/null +++ b/src/bin/userdel.rs @@ -0,0 +1,81 @@ +#[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); +} diff --git a/src/bin/usermod.rs b/src/bin/usermod.rs new file mode 100644 index 0000000000..061c2502ed --- /dev/null +++ b/src/bin/usermod.rs @@ -0,0 +1,206 @@ +#![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::().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") { + eprintln!("usermod: no comment found"); + exit(1); + } + + if let Some(new_login) = parser.get_opt("login") { + user.user = new_login; + } else if parser.found("login") { + eprintln!("usermod: no login found"); + exit(1); + } + + if let Some(shell) = parser.get_opt("shell") { + user.shell = shell; + } else if parser.found("shell") { + eprintln!("usermod: no shell found"); + exit(1); + } + + if let Some(home) = parser.get_opt("home-dir") { + if parser.found("move-home") { + rename(&user.home, &home).unwrap_or_exit(1); + } else { + create_user_dir(user, &home).unwrap_or_exit(1); + remove_dir(&user.home).unwrap_or_exit(1); + } + user.home = home; + } else if parser.found("home-dir") { + eprintln!("usermod: no home dir found"); + exit(1); + } + + if let Some(uid) = uid { + user.uid = uid; + } + + if let Some(gid) = parser.get_opt("gid") { + sys_groups = AllGroups::new().unwrap_or_exit(1); + let gid = gid.parse::().unwrap_or_exit(1); + + if let Some(_group) = sys_groups.get_by_id(gid) { + user.gid = gid; + } else { + eprintln!("usermod: no group found for id: {}", gid); + } + } else if parser.found("gid") { + eprintln!("usermod: no gid found"); + exit(1); + } + } + + sys_users.save().unwrap_or_exit(1); +} diff --git a/src/lib.rs b/src/lib.rs index 95323ebf64..c7a7dd86fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,10 +17,16 @@ //! - `whoami`: Display effective user ID. extern crate redox_users; +extern crate syscall; use std::io::Result; use redox_users::User; +use syscall::call::{open, fchmod, fchown}; +use syscall::error::Result as SysResult; +use syscall::flag::{O_CREAT, O_DIRECTORY, O_CLOEXEC}; + +const DEFAULT_MODE: u16 = 0o700; /// Spawns a shell for the given `User`. /// @@ -46,3 +52,13 @@ pub fn spawn_shell(user: &User) -> Result { None => Ok(1) } } + + +pub fn create_user_dir(user: &User, dir: T) -> SysResult<()> + where T: AsRef + std::convert::AsRef<[u8]> +{ + let fd = open(dir, O_CREAT | O_DIRECTORY | O_CLOEXEC)?; + fchmod(fd, DEFAULT_MODE)?; + fchown(fd, user.uid as u32, user.gid as u32)?; + Ok(()) +}