use clap::{Arg, Command};
use color_eyre::Result;
use crate::userdb::{add_user_to_group, ensure_group};
use crate::userdb::{group_exists, user_exists};
use crate::userdb;
use crate::busybox::useradd::UserAddOptions;
#[derive(Debug, Clone, PartialEq, Eq)]
enum AddUserMode {
CreateUser,
AddToGroup,
}
#[derive(Debug, Clone)]
pub struct AddUserCmd {
pub options: UserAddOptions,
pub target_group: Option<String>,
mode: AddUserMode,
}
pub fn parse_options(matches: &clap::ArgMatches) -> Result<AddUserCmd> {
let system = matches.get_flag("system");
let disabled_password = matches.get_flag("disabled_password");
let _no_create_home = matches.get_flag("no_create_home");
let home = matches.get_one::<String>("home").cloned();
let shell = matches.get_one::<String>("shell").cloned();
let gecos = matches.get_one::<String>("gecos").cloned();
let uid = matches.get_one::<String>("uid").cloned();
let primary_group = matches.get_one::<String>("ingroup").cloned();
let group_flag = matches.get_flag("group");
let positionals: Vec<String> = matches
.get_many::<String>("args")
.map(|vals| vals.cloned().collect())
.unwrap_or_default();
if positionals.is_empty() {
return Ok(AddUserCmd {
options: UserAddOptions::default(),
target_group: None,
mode: AddUserMode::CreateUser,
});
}
let (username, target_group, mode) = parse_positionals(positionals, system, group_flag);
let options = UserAddOptions {
system,
home,
shell,
gecos,
primary_group,
lock_password: disabled_password,
uid,
username,
..Default::default()
};
Ok(AddUserCmd {
options,
target_group,
mode,
})
}
fn parse_positionals(
mut positionals: Vec<String>,
system: bool,
group_flag: bool,
) -> (String, Option<String>, AddUserMode) {
let username = positionals.remove(0);
let target_group = if !positionals.is_empty() {
Some(positionals.remove(0))
} else {
None
};
let mode = if target_group.is_some() && !system && !group_flag {
AddUserMode::AddToGroup
} else {
AddUserMode::CreateUser
};
(username, target_group, mode)
}
pub fn command() -> Command {
let mut cmd = Command::new("adduser")
.about("Debian compatible adduser (subset)")
.disable_help_flag(true);
for arg in vec![
Arg::new("system")
.short('S')
.long("system")
.help("Create a system account")
.action(clap::ArgAction::SetTrue),
Arg::new("disabled_password")
.short('D')
.long("disabled-password")
.help("Lock the account (no password)")
.action(clap::ArgAction::SetTrue),
Arg::new("quiet")
.long("quiet")
.help("Reduce output (ignored)")
.action(clap::ArgAction::SetTrue),
Arg::new("home")
.short('h')
.long("home")
.value_name("DIR")
.help("Home directory"),
Arg::new("no_create_home")
.short('H')
.long("no-create-home")
.help("Do not create home directory")
.action(clap::ArgAction::SetTrue),
Arg::new("shell")
.short('s')
.long("shell")
.value_name("SHELL")
.help("Login shell"),
Arg::new("gecos")
.short('g')
.long("gecos")
.visible_alias("comment")
.value_name("GECOS")
.help("GECOS/comment"),
Arg::new("group")
.long("group")
.help("Create group with same name as user")
.action(clap::ArgAction::SetTrue),
Arg::new("ingroup")
.short('G')
.long("ingroup")
.value_name("GROUP")
.help("Primary group"),
Arg::new("uid")
.short('u')
.long("uid")
.value_name("UID")
.help("User id"),
Arg::new("allow_bad_names")
.long("allow-bad-names")
.visible_alias("force-badname")
.visible_alias("allow-badname")
.help("Relax name checks (ignored, we are permissive)")
.action(clap::ArgAction::SetTrue),
Arg::new("stdout_msg_level")
.long("stdoutmsglevel")
.value_name("LEVEL")
.help("Message level (ignored)")
.num_args(0..=1),
] {
cmd = cmd.arg(arg);
}
cmd.arg(Arg::new("args").value_name("ARGS").num_args(1..=2).help("user [group]"))
.arg(Arg::new("help").long("help").action(clap::ArgAction::Help))
}
pub fn run(cmd: AddUserCmd) -> Result<()> {
match cmd.mode {
AddUserMode::AddToGroup => {
let user = &cmd.options.username;
let group = cmd
.target_group
.as_ref()
.expect("group must be present in AddToGroup mode");
if !user_exists(user, None)? {
return Err(color_eyre::eyre::eyre!("The user `{}' does not exist.", user));
}
if !group_exists(group, None)? {
return Err(color_eyre::eyre::eyre!("The group `{}' does not exist.", group));
}
let groups = userdb::read_group(None)?;
if let Some(g) = groups.iter().find(|g| g.name == *group) {
if g.members.iter().any(|m| m == user) {
return Ok(());
}
}
add_user_to_group(user, group, None)?;
}
AddUserMode::CreateUser => {
let mut opts = cmd.options;
if let Some(ref g) = opts.primary_group {
if !group_exists(g, None)? {
ensure_group(g, None, opts.system, None)?;
}
}
if opts.home.is_none() && opts.system {
opts.home = Some("/nonexistent".to_string());
}
crate::busybox::useradd::run(opts)?;
}
}
Ok(())
}