diff options
Diffstat (limited to 'src/auth.rs')
-rw-r--r-- | src/auth.rs | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/src/auth.rs b/src/auth.rs new file mode 100644 index 0000000..99a8216 --- /dev/null +++ b/src/auth.rs @@ -0,0 +1,264 @@ +use nix::unistd::{User, Group, Uid, Gid, self}; +use crate::persist; + + +pub struct Config { + pub permit: bool, + pub persist: bool, + pub nopass: bool, + pub user_uid: Option<Uid>, + pub user_gid: Option<Gid>, + pub privlaged_uid: Uid, +} + + +/// Returns a option containing the config ad the specific pile path. +/// This function will print out config syntax errors to the standard +/// output if it failes to parse certain lines. +/// #### Arguments +/// * `path` - The path to the config file +/// #### Returns +/// * `None` - If the config failed to load +/// * `Some(Vec<Config>)` - If the config was sucessfully parsed. +pub fn load_config_file(path: &str) -> Option<Vec<Config>> { + let file = match std::fs::read_to_string(path) { + Err(e) => { + eprintln!("{}: {}", &path, e); + return None + }, + Ok(data) => data + }; + + let mut configs = vec![]; + + for (line_num, line) in file.split("\n").enumerate() { + + let args: Vec<&str> = line.split(" ").collect(); + let len = args.len(); + + if line.starts_with("#") || line.trim() == "" { + continue; + } + + if len < 2 { + config_error(line_num, "Not enough arguments"); + continue; + } + + let permit = match args[0] { + "permit" => true, + "deny" => false, + _ => { + config_error(line_num, "The first argument must be `permit` or `deny"); + continue; + } + }; + + let (user_name, privlaged_name, name_index) = match args.iter().position(|&a| a == "as") { + Some(index) => { + if index != len - 2 { + config_error(line_num, "Target user not specified or to many arguments after `as`"); + continue; + } + (args[index-1].to_string(), args[index+1].to_string(), index-1) + }, + None => (args[len-1].to_string(), "root".to_string(), len-1) + }; + + let persist = args[1..name_index].contains(&"persist"); + + let nopass = args[1..name_index].contains(&"nopass"); + + + for &check in args[1..name_index].iter() { + match check { + "persist" => continue, + "nopass" => continue, + _ => { + config_error(line_num, &format!("Unexpected token `{}`", check)) + } + } + } + + + let (user_uid, user_gid) = + if user_name.starts_with(":") { + match get_gid_from_name(&user_name[1..]) { + Some(gid) => (None, Some(gid)), + None => { + config_error(line_num, &format!("Group `{}` does not exist", &user_name[1..])); + continue; + } + } + } else { + match get_uid_from_name(&user_name) { + Some(uid) => (Some(uid), None), + None => { + config_error(line_num, &format!("User `{}` does not exist", user_name)); + continue; + } + } + }; + + let privlaged_uid = match get_uid_from_name(&privlaged_name) { + Some(uid) => uid, + None => { + config_error(line_num, &format!("User `{}` does not exist", privlaged_name)); + continue; + } + }; + + configs.push(Config { + permit, + persist, + nopass, + user_uid, + user_gid, + privlaged_uid + }); + } + Some(configs) +} + + +/// Print a crab config error to the standard output +fn config_error(line_num: usize, message: &str) { + eprintln!("Error in config at line {}: {}", line_num, message); +} + + +/// Returns a Uid from a Users name +/// #### Arguments +/// * `name` - The name of the user +/// #### Returns +/// * `None` - If the user doesn't exist +/// * `Some(Gid)` - If the user exists +fn get_uid_from_name(name: &str) -> Option<Uid> { + return match User::from_name(name) { + Ok(result) => match result { + Some(data) => Some(data.uid), + None => None + }, + Err(_) => None + } +} + + +/// Returns a Uesrs name from a Uid +/// #### Arguments +/// * `uid` - The uid of the user +/// #### Returns +/// * `None` - If the user doesn't exist +/// * `Some(Gid)` - If the user exists +fn get_name_from_uid(uid: Uid) -> Option<String> { + return match User::from_uid(uid) { + Ok(result) => match result { + Some(data) => Some(data.name), + None => None + }, + Err(_) => None + } +} + + +/// Returns a Gid from a Groups name +/// #### Arguments +/// * `name` - The name of the group +/// #### Returns +/// * `None` - If the group doesn't exist +/// * `Some(Gid)` - If the group exists +fn get_gid_from_name(name: &str) -> Option<Gid> { + return match Group::from_name(name) { + Ok(result) => match result { + Some(data) => Some(data.gid), + None => None + }, + Err(_) => None + } +} + + +/// Returns a vector of group names of the groups the current effective uid is in +/// #### Returns +/// A vector of strings of the groups the user is in. If the vector is empty, +/// either the function coudn't retrieve the users groups, or the user is not in +/// any groups. +fn get_groups() -> Vec<Gid> { + let groups = match unistd::getgroups() { + Ok(data) => data, + Err(_) => return vec![] + }; + groups +} + + +/// Returns if the user is authorized given a sepcific config and user name. +/// #### Arguments +/// * `config` - A config struct contaning the authorization settings for crab +/// * `uid` - The uid to check is authorized +/// #### Returns +/// * `None` - If the uid is not authorized +/// * `Some(Config)` - If the uid is authorized, returns the specific index of the associated config +pub fn authorize(configs: &Vec<Config>, uid: Uid) -> Option<usize> { + let groups = get_groups(); + for (config_index, config) in configs.iter().enumerate() { + if config.user_gid.is_some() { + if groups.contains(&config.user_gid.unwrap()) { + if config.permit { + return Some(config_index) + } else { + return None + } + } + } else if config.user_uid.is_some() { + if config.user_uid.unwrap() == uid { + if config.permit { + return Some(config_index) + } else { + return None + } + } + } + } + None +} + + +/// Authenticates an authorized user for the current session. +/// If the user is already persisted, it will attempt to +/// read the persist file, and then skip authentication if +/// the is still persisted. +/// #### Arguments +/// * `config` - The config associated with the user to authenticate with +/// * `force_pass` - Force a password prompt even if persistance is set to true +/// * `uid` - The user uid that is authenticating +/// #### Returns +/// * `true` - If the user authenticated sucessfully +/// * `false` - If the user failed to authenticate +pub fn authenticate(config: &Config, force_pass: bool, uid: Uid) -> bool { + let name = match get_name_from_uid(uid) { + Some(data) => data, + None => return false + }; + if config.nopass || ( !force_pass && config.persist && persist::get_persist(&name) ) { + return true; + } + let input = match rpassword::prompt_password(format!("crab ({}) password: ", &name)) { + Ok(data) => data, + Err(_) => return false + }; + let mut auth = match pam::Authenticator::with_password("crab") { + Ok(data) => data, + Err(_) => return false + }; + auth.get_handler().set_credentials(&name, input); + if !auth.authenticate().is_ok() || !auth.open_session().is_ok() { + return false; + } + if !force_pass && config.persist { + persist::set_persist(&name); + } else if force_pass { + persist::remove_persist(&name); + } + return true; +} |