diff --git a/src/auth.rs b/src/auth.rs new file mode 100644 index 0000000..c973921 --- /dev/null +++ b/src/auth.rs @@ -0,0 +1,126 @@ +use nix::unistd::Group; +use crate::{persist, secure}; + + +pub struct Config { + pub identitys: Vec<(String, bool)>, +} + + +/// 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(Config)` - If the config was sucessfully parsed. +pub fn load_config(path: &str) -> Option { + let file = match std::fs::read_to_string(path) { + Err(e) => { + eprintln!("{}: {}", &path, e); + return None + }, + Ok(data) => data + }; + + let mut identitys = vec![]; + for (line_num, line) in file.split("\n").enumerate() { + let args: Vec<&str> = line.split(" ").collect(); + if line.starts_with("#") || line.trim() == "" { + continue; + } + if args.len() < 2 { + eprintln!("Error in config at line {}: Not enough arguments", line_num); + continue; + } + let identity: String = args[0].to_string(); + let persist: bool = match args[1].parse() { + Err(e) => { + eprintln!("Error in config at line {}: {}", line_num, e); + continue; + }, + Ok(data) => data + }; + identitys.push((identity, persist)); + } + Some(Config{identitys}) +} + + +/// 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 nix::unistd::getgroups() { + Ok(data) => data, + Err(_) => return vec![] + }; + let names = groups.iter() + .map(|gid| Group::from_gid(*gid)) + .flatten().flatten() + .map(|group| group.name) + .collect(); + names +} + + +/// Returns if the user is authorized given a sepcific config and user name. +/// #### Arguments +/// * `config` - A config struct contaning the authorization settings for crab +/// * `user` - The name of the user to check is authorized +/// #### Returns +/// * `None` - If the user is not authorized +/// * `Some(bool)` - If the user is authorized, returning the boolean if the +/// user is set to persist +pub fn authorize(config: &Config, user: &str) -> Option { + let groups = get_groups(); + for (identity, persist) in &config.identitys { + if identity.starts_with(":") { + let group = &identity[1..]; + if groups.contains(&group.to_string()) { + return Some(persist.clone()); + }; + } else if identity == user { + return Some(persist.clone()); + } + } + 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 +/// * `user` - The login name of the linux user +/// * `persist` - If the user's login should persist +/// #### Returns +/// * `true` - If the user authenticated sucessfully, or the user is persisted +/// * `false` - If the user failed to authenticate +pub fn authenticate(user: &str, persist: bool) -> bool { + if persist && persist::get_persist(user) { + secure::elevate_privilages(0, 0); + return true; + } + let input = match rpassword::prompt_password(format!("crab ({}) password: ", user)) { + 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(user.to_owned(), input); + if !auth.authenticate().is_ok() || !auth.open_session().is_ok() { + return false; + } + if persist { + persist::set_persist(user); + } + secure::elevate_privilages(0, 0); + return true; +} diff --git a/src/main.rs b/src/main.rs index 850d43b..ed89e76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ use std::env; use std::process::ExitCode; use pwd::Passwd; -use nix::unistd::{self, Uid, Group}; -mod persist; +mod auth; mod flags; +mod persist; mod secure; @@ -50,7 +50,7 @@ fn main() -> ExitCode { } // Load the command config from /etc - let config = match config("/etc/crab.conf") { + let config = match auth::load_config("/etc/crab.conf") { Some(data) => data, None => return ExitCode::from(ERROR_CONFIG) }; @@ -65,7 +65,7 @@ fn main() -> ExitCode { }; // check if the user is authorized - let persist = match allowed(&config, &user.name) { + let persist = match auth::authorize(&config, &user.name) { Some(data) => data && !flags.dont_persist, None => { eprintln!("Operation Not Permitted."); @@ -74,7 +74,7 @@ fn main() -> ExitCode { }; // authenticate the user - if !validate(&user.name, persist) { + if !auth::authenticate(&user.name, persist) { eprintln!("Authentication failed."); return ExitCode::from(ERROR_AUTH_FAILED); } @@ -101,137 +101,3 @@ Options: -d If your user is set to persist, dont save persistance"; println!("{}", help); } - - -struct Config { - identitys: Vec<(String, bool)>, -} - - -/// Validates the authorized user, and authenticates them 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 -/// * `user` - The login name of the linux user -/// * `persist` - If the user's login should persist -/// #### Returns -/// * `true` - If the user authenticated sucessfully, or the user is persisted -/// * `false` - If the user failed to authenticate -fn validate(user: &str, persist: bool) -> bool { - if persist && persist::get_persist(user) { - secure::elevate_privilages(0, 0); - return true; - } - let input = match rpassword::prompt_password(format!("crab ({}) password: ", user)) { - 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(user.to_owned(), input); - if !auth.authenticate().is_ok() || !auth.open_session().is_ok() { - return false; - } - secure::elevate_privilages(0, 0); - if persist { - persist::set_persist(user); - } - return true; -} - - -/// Returns if the user is authorized given a sepcific config and user name. -/// #### Arguments -/// * `config` - A config struct contaning the authorization settings for crab -/// * `user` - The name of the user to check is authorized -/// #### Returns -/// * `None` - If the user is not authorized -/// * `Some(bool)` - If the user is authorized, returning the boolean if the -/// user is set to persist -fn allowed(config: &Config, user: &str) -> Option { - // get user groups - let groups = get_groups(Uid::current()); - // check each config identiy - for (identity, persist) in &config.identitys { - // if it starts with a :, its a group - if identity.starts_with(":") { - let group = &identity[1..]; - if groups.contains(&group.to_string()) { - return Some(persist.clone()); - }; - // else its a user - } else if identity == user { - return Some(persist.clone()); - } - } - None -} - - -/// Returns a vector of group names of the groups a specific linux user is in -/// #### Arguments -/// * 'uid' - The user id od the linux user to get groups from -/// #### 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(uid: Uid) -> Vec { - let no_change = Uid::from_raw(u32::MAX); - if unistd::setresuid(no_change, uid, no_change).is_err() { - return vec![] - }; - let groups = match nix::unistd::getgroups() { - Ok(data) => data, - Err(_) => return vec![] - }; - let names = groups.iter() - .map(|gid| Group::from_gid(*gid)) - .flatten().flatten() - .map(|group| group.name) - .collect(); - names -} - - -/// 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(Config)` - If the config was sucessfully parsed. -fn config(path: &str) -> Option { - let file = match std::fs::read_to_string(path) { - Err(e) => { - eprintln!("{}: {}", &path, e); - return None - }, - Ok(data) => data - }; - - let mut identitys = vec![]; - for (line_num, line) in file.split("\n").enumerate() { - let args: Vec<&str> = line.split(" ").collect(); - if line.starts_with("#") || line.trim() == "" { - continue; - } - if args.len() < 2 { - eprintln!("Error in config at line {}: Not enough arguments", line_num); - continue; - } - let identity: String = args[0].to_string(); - let persist: bool = match args[1].parse() { - Err(e) => { - eprintln!("Error in config at line {}: {}", line_num, e); - continue; - }, - Ok(data) => data - }; - identitys.push((identity, persist)); - } - Some(Config{identitys}) -} diff --git a/src/persist.rs b/src/persist.rs index 0e0d1fd..1cddb7f 100644 --- a/src/persist.rs +++ b/src/persist.rs @@ -78,9 +78,7 @@ fn get_persist_config() -> Option { }; let data = match secure::read_file(PERSIST_PATH, &format!("{}", session)) { Some(data) => data, - None => { - "{}".to_string() - } + None => "{}".to_string() }; let json: Value = match serde_json::from_str(&data) { Ok(data) => data, diff --git a/src/secure.rs b/src/secure.rs index f463048..352e02b 100644 --- a/src/secure.rs +++ b/src/secure.rs @@ -11,11 +11,10 @@ use nix::unistd::{self, Uid, Gid}; /// A ``io::Result<()>`` if the write succeded or failed pub fn write_file(dir: &str, file: &str, data: &str) -> Result<(), io::Error> { fs::create_dir_all(dir)?; - set_file_permissions(0, 0, 0o600, dir)?; + set_file_permissions(0, 0, 0o100600, dir)?; let path = path(dir, file); - fs::write(&path, "")?; - set_file_permissions(0, 0, 0o600, &path)?; fs::write(&path, data)?; + set_file_permissions(0, 0, 0o100600, &path)?; Ok(()) }