use std::env; use std::process::ExitCode; use pwd::Passwd; use nix::unistd::{self, Uid, Group}; mod persist; mod flags; mod secure; const ERROR_ARGS: u8 = 1; const ERROR_CONFIG: u8 = 2; const ERROR_NO_USER: u8 = 3; const ERROR_NOT_AUTHORIZED: u8 = 4; const ERROR_AUTH_FAILED: u8 = 5; const ERROR_RUN_ROOT: u8 = 6; fn main() -> ExitCode { // gets the arguments from the env let args: Vec = env::args().collect(); // pase the arguments into valid flags that are usuable by crab let flags = match flags::parse(&args[1..]) { Some(data) => data, // if there is an invalid flag, print the help message and exit None => { help(); return ExitCode::from(ERROR_ARGS); } }; // If the version arg flag is set, print the crab version if flags.version { println!("crab version 0.0.5"); return ExitCode::SUCCESS; } // If the help arg flag is set, print the crab help message if flags.help { help(); return ExitCode::SUCCESS; } // If the amount of acutal command arguments is less than two, a.k.a just `crab`, print the command usage if args.len() - flags.arg_count < 2 { println!("usage: crab [-d] command [args]"); return ExitCode::SUCCESS; } // Load the command config from /etc let config = match config("/etc/crab.conf") { Some(data) => data, None => return ExitCode::from(ERROR_CONFIG) }; // get the current user login let user = match Passwd::current_user() { Some(data) => data, None => { eprintln!("You dont exist."); return ExitCode::from(ERROR_NO_USER); } }; // check if the user is authorized let persist = match allowed(&config, &user.name) { Some(data) => data && !flags.dont_persist, None => { eprintln!("Operation Not Permitted."); return ExitCode::from(ERROR_NOT_AUTHORIZED); } }; // authenticate the user if !validate(&user.name, persist) { eprintln!("Authentication failed."); return ExitCode::from(ERROR_AUTH_FAILED); } // set the uid and gid of the process to root to run the command as root if !unistd::setuid(unistd::geteuid()).is_ok() || !unistd::setgid(unistd::getegid()).is_ok() { eprintln!("Failed to set root permissions"); return ExitCode::from(ERROR_RUN_ROOT); }; // execute the passed command let start = 1 + flags.arg_count; let err = exec::execvp(&args[start], &args[start..]); // print an error if an error was returned eprintln!("{}", err); ExitCode::SUCCESS } /// Prints the help message to the standard output fn help() { let help = "Usage: crab [-d] command [args] Options: -v --version Get the current version of the package -h --help Generates the crab help message -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) { 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); } 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.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}) }