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, pub user_gid: Option, 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)` - If the config was sucessfully parsed. pub fn load_config_file(path: &str) -> Option> { 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 { 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 { 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 { 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 { 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, uid: Uid) -> Option { 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; }