use std::fs; use std::{env, os::unix::prelude::PermissionsExt}; use std::process::ExitCode; use std::time::SystemTime; use pwd::Passwd; use nix::{unistd}; use serde_json::Value; extern crate time; fn main() -> ExitCode { let args: Vec = env::args().collect(); if args.len() < 2 { eprintln!("Invalid argument count."); return ExitCode::from(0); } let config = match config("/etc/crab.conf") { Some(data) => data, None => return ExitCode::from(1) }; let user = match Passwd::current_user() { Some(data) => data, None => { eprintln!("You dont exist."); return ExitCode::from(2); } }; let persist = match allowed(&config, &user.name) { Some(data) => data, None => { eprintln!("Operation Not Permitted. This incidence will be reported."); return ExitCode::from(3); } }; if !validate(&user.name, persist) { eprintln!("Authentication failed."); return ExitCode::from(4); } if !unistd::setuid(unistd::geteuid()).is_ok() || !unistd::setgid(unistd::getegid()).is_ok() { eprintln!("Failed to set root permissions"); return ExitCode::from(5); }; let err = exec::execvp(&args[1], &args[1..]); println!("Error: {}", err); ExitCode::from(0) } struct Config { users: Vec<(String, bool)> } fn validate(user: &str, persist: bool) -> bool { if persist && get_persist(user) { return true; } let input = rpassword::prompt_password(format!("crab ({}) password: ", user)).unwrap(); let mut auth = pam::Authenticator::with_password("crab").unwrap(); auth.get_handler().set_credentials(user.to_owned(), input); if !auth.authenticate().is_ok() || !auth.open_session().is_ok() { return false; } if persist { set_persist(user); } return true; } fn allowed(config: &Config, user: &str) -> Option { for (name, persist) in &config.users { if name == user { return Some(persist.clone()); } } None } 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 users = 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 user: 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 }; users.push((user, persist)); } Some(Config{users}) } fn get_terminal_process() -> Option { let id: i32 = match std::process::id().try_into() { Ok(data) => data, Err(_) => return None }; let stat = match procinfo::pid::stat(id) { Ok(data) => data, Err(_) => return None }; Some(stat.tty_nr) } fn get_terminal_config() -> Option { let id = match get_terminal_process() { Some(data) => data, None => return None }; let data = match std::fs::read_to_string(path(&id)) { Ok(data) => data, Err(_) => "{}".to_string() }; let json: Value = match serde_json::from_str(&data) { Ok(data) => data, Err(_) => return None }; Some(json) } fn write_terminal_config(id: &i32, data: &str) -> Result<(), Box> { std::fs::write(path(&id), data)?; unistd::chown(std::path::Path::new(&path(&id)), Some(unistd::Uid::from(0)), Some(unistd::Gid::from(0)))?; let metadata = std::fs::metadata(path(&id))?; let mut perms = metadata.permissions(); perms.set_mode(0o0660); fs::set_permissions(path(&id), perms)?; Ok(()) } fn get_persist(user: &str) -> bool { let json = match get_terminal_config() { Some(data) => data, None => return false }; let timestamp = match json[user].as_u64() { Some(data) => data, None => return false }; return now() - timestamp < 60 * 3; } fn set_persist(user: &str) { let mut json = match get_terminal_config() { Some(data) => data, None => return }; json[user] = Value::from(now()); let id = match get_terminal_process() { Some(data) => data, None => return }; match write_terminal_config(&id, &json.to_string()) { Ok(_) => {}, Err(e) => { eprintln!("Internal Error: {}", e) } }; } fn now() -> u64 { return SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(); } fn path(id: &i32) -> String { return format!("/tmp/crab-{}", id); }