From f7a13253e72f1dde7389fb98c090b753fdfc42b9 Mon Sep 17 00:00:00 2001 From: tylermurphy534 Date: Thu, 10 Nov 2022 11:28:10 -0500 Subject: [PATCH 01/10] slight refactor --- src/help.rs | 10 ---------- src/main.rs | 16 +++++++++++++--- src/persist.rs | 12 ++++++------ src/secure.rs | 36 +++++++++++++++--------------------- 4 files changed, 34 insertions(+), 40 deletions(-) delete mode 100644 src/help.rs diff --git a/src/help.rs b/src/help.rs deleted file mode 100644 index e6f4a72..0000000 --- a/src/help.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub 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); -} diff --git a/src/main.rs b/src/main.rs index 28f08cd..51ca0f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,6 @@ const ERROR_RUN_ROOT: u8 = 6; mod persist; mod flags; -mod help; mod secure; fn main() -> ExitCode { @@ -22,7 +21,7 @@ fn main() -> ExitCode { let flags = match flags::parse(&args[1..]) { Some(data) => data, None => { - help::help(); + help(); return ExitCode::from(ERROR_ARGS); } }; @@ -31,7 +30,7 @@ fn main() -> ExitCode { return ExitCode::SUCCESS; } if flags.help { - help::help(); + help(); return ExitCode::SUCCESS; } if args.len() - flags.arg_count < 2 { @@ -74,6 +73,17 @@ fn main() -> ExitCode { ExitCode::SUCCESS } +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 { users: Vec<(String, bool)> } diff --git a/src/persist.rs b/src/persist.rs index 6a813dc..667b12a 100644 --- a/src/persist.rs +++ b/src/persist.rs @@ -21,7 +21,7 @@ pub fn get_persist(user: &str) -> bool { pub fn set_persist(user: &str) { let mut json = match get_terminal_config() { Some(data) => data, - None => return + None => serde_json::from_str("{}").unwrap() }; json[user] = Value::from(now()); let id = match get_terminal_process() { @@ -30,22 +30,22 @@ pub fn set_persist(user: &str) { }; match secure::write_file(PERSIST_PATH, &format!("{}", id), &json.to_string()) { Ok(_) => {}, - Err(e) => { - eprintln!("Internal Error: {}", e) + Err(_) => { + eprintln!("crab: An Internal Has Error") } }; } fn get_terminal_process() -> Option { - let id: i32 = match std::process::id().try_into() { + let pid: i32 = match std::process::id().try_into() { Ok(data) => data, Err(_) => return None }; - let stat = match procinfo::pid::stat(id) { + let pid_stat = match procinfo::pid::stat(pid) { Ok(data) => data, Err(_) => return None }; - Some(stat.session) + Some(pid_stat.session) } fn get_terminal_config() -> Option { diff --git a/src/secure.rs b/src/secure.rs index 1fc3a11..5fb0cc8 100644 --- a/src/secure.rs +++ b/src/secure.rs @@ -1,46 +1,40 @@ -use std::{os::{unix::prelude::PermissionsExt, linux::fs::MetadataExt}, fs, io::ErrorKind}; +use std::{os::{unix::prelude::PermissionsExt, linux::fs::MetadataExt}, fs, io}; use nix::unistd; -pub fn write_file(dir: &str, file: &str, data: &str) -> Result<(), Box> { - std::fs::create_dir_all(dir)?; - make_file_root(dir)?; +pub fn write_file(dir: &str, file: &str, data: &str) -> Result<(), io::Error> { + fs::create_dir_all(dir)?; + set_file_permissions(0, 0, 0o100600, dir)?; let path = path(dir, file); - std::fs::write(&path, "")?; - make_file_root(&path)?; - std::fs::write(&path, data)?; + fs::write(&path, "")?; + set_file_permissions(0, 0, 0o100600, &path)?; + fs::write(&path, data)?; Ok(()) } pub fn read_file(dir: &str, file: &str) -> Option { let path = path(dir,file); - if !is_file_root(&path) { + if !check_file_permissions(0, 0, 0o100600, &path) { return None; } - match std::fs::read_to_string(&path) { + match fs::read_to_string(&path) { Ok(data) => return Some(data), Err(_) => return None }; } -fn make_file_root(path: &str) -> Result<(), Box> { - unistd::chown(std::path::Path::new(path), Some(unistd::Uid::from(0)), Some(unistd::Gid::from(0)))?; - let metadata = std::fs::metadata(path)?; +fn set_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> Result<(), io::Error> { + unistd::chown(std::path::Path::new(path), Some(unistd::Uid::from(uid)), Some(unistd::Gid::from(gid)))?; + let metadata = fs::metadata(path)?; let mut perms = metadata.permissions(); - perms.set_mode(0o100600); + perms.set_mode(mode); fs::set_permissions(path, perms)?; Ok(()) } -fn is_file_root(path: &str) -> bool { - return check_file_permissions(0, 0, 0o100600, path); -} - fn check_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> bool { - let metadata = match std::fs::metadata(path) { + let metadata = match fs::metadata(path) { Ok(data) => data, - Err(e) => { - return e.kind() == ErrorKind::NotFound - } + Err(_) => return false }; let perms = metadata.permissions(); return perms.mode() == mode && metadata.st_uid() == uid && metadata.st_gid() == gid; From 83d045a58cd67286ae9d9fa2bee9103fa0cc931a Mon Sep 17 00:00:00 2001 From: Tyler Murphy Date: Thu, 10 Nov 2022 14:17:16 -0500 Subject: [PATCH 02/10] documentation and group support --- src/flags.rs | 32 ++++++++++++-- src/main.rs | 115 +++++++++++++++++++++++++++++++++++++++++++------ src/persist.rs | 43 ++++++++++++++---- src/secure.rs | 38 ++++++++++++++++ 4 files changed, 204 insertions(+), 24 deletions(-) diff --git a/src/flags.rs b/src/flags.rs index 9432b8f..c93f3ef 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -1,3 +1,9 @@ + +const HELP_FLAG: &str = "help h"; +const VERSION_FLAG: &str = "version v"; +const DONT_PERSIST: &str = "d"; + + pub struct Flags { pub help: bool, pub version: bool, @@ -5,6 +11,13 @@ pub struct Flags { pub arg_count: usize } + +/// Parses an list of String arguments into a set of flags that can be used by crab +/// #### Arguments +/// * `args` - The list of String arguments +/// #### Returns +/// * `None` - If there is an invalid argument in the list +/// * `Some(Flags)` - If the arguments were secussfully parsed, returning the flags pub fn parse(args: &[String]) -> Option { let mut flags = Flags { help: false, @@ -34,14 +47,20 @@ pub fn parse(args: &[String]) -> Option { Some(flags) } + +/// Checks if a given string is a given argument fn is_arg(arg: &str) -> bool { return arg.starts_with("-"); } -const HELP_FLAG: &str = "help h"; -const VERSION_FLAG: &str = "version v"; -const DONT_PERSIST: &str = "d"; +/// Sets a flag in a `Flags` struct +/// #### Arguments +/// * `arg` - The argument to check +/// * `flags` - The `Flags` stuct to update +/// #### Returns +/// * `true` - If the argument passed is a valid flag +/// * `false` - If the argument passed is not a valid flag fn set_flag(arg: &str, flags: &mut Flags) -> bool { if has_flag_set(&arg, HELP_FLAG) { flags.help = true; @@ -56,6 +75,13 @@ fn set_flag(arg: &str, flags: &mut Flags) -> bool { false } + +/// Returns if a given argument is a certain flag +/// #### Arguments +/// * `arg` - The arch to check +/// #### Returns +/// * `true` - If the argument matches the flag +/// * `false` - If the argument doesn't match the flag fn has_flag_set(arg: &str, check: &str) -> bool { for check_arg in check.split(" ") { if check_arg == arg { diff --git a/src/main.rs b/src/main.rs index 51ca0f2..c6e8201 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,13 @@ use std::env; use std::process::ExitCode; use pwd::Passwd; -use nix::unistd; +use nix::unistd::{self, Uid, Group}; + + +mod persist; +mod flags; +mod secure; -extern crate time; const ERROR_ARGS: u8 = 1; const ERROR_CONFIG: u8 = 2; @@ -12,35 +16,47 @@ const ERROR_NOT_AUTHORIZED: u8 = 4; const ERROR_AUTH_FAILED: u8 = 5; const ERROR_RUN_ROOT: u8 = 6; -mod persist; -mod flags; -mod secure; 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 => { @@ -48,6 +64,8 @@ fn main() -> ExitCode { 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 => { @@ -56,23 +74,31 @@ fn main() -> ExitCode { } }; + + // 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: @@ -84,10 +110,22 @@ Options: println!("{}", help); } + struct Config { - users: Vec<(String, bool)> + 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; @@ -110,15 +148,68 @@ fn validate(user: &str, persist: bool) -> bool { 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 { - for (name, persist) in &config.users { - if name == user { + // 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) => { @@ -128,7 +219,7 @@ fn config(path: &str) -> Option { Ok(data) => data }; - let mut users = vec![]; + let mut identitys = vec![]; for (line_num, line) in file.split("\n").enumerate() { let args: Vec<&str> = line.split(" ").collect(); if line.trim() == "" { @@ -138,7 +229,7 @@ fn config(path: &str) -> Option { eprintln!("Error in config at line {}: Not enough arguments", line_num); continue; } - let user: String = args[0].to_string(); + 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); @@ -146,7 +237,7 @@ fn config(path: &str) -> Option { }, Ok(data) => data }; - users.push((user, persist)); + identitys.push((identity, persist)); } - Some(Config{users}) + Some(Config{identitys}) } diff --git a/src/persist.rs b/src/persist.rs index 667b12a..fe4ce69 100644 --- a/src/persist.rs +++ b/src/persist.rs @@ -1,13 +1,22 @@ use std::time::SystemTime; use serde_json::Value; - use crate::secure; + const PERSIST_TIME: u64 = 60 * 3; const PERSIST_PATH: &str = "/var/run/crab"; + +/// Returns true or false if a user is currently persisted from +/// a prior authentication. If the persist file had been tampered +/// with, or not trusted, it will be ignored, and return false. +/// #### Arguments +/// * `user` - The user to check if is persisted +/// #### Returns +/// * `true` - If the user is persisted +/// * `false` - If the user is not persisted, or if the persist file is not trusted pub fn get_persist(user: &str) -> bool { - let json = match get_terminal_config() { + let json = match get_persist_config() { Some(data) => data, None => return false }; @@ -18,17 +27,21 @@ pub fn get_persist(user: &str) -> bool { return now() - timestamp < PERSIST_TIME && timestamp - 1 < now(); } + +/// Updates the current sessions persist file +/// #### Arguments +/// * `user` - The user to set persisted pub fn set_persist(user: &str) { - let mut json = match get_terminal_config() { + let mut json = match get_persist_config() { Some(data) => data, None => serde_json::from_str("{}").unwrap() }; json[user] = Value::from(now()); - let id = match get_terminal_process() { + let session = match get_current_session() { Some(data) => data, None => return }; - match secure::write_file(PERSIST_PATH, &format!("{}", id), &json.to_string()) { + match secure::write_file(PERSIST_PATH, &format!("{}", session), &json.to_string()) { Ok(_) => {}, Err(_) => { eprintln!("crab: An Internal Has Error") @@ -36,7 +49,12 @@ pub fn set_persist(user: &str) { }; } -fn get_terminal_process() -> Option { + +/// Gets the current session that crab is running in +/// #### Returns +/// * `None` - If crab failed to get the current session +/// * `Some(i32)` - If the session is retrieved, returns the i32/pid_t session id +fn get_current_session() -> Option { let pid: i32 = match std::process::id().try_into() { Ok(data) => data, Err(_) => return None @@ -48,12 +66,17 @@ fn get_terminal_process() -> Option { Some(pid_stat.session) } -fn get_terminal_config() -> Option { - let id = match get_terminal_process() { + +/// Gets the current persist file for the current session +/// #### Returns +/// * `None` - If the persist file is untrusted or doesnt exist +/// * `Some(Value)` - If the persist file is retrieved, returns the serde_json Value of the file +fn get_persist_config() -> Option { + let session = match get_current_session() { Some(data) => data, None => return None }; - let data = match secure::read_file(PERSIST_PATH, &format!("{}", id)) { + let data = match secure::read_file(PERSIST_PATH, &format!("{}", session)) { Some(data) => data, None => "{}".to_string() }; @@ -64,6 +87,8 @@ fn get_terminal_config() -> Option { Some(json) } + +// Gets the current time in seconds since the Unix Epoch fn now() -> u64 { return SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(); } diff --git a/src/secure.rs b/src/secure.rs index 5fb0cc8..39339a0 100644 --- a/src/secure.rs +++ b/src/secure.rs @@ -1,6 +1,14 @@ use std::{os::{unix::prelude::PermissionsExt, linux::fs::MetadataExt}, fs, io}; use nix::unistd; + +/// Writes a file securly to a specified path with given data +/// #### Arguments +/// * `dir` - The directory of the secure folder +/// * `file` - The file name to write +/// * `data` - The data to write +/// #### Returns +/// 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, 0o100600, dir)?; @@ -11,6 +19,16 @@ pub fn write_file(dir: &str, file: &str, data: &str) -> Result<(), io::Error> { Ok(()) } + +/// Reads the file secutly to a specified path. If the file has +/// been tampered (permissions have been changed), it ignores the +/// file. +/// #### Arguments +/// * `dir` - The directory of the secure folder +/// * `file` - The file name to write +/// #### Returns +/// * `None` - If the files doesnt exist or isnt trusted +/// * `Some(String) - If the file is trusted, it returns the file's contents pub fn read_file(dir: &str, file: &str) -> Option { let path = path(dir,file); if !check_file_permissions(0, 0, 0o100600, &path) { @@ -22,6 +40,15 @@ pub fn read_file(dir: &str, file: &str) -> Option { }; } + +/// Sets the permission for a secure file +/// #### Arguments +/// * `uid` - The user to own the file +/// * `gid` - The group to own the file +/// * `mode` - The mode permissions of the file +/// * `path` - The path of the secure file +/// #### Returns +/// A ``io::Result<()>`` if the write succeded or failed fn set_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> Result<(), io::Error> { unistd::chown(std::path::Path::new(path), Some(unistd::Uid::from(uid)), Some(unistd::Gid::from(gid)))?; let metadata = fs::metadata(path)?; @@ -31,6 +58,15 @@ fn set_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> Result<(), Ok(()) } + +/// Checks if the files permissions equals the given parameters +/// #### Arguments +/// * `uid` - The user to own the file +/// * `gid` - The group to own the file +/// * `mode` - The mode permissions of the file +/// * `path` - The path of the secure file +/// #### Returns +/// True or false if the files permissions match fn check_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> bool { let metadata = match fs::metadata(path) { Ok(data) => data, @@ -40,6 +76,8 @@ fn check_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> bool { return perms.mode() == mode && metadata.st_uid() == uid && metadata.st_gid() == gid; } + +/// Get the path of a file given a directory and file name fn path(dir: &str, file: &str) -> String { return format!("{}/{}", dir, file); } \ No newline at end of file From b457c08923f1ef8d88005cf9781d1b4d5dd9552e Mon Sep 17 00:00:00 2001 From: Tyler Murphy Date: Thu, 10 Nov 2022 16:22:29 -0500 Subject: [PATCH 03/10] move root priv fn, slight refactor --- src/main.rs | 12 +++--------- src/persist.rs | 10 ++++++---- src/secure.rs | 25 +++++++++++++++++++------ 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index c6e8201..850d43b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,6 @@ 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 { @@ -74,19 +73,12 @@ fn main() -> ExitCode { } }; - // 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..]); @@ -128,6 +120,7 @@ struct Config { /// * `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)) { @@ -142,6 +135,7 @@ fn validate(user: &str, persist: bool) -> bool { if !auth.authenticate().is_ok() || !auth.open_session().is_ok() { return false; } + secure::elevate_privilages(0, 0); if persist { persist::set_persist(user); } @@ -222,7 +216,7 @@ fn config(path: &str) -> Option { let mut identitys = vec![]; for (line_num, line) in file.split("\n").enumerate() { let args: Vec<&str> = line.split(" ").collect(); - if line.trim() == "" { + if line.starts_with("#") || line.trim() == "" { continue; } if args.len() < 2 { diff --git a/src/persist.rs b/src/persist.rs index fe4ce69..0e0d1fd 100644 --- a/src/persist.rs +++ b/src/persist.rs @@ -34,7 +34,7 @@ pub fn get_persist(user: &str) -> bool { pub fn set_persist(user: &str) { let mut json = match get_persist_config() { Some(data) => data, - None => serde_json::from_str("{}").unwrap() + None => return }; json[user] = Value::from(now()); let session = match get_current_session() { @@ -43,8 +43,8 @@ pub fn set_persist(user: &str) { }; match secure::write_file(PERSIST_PATH, &format!("{}", session), &json.to_string()) { Ok(_) => {}, - Err(_) => { - eprintln!("crab: An Internal Has Error") + Err(e) => { + eprintln!("crab: An Internal Has Error: {}", e); } }; } @@ -78,7 +78,9 @@ 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 39339a0..f463048 100644 --- a/src/secure.rs +++ b/src/secure.rs @@ -1,5 +1,5 @@ -use std::{os::{unix::prelude::PermissionsExt, linux::fs::MetadataExt}, fs, io}; -use nix::unistd; +use std::{os::{unix::prelude::PermissionsExt, linux::fs::MetadataExt}, fs, io::{self, ErrorKind}}; +use nix::unistd::{self, Uid, Gid}; /// Writes a file securly to a specified path with given data @@ -11,10 +11,10 @@ use nix::unistd; /// 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, 0o100600, dir)?; + set_file_permissions(0, 0, 0o600, dir)?; let path = path(dir, file); fs::write(&path, "")?; - set_file_permissions(0, 0, 0o100600, &path)?; + set_file_permissions(0, 0, 0o600, &path)?; fs::write(&path, data)?; Ok(()) } @@ -41,6 +41,17 @@ pub fn read_file(dir: &str, file: &str) -> Option { } +pub fn elevate_privilages(uid: u32, gid: u32) -> bool { + if unistd::setuid(Uid::from(uid)).is_err() { + return false; + } + if unistd::setgid(Gid::from(gid)).is_err() { + return false; + } + true +} + + /// Sets the permission for a secure file /// #### Arguments /// * `uid` - The user to own the file @@ -70,7 +81,9 @@ fn set_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> Result<(), fn check_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> bool { let metadata = match fs::metadata(path) { Ok(data) => data, - Err(_) => return false + Err(e) => { + return e.kind() == ErrorKind::NotFound; + } }; let perms = metadata.permissions(); return perms.mode() == mode && metadata.st_uid() == uid && metadata.st_gid() == gid; @@ -79,5 +92,5 @@ fn check_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> bool { /// Get the path of a file given a directory and file name fn path(dir: &str, file: &str) -> String { - return format!("{}/{}", dir, file); + return format!("{}/{}.persist", dir, file); } \ No newline at end of file From 9bb5154ae8c09616b1a9a1caa29325f8dd3b6ebd Mon Sep 17 00:00:00 2001 From: tylermurphy534 Date: Thu, 10 Nov 2022 19:19:20 -0500 Subject: [PATCH 04/10] finalize adding groups --- src/auth.rs | 126 +++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 144 ++----------------------------------------------- src/persist.rs | 4 +- src/secure.rs | 5 +- 4 files changed, 134 insertions(+), 145 deletions(-) create mode 100644 src/auth.rs 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(()) } From f528a215f5effb748c04ee7f646f3ac0b34a8d29 Mon Sep 17 00:00:00 2001 From: tylermurphy534 Date: Thu, 10 Nov 2022 19:23:21 -0500 Subject: [PATCH 05/10] add doc string for elevate_privlages --- src/secure.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/secure.rs b/src/secure.rs index 352e02b..13d67b0 100644 --- a/src/secure.rs +++ b/src/secure.rs @@ -40,6 +40,12 @@ pub fn read_file(dir: &str, file: &str) -> Option { } +/// Ekevate tge oruvukages if the current process +/// #### Arguments +/// * `uid` - The uid to set the process to +/// * `gid` - The gid to set the process to +/// #### Returns +/// If the process failes to elevate, it returns false pub fn elevate_privilages(uid: u32, gid: u32) -> bool { if unistd::setuid(Uid::from(uid)).is_err() { return false; @@ -92,4 +98,4 @@ fn check_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> bool { /// Get the path of a file given a directory and file name fn path(dir: &str, file: &str) -> String { return format!("{}/{}.persist", dir, file); -} \ No newline at end of file +} From 58208a126858d14e4d4bf4707e298919d234bc22 Mon Sep 17 00:00:00 2001 From: tylermurphy534 Date: Fri, 11 Nov 2022 01:25:10 -0500 Subject: [PATCH 06/10] update config format --- Cargo.lock | 68 +--------- Cargo.toml | 3 +- conf | 6 +- deployments/aur/.SRCINFO | 2 +- deployments/aur/PKGBUILD | 8 +- install.sh | 8 +- src/auth.rs | 277 +++++++++++++++++++++++++++------------ src/flags.rs | 88 ++++++------- src/main.rs | 24 +--- src/persist.rs | 94 ++++++------- src/secure.rs | 78 ++++++----- uninstall.sh | 9 +- 12 files changed, 348 insertions(+), 317 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ba42db..054acb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,13 +34,12 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "crab" -version = "0.0.5" +version = "0.0.6" dependencies = [ "exec", "nix", "pam", "procinfo", - "pwd", "rpassword", "serde_json", "time", @@ -144,15 +143,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "proc-macro2" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" -dependencies = [ - "unicode-ident", -] - [[package]] name = "procinfo" version = "0.4.2" @@ -165,25 +155,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "pwd" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c71c0c79b9701efe4e1e4b563b2016dd4ee789eb99badcb09d61ac4b92e4a2" -dependencies = [ - "libc", - "thiserror", -] - -[[package]] -name = "quote" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" -dependencies = [ - "proc-macro2", -] - [[package]] name = "rpassword" version = "7.1.0" @@ -241,37 +212,6 @@ dependencies = [ "serde", ] -[[package]] -name = "syn" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "time" version = "0.3.17" @@ -288,12 +228,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" -[[package]] -name = "unicode-ident" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" - [[package]] name = "users" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 0408f28..e9b9a13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,10 @@ [package] name = "crab" -version = "0.0.5" +version = "0.0.6" edition = "2021" [dependencies] pam = "0.7.0" -pwd = "1.4.0" nix = "0.25.0" exec = "0.3.1" rpassword = "7.1.0" diff --git a/conf b/conf index bebb505..0da97e5 100644 --- a/conf +++ b/conf @@ -1 +1,5 @@ -root true \ No newline at end of file +permit nopass linus as root +deny :docker +#deny jane +permit persist :wheel +permit jane as doe \ No newline at end of file diff --git a/deployments/aur/.SRCINFO b/deployments/aur/.SRCINFO index 66c6ce5..c145393 100644 --- a/deployments/aur/.SRCINFO +++ b/deployments/aur/.SRCINFO @@ -1,6 +1,6 @@ pkgbase = crab pkgdesc = A rusty permission authentication system - pkgver = 0.0.5 + pkgver = 0.0.6 pkgrel = 2 url = https://g.tylerm.dev/tylermurphy534/crab.git arch = x86_64 diff --git a/deployments/aur/PKGBUILD b/deployments/aur/PKGBUILD index b49ce75..6196e48 100644 --- a/deployments/aur/PKGBUILD +++ b/deployments/aur/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Tyler Murphy pkgname=crab -pkgver=0.0.5 +pkgver=0.0.6 pkgrel=2 pkgdesc="A rusty permission authentication system" arch=('x86_64' 'i686') @@ -18,7 +18,7 @@ build() { package() { cd crab - install -D --mode=6755 --owner=root --group=root ./target/release/crab ${pkgdir}/usr/bin/crab - install -D --mode=660 --owner=root --group=root pam ${pkgdir}/etc/pam.d/crab - install -D --mode=660 --owner=root --group=root conf ${pkgdir}/usr/share/crab/crab.conf + install -D --mode=4755 --owner=root --group=root ./target/release/crab ${pkgdir}/usr/bin/crab + install -D --mode=600 --owner=root --group=root pam ${pkgdir}/etc/pam.d/crab + install -D --mode=600 --owner=root --group=root conf ${pkgdir}/usr/share/crab/crab.conf } diff --git a/install.sh b/install.sh index 716d9b1..6c7b019 100755 --- a/install.sh +++ b/install.sh @@ -8,10 +8,14 @@ fi # Copy executable to bin cp ./target/release/crab /usr/bin/crab chown root:root /bin/crab -chmod 6755 /bin/crab +chmod 4755 /bin/crab # Set up config files cp pam /etc/pam.d/crab +chmod 600 /etc/pam.d/crab + mkdir /usr/share/crab +chmod 600 /usr/share/crab + cp conf /usr/share/crab/crab.conf -chmod 660 /usr/share/crab/crab.conf +chmod 600 /usr/share/crab/crab.conf diff --git a/src/auth.rs b/src/auth.rs index c973921..82a09cb 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,9 +1,15 @@ -use nix::unistd::Group; +use nix::unistd::{User, Group, Uid, Gid}; use crate::{persist, secure}; +#[derive(Debug)] pub struct Config { - pub identitys: Vec<(String, bool)>, + pub permit: bool, + pub persist: bool, + pub nopass: bool, + pub user_uid: Option, + pub user_gid: Option, + pub privlaged_uid: Uid, } @@ -14,37 +20,132 @@ pub struct Config { /// * `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 - }; +/// * `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 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}) + 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, as_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) + }, + None => (args[len-1].to_string(), "root".to_string(), len-1) + }; + + let persist = args[1..as_index].contains(&"persist"); + + let nopass = args[1..as_index].contains(&"nopass"); + + + 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) +} + + +fn config_error(line_num: usize, message: &str) { + eprintln!("Error in config at line {}: {}", line_num, message); +} + + +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 + } +} + + +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 + } +} + + +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 + } } @@ -53,41 +154,44 @@ pub fn load_config(path: &str) -> Option { /// 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 +fn get_groups() -> Vec { + let groups = match nix::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 -/// * `user` - The name of the user to check is authorized +/// * `uid` - The uid 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 +/// * `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 } @@ -96,31 +200,36 @@ pub fn authorize(config: &Config, user: &str) -> Option { /// 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 +/// * `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, or the user is persisted +/// * `true` - If the user authenticated sucessfully /// * `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, +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) ) { + secure::elevate_privilages(config.privlaged_uid); + 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 - }; - 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; + }; + auth.get_handler().set_credentials(&name, input); + if !auth.authenticate().is_ok() || !auth.open_session().is_ok() { + return false; + } + if config.persist { + persist::set_persist(&name); + } + secure::elevate_privilages(config.privlaged_uid); + return true; } diff --git a/src/flags.rs b/src/flags.rs index c93f3ef..a945483 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -5,10 +5,10 @@ const DONT_PERSIST: &str = "d"; pub struct Flags { - pub help: bool, - pub version: bool, - pub dont_persist: bool, - pub arg_count: usize + pub help: bool, + pub version: bool, + pub dont_persist: bool, + pub arg_count: usize } @@ -19,32 +19,32 @@ pub struct Flags { /// * `None` - If there is an invalid argument in the list /// * `Some(Flags)` - If the arguments were secussfully parsed, returning the flags pub fn parse(args: &[String]) -> Option { - let mut flags = Flags { - help: false, - version: false, - dont_persist: false, - arg_count: 0 - }; - for arg in args { - if !is_arg(&arg) { break; } - flags.arg_count += 1; - if arg.starts_with("--") { - let flag = &arg[2..]; - if !set_flag(&flag, &mut flags) { - eprintln!("Invalid argument: {}", arg); - return None - } - } else { - let flag = &arg[1..]; - for char in flag.chars() { - if !set_flag(&char.to_string(), &mut flags) { - eprintln!("Invalid argument: {}", arg); - return None + let mut flags = Flags { + help: false, + version: false, + dont_persist: false, + arg_count: 0 + }; + for arg in args { + if !is_arg(&arg) { break; } + flags.arg_count += 1; + if arg.starts_with("--") { + let flag = &arg[2..]; + if !set_flag(&flag, &mut flags) { + eprintln!("Invalid argument: {}", arg); + return None + } + } else { + let flag = &arg[1..]; + for char in flag.chars() { + if !set_flag(&char.to_string(), &mut flags) { + eprintln!("Invalid argument: {}", arg); + return None + } + } } - } } - } - Some(flags) + Some(flags) } @@ -62,17 +62,17 @@ fn is_arg(arg: &str) -> bool { /// * `true` - If the argument passed is a valid flag /// * `false` - If the argument passed is not a valid flag fn set_flag(arg: &str, flags: &mut Flags) -> bool { - if has_flag_set(&arg, HELP_FLAG) { - flags.help = true; - return true - } else if has_flag_set(&arg, VERSION_FLAG) { - flags.version = true; - return true - } else if has_flag_set(&arg, DONT_PERSIST) { - flags.dont_persist = true; - return true - } - false + if has_flag_set(&arg, HELP_FLAG) { + flags.help = true; + return true + } else if has_flag_set(&arg, VERSION_FLAG) { + flags.version = true; + return true + } else if has_flag_set(&arg, DONT_PERSIST) { + flags.dont_persist = true; + return true + } + false } @@ -83,10 +83,10 @@ fn set_flag(arg: &str, flags: &mut Flags) -> bool { /// * `true` - If the argument matches the flag /// * `false` - If the argument doesn't match the flag fn has_flag_set(arg: &str, check: &str) -> bool { - for check_arg in check.split(" ") { - if check_arg == arg { - return true + for check_arg in check.split(" ") { + if check_arg == arg { + return true + } } - } - return false + return false } diff --git a/src/main.rs b/src/main.rs index ed89e76..cca85de 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ use std::env; use std::process::ExitCode; -use pwd::Passwd; mod auth; @@ -11,9 +10,8 @@ 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_NOT_AUTHORIZED: u8 = 3; +const ERROR_AUTH_FAILED: u8 = 4; fn main() -> ExitCode { @@ -33,7 +31,7 @@ fn main() -> ExitCode { // If the version arg flag is set, print the crab version if flags.version { - println!("crab version 0.0.5"); + println!("crab version 0.0.6"); return ExitCode::SUCCESS; } @@ -50,23 +48,15 @@ fn main() -> ExitCode { } // Load the command config from /etc - let config = match auth::load_config("/etc/crab.conf") { + let configs = match auth::load_config_file("/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 auth::authorize(&config, &user.name) { - Some(data) => data && !flags.dont_persist, + let auth = match auth::authorize(&configs, nix::unistd::getuid()) { + Some(data) => data, None => { eprintln!("Operation Not Permitted."); return ExitCode::from(ERROR_NOT_AUTHORIZED); @@ -74,7 +64,7 @@ fn main() -> ExitCode { }; // authenticate the user - if !auth::authenticate(&user.name, persist) { + if !auth::authenticate(&configs[auth], flags.dont_persist, nix::unistd::getuid()) { eprintln!("Authentication failed."); return ExitCode::from(ERROR_AUTH_FAILED); } diff --git a/src/persist.rs b/src/persist.rs index 1cddb7f..762d8b1 100644 --- a/src/persist.rs +++ b/src/persist.rs @@ -16,15 +16,15 @@ const PERSIST_PATH: &str = "/var/run/crab"; /// * `true` - If the user is persisted /// * `false` - If the user is not persisted, or if the persist file is not trusted pub fn get_persist(user: &str) -> bool { - let json = match get_persist_config() { - Some(data) => data, - None => return false - }; - let timestamp = match json[user].as_u64() { - Some(data) => data, - None => return false - }; - return now() - timestamp < PERSIST_TIME && timestamp - 1 < now(); + let json = match get_persist_config() { + Some(data) => data, + None => return false + }; + let timestamp = match json[user].as_u64() { + Some(data) => data, + None => return false + }; + return now() - timestamp < PERSIST_TIME && timestamp - 1 < now(); } @@ -32,21 +32,21 @@ pub fn get_persist(user: &str) -> bool { /// #### Arguments /// * `user` - The user to set persisted pub fn set_persist(user: &str) { - let mut json = match get_persist_config() { - Some(data) => data, - None => return - }; - json[user] = Value::from(now()); - let session = match get_current_session() { - Some(data) => data, - None => return - }; - match secure::write_file(PERSIST_PATH, &format!("{}", session), &json.to_string()) { - Ok(_) => {}, - Err(e) => { - eprintln!("crab: An Internal Has Error: {}", e); - } - }; + let mut json = match get_persist_config() { + Some(data) => data, + None => return + }; + json[user] = Value::from(now()); + let session = match get_current_session() { + Some(data) => data, + None => return + }; + match secure::write_file(PERSIST_PATH, &format!("{}", session), &json.to_string()) { + Ok(_) => {}, + Err(e) => { + eprintln!("crab: An Internal Has Error: {}", e); + } + }; } @@ -55,15 +55,15 @@ pub fn set_persist(user: &str) { /// * `None` - If crab failed to get the current session /// * `Some(i32)` - If the session is retrieved, returns the i32/pid_t session id fn get_current_session() -> Option { - let pid: i32 = match std::process::id().try_into() { - Ok(data) => data, - Err(_) => return None - }; - let pid_stat = match procinfo::pid::stat(pid) { - Ok(data) => data, - Err(_) => return None - }; - Some(pid_stat.session) + let pid: i32 = match std::process::id().try_into() { + Ok(data) => data, + Err(_) => return None + }; + let pid_stat = match procinfo::pid::stat(pid) { + Ok(data) => data, + Err(_) => return None + }; + Some(pid_stat.session) } @@ -72,23 +72,23 @@ fn get_current_session() -> Option { /// * `None` - If the persist file is untrusted or doesnt exist /// * `Some(Value)` - If the persist file is retrieved, returns the serde_json Value of the file fn get_persist_config() -> Option { - let session = match get_current_session() { - Some(data) => data, - None => return None - }; - let data = match secure::read_file(PERSIST_PATH, &format!("{}", session)) { - Some(data) => data, - None => "{}".to_string() - }; - let json: Value = match serde_json::from_str(&data) { - Ok(data) => data, - Err(_) => return None - }; - Some(json) + let session = match get_current_session() { + Some(data) => data, + None => return None + }; + let data = match secure::read_file(PERSIST_PATH, &format!("{}", session)) { + Some(data) => data, + None => "{}".to_string() + }; + let json: Value = match serde_json::from_str(&data) { + Ok(data) => data, + Err(_) => return None + }; + Some(json) } // Gets the current time in seconds since the Unix Epoch fn now() -> u64 { - return SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(); + return SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(); } diff --git a/src/secure.rs b/src/secure.rs index 13d67b0..018bc4f 100644 --- a/src/secure.rs +++ b/src/secure.rs @@ -1,5 +1,5 @@ use std::{os::{unix::prelude::PermissionsExt, linux::fs::MetadataExt}, fs, io::{self, ErrorKind}}; -use nix::unistd::{self, Uid, Gid}; +use nix::unistd::{self, Uid}; /// Writes a file securly to a specified path with given data @@ -10,12 +10,12 @@ use nix::unistd::{self, Uid, Gid}; /// #### Returns /// 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, 0o100600, dir)?; - let path = path(dir, file); - fs::write(&path, data)?; - set_file_permissions(0, 0, 0o100600, &path)?; - Ok(()) + fs::create_dir_all(dir)?; + set_file_permissions(0, 0, 0o100600, dir)?; + let path = path(dir, file); + fs::write(&path, data)?; + set_file_permissions(0, 0, 0o100600, &path)?; + Ok(()) } @@ -29,31 +29,27 @@ pub fn write_file(dir: &str, file: &str, data: &str) -> Result<(), io::Error> { /// * `None` - If the files doesnt exist or isnt trusted /// * `Some(String) - If the file is trusted, it returns the file's contents pub fn read_file(dir: &str, file: &str) -> Option { - let path = path(dir,file); - if !check_file_permissions(0, 0, 0o100600, &path) { - return None; - } - match fs::read_to_string(&path) { - Ok(data) => return Some(data), - Err(_) => return None - }; + let path = path(dir,file); + if !check_file_permissions(0, 0, 0o100600, &path) { + return None; + } + match fs::read_to_string(&path) { + Ok(data) => return Some(data), + Err(_) => return None + }; } -/// Ekevate tge oruvukages if the current process +/// Elecate the privlages of the current process /// #### Arguments /// * `uid` - The uid to set the process to -/// * `gid` - The gid to set the process to /// #### Returns -/// If the process failes to elevate, it returns false -pub fn elevate_privilages(uid: u32, gid: u32) -> bool { - if unistd::setuid(Uid::from(uid)).is_err() { - return false; - } - if unistd::setgid(Gid::from(gid)).is_err() { - return false; - } - true +/// If the process fails to elevate, it returns false +pub fn elevate_privilages(uid: Uid) -> bool { + if unistd::setuid(uid).is_err() { + return false; + } + true } @@ -66,12 +62,12 @@ pub fn elevate_privilages(uid: u32, gid: u32) -> bool { /// #### Returns /// A ``io::Result<()>`` if the write succeded or failed fn set_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> Result<(), io::Error> { - unistd::chown(std::path::Path::new(path), Some(unistd::Uid::from(uid)), Some(unistd::Gid::from(gid)))?; - let metadata = fs::metadata(path)?; - let mut perms = metadata.permissions(); - perms.set_mode(mode); - fs::set_permissions(path, perms)?; - Ok(()) + unistd::chown(std::path::Path::new(path), Some(unistd::Uid::from(uid)), Some(unistd::Gid::from(gid)))?; + let metadata = fs::metadata(path)?; + let mut perms = metadata.permissions(); + perms.set_mode(mode); + fs::set_permissions(path, perms)?; + Ok(()) } @@ -84,18 +80,18 @@ fn set_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> Result<(), /// #### Returns /// True or false if the files permissions match fn check_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> bool { - let metadata = match fs::metadata(path) { - Ok(data) => data, - Err(e) => { - return e.kind() == ErrorKind::NotFound; - } - }; - let perms = metadata.permissions(); - return perms.mode() == mode && metadata.st_uid() == uid && metadata.st_gid() == gid; + let metadata = match fs::metadata(path) { + Ok(data) => data, + Err(e) => { + return e.kind() == ErrorKind::NotFound; + } + }; + let perms = metadata.permissions(); + return perms.mode() == mode && metadata.st_uid() == uid && metadata.st_gid() == gid; } /// Get the path of a file given a directory and file name fn path(dir: &str, file: &str) -> String { - return format!("{}/{}.persist", dir, file); + return format!("{}/{}.persist", dir, file); } diff --git a/uninstall.sh b/uninstall.sh index f6cba7f..cf7ade8 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -6,11 +6,6 @@ if [[ $(/usr/bin/id -u) -ne 0 ]]; then fi # Delete crab files +rm /etc/pam.d/crab rm /usr/bin/crab -chown root:root /bin/crab -chmod 6755 /bin/crab - -# Set up config files -cp pam /etc/pam.d/crab -cp -n conf /etc/crab.conf -chmod 660 /etc/crab.conf +rm -fr /usr/share/crab From de24d5499ac83f426228b28b90bed8f26e68fb3b Mon Sep 17 00:00:00 2001 From: tylermurphy534 Date: Fri, 11 Nov 2022 09:56:48 -0500 Subject: [PATCH 07/10] doc string, refactor, config token error --- readme.md | 14 +++++++++++--- src/auth.rs | 51 +++++++++++++++++++++++++++++++++++++++----------- src/main.rs | 15 +++++++++++---- src/persist.rs | 28 ++++++++++++++++++++++++--- src/secure.rs | 19 +++---------------- 5 files changed, 90 insertions(+), 37 deletions(-) diff --git a/readme.md b/readme.md index 971c469..7d6566d 100644 --- a/readme.md +++ b/readme.md @@ -12,11 +12,19 @@ Run `uninstall.sh` as root to uninstall crab. If you are on an arch based distro, crab is avaliable on the [AUR](https://aur.archlinux.org/packages/crab) as `crab`. # Configuration -Crab supports multiple users with persistence. Each line of the config is the username, then `true` of `false` if the crab authentication persists. +Each line in the configuration specifies a different rule. Each rule is applied from top to bottom, +so the first onethat matches a user is what is used. The first word is either `permit` or `deny` to +allow or deny a certain group. Then the tags `persist` and `nopass` can be added to allow authoriziation +persistance or skipping respectively. Then a user can be specified by putting their name, or a group by a +colon then the groups name. Finally, if you dont want to run that user as root, you can add `as` and then +a user name to run the process as. All lines starting in a # will be ignored. For Example ``` -root true -tylerm false +deny :docker +permit nopass persist linus as root +permit :wheel persist +#deny stallman +permit nvidia as fu ``` The default configuration file is stored in `/usr/share/crab/crab.conf` and must be coppied to `/etc/crab.conf`. diff --git a/src/auth.rs b/src/auth.rs index 82a09cb..99a8216 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,8 +1,7 @@ -use nix::unistd::{User, Group, Uid, Gid}; -use crate::{persist, secure}; +use nix::unistd::{User, Group, Uid, Gid, self}; +use crate::persist; -#[derive(Debug)] pub struct Config { pub permit: bool, pub persist: bool, @@ -55,20 +54,31 @@ pub fn load_config_file(path: &str) -> Option> { } }; - let (user_name, privlaged_name, as_index) = match args.iter().position(|&a| a == "as") { + 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) + (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..as_index].contains(&"persist"); + let persist = args[1..name_index].contains(&"persist"); - let nopass = args[1..as_index].contains(&"nopass"); + 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) = @@ -111,11 +121,18 @@ pub fn load_config_file(path: &str) -> Option> { } +/// 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 { @@ -127,6 +144,12 @@ fn get_uid_from_name(name: &str) -> Option { } +/// 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 { @@ -138,6 +161,12 @@ fn get_name_from_uid(uid: Uid) -> Option { } +/// 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 { @@ -155,7 +184,7 @@ fn get_gid_from_name(name: &str) -> Option { /// 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() { + let groups = match unistd::getgroups() { Ok(data) => data, Err(_) => return vec![] }; @@ -212,7 +241,6 @@ pub fn authenticate(config: &Config, force_pass: bool, uid: Uid) -> bool { None => return false }; if config.nopass || ( !force_pass && config.persist && persist::get_persist(&name) ) { - secure::elevate_privilages(config.privlaged_uid); return true; } let input = match rpassword::prompt_password(format!("crab ({}) password: ", &name)) { @@ -227,9 +255,10 @@ pub fn authenticate(config: &Config, force_pass: bool, uid: Uid) -> bool { if !auth.authenticate().is_ok() || !auth.open_session().is_ok() { return false; } - if config.persist { + if !force_pass && config.persist { persist::set_persist(&name); + } else if force_pass { + persist::remove_persist(&name); } - secure::elevate_privilages(config.privlaged_uid); return true; } diff --git a/src/main.rs b/src/main.rs index cca85de..56df611 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,10 +8,11 @@ mod persist; mod secure; -const ERROR_ARGS: u8 = 1; -const ERROR_CONFIG: u8 = 2; -const ERROR_NOT_AUTHORIZED: u8 = 3; -const ERROR_AUTH_FAILED: u8 = 4; +const ERROR_ARGS: u8 = 1; +const ERROR_CONFIG: u8 = 2; +const ERROR_NOT_AUTHORIZED: u8 = 3; +const ERROR_AUTH_FAILED: u8 = 4; +const ERROR_ELEVATE_PRIVILEGES: u8 = 5; fn main() -> ExitCode { @@ -69,6 +70,12 @@ fn main() -> ExitCode { return ExitCode::from(ERROR_AUTH_FAILED); } + // elevate privileges + if nix::unistd::setuid(configs[auth].privlaged_uid).is_err() { + eprintln!("Failed to elevate privileges."); + return ExitCode::from(ERROR_ELEVATE_PRIVILEGES); + }; + // execute the passed command let start = 1 + flags.arg_count; let err = exec::execvp(&args[start], &args[start..]); diff --git a/src/persist.rs b/src/persist.rs index 762d8b1..2bca386 100644 --- a/src/persist.rs +++ b/src/persist.rs @@ -28,7 +28,7 @@ pub fn get_persist(user: &str) -> bool { } -/// Updates the current sessions persist file +/// Updates a user in the current sessions persist file /// #### Arguments /// * `user` - The user to set persisted pub fn set_persist(user: &str) { @@ -43,8 +43,30 @@ pub fn set_persist(user: &str) { }; match secure::write_file(PERSIST_PATH, &format!("{}", session), &json.to_string()) { Ok(_) => {}, - Err(e) => { - eprintln!("crab: An Internal Has Error: {}", e); + Err(_) => { + eprintln!("crab: An internal error has occured"); + } + }; +} + + +/// Removes a user from the current sessions persist file +/// #### Arguments +/// * `user` - The user to set non-persisted +pub fn remove_persist(user: &str) { + let mut json = match get_persist_config() { + Some(data) => data, + None => return + }; + json[user] = Value::from(0); + let session = match get_current_session() { + Some(data) => data, + None => return + }; + match secure::write_file(PERSIST_PATH, &format!("{}", session), &json.to_string()) { + Ok(_) => {}, + Err(_) => { + eprintln!("crab: An internal error has occured"); } }; } diff --git a/src/secure.rs b/src/secure.rs index 018bc4f..7c85ece 100644 --- a/src/secure.rs +++ b/src/secure.rs @@ -1,5 +1,5 @@ -use std::{os::{unix::prelude::PermissionsExt, linux::fs::MetadataExt}, fs, io::{self, ErrorKind}}; -use nix::unistd::{self, Uid}; +use std::{os::{unix::prelude::PermissionsExt, linux::fs::MetadataExt}, fs, io::{self, ErrorKind}, path::Path}; +use nix::unistd::{self, Uid, Gid}; /// Writes a file securly to a specified path with given data @@ -40,19 +40,6 @@ pub fn read_file(dir: &str, file: &str) -> Option { } -/// Elecate the privlages of the current process -/// #### Arguments -/// * `uid` - The uid to set the process to -/// #### Returns -/// If the process fails to elevate, it returns false -pub fn elevate_privilages(uid: Uid) -> bool { - if unistd::setuid(uid).is_err() { - return false; - } - true -} - - /// Sets the permission for a secure file /// #### Arguments /// * `uid` - The user to own the file @@ -62,7 +49,7 @@ pub fn elevate_privilages(uid: Uid) -> bool { /// #### Returns /// A ``io::Result<()>`` if the write succeded or failed fn set_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> Result<(), io::Error> { - unistd::chown(std::path::Path::new(path), Some(unistd::Uid::from(uid)), Some(unistd::Gid::from(gid)))?; + unistd::chown(Path::new(path), Some(Uid::from(uid)), Some(Gid::from(gid)))?; let metadata = fs::metadata(path)?; let mut perms = metadata.permissions(); perms.set_mode(mode); From 0443572477787936df9561371be45a038839f3e2 Mon Sep 17 00:00:00 2001 From: tylermurphy534 Date: Fri, 11 Nov 2022 15:16:37 -0500 Subject: [PATCH 08/10] 0.0.6 --- .gitignore | 1 + conf | 5 ---- config/default | 5 ++++ pam => config/pam | 0 deployments/aur/.SRCINFO | 5 ++-- deployments/aur/PKGBUILD | 16 +++++----- install.sh => deployments/source/install.sh | 4 +-- .../source/uninstall.sh | 0 readme.md | 29 +++++++++++++++---- 9 files changed, 42 insertions(+), 23 deletions(-) delete mode 100644 conf create mode 100644 config/default rename pam => config/pam (100%) rename install.sh => deployments/source/install.sh (84%) rename uninstall.sh => deployments/source/uninstall.sh (100%) diff --git a/.gitignore b/.gitignore index dc73d83..0f0d9c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target Cargo.lock test.sh +crab.tar.gz diff --git a/conf b/conf deleted file mode 100644 index 0da97e5..0000000 --- a/conf +++ /dev/null @@ -1,5 +0,0 @@ -permit nopass linus as root -deny :docker -#deny jane -permit persist :wheel -permit jane as doe \ No newline at end of file diff --git a/config/default b/config/default new file mode 100644 index 0000000..8976cc6 --- /dev/null +++ b/config/default @@ -0,0 +1,5 @@ +deny :docker +permit nopass persist linus as root +#deny stallman +permit :wheel persist +permit nvidia as fu \ No newline at end of file diff --git a/pam b/config/pam similarity index 100% rename from pam rename to config/pam diff --git a/deployments/aur/.SRCINFO b/deployments/aur/.SRCINFO index c145393..83827ea 100644 --- a/deployments/aur/.SRCINFO +++ b/deployments/aur/.SRCINFO @@ -2,15 +2,14 @@ pkgbase = crab pkgdesc = A rusty permission authentication system pkgver = 0.0.6 pkgrel = 2 - url = https://g.tylerm.dev/tylermurphy534/crab.git + url = https://g.tylerm.dev/tylermurphy534/crab arch = x86_64 arch = i686 license = GPL3 - makedepends = git makedepends = cargo depends = glibc depends = pam - source = git+https://g.tylerm.dev/tylermurphy534/crab.git + source = crab-0.0.6.tar.gz::https://f.tylerm.dev/source/crab/crab-0.0.6.tar.gz md5sums = SKIP pkgname = crab diff --git a/deployments/aur/PKGBUILD b/deployments/aur/PKGBUILD index 6196e48..2edfc1e 100644 --- a/deployments/aur/PKGBUILD +++ b/deployments/aur/PKGBUILD @@ -1,24 +1,24 @@ # Maintainer: Tyler Murphy pkgname=crab pkgver=0.0.6 -pkgrel=2 +pkgrel=1 pkgdesc="A rusty permission authentication system" arch=('x86_64' 'i686') -url="https://g.tylerm.dev/tylermurphy534/crab.git" +url="https://g.tylerm.dev/tylermurphy534/crab" license=('GPL3') -makedepends=('git' 'cargo') +makedepends=('cargo') depends=('glibc' 'pam') -source=("git+$url") +source=("$pkgname-$pkgver.tar.gz::https://f.tylerm.dev/source/$pkgname/$pkgname-$pkgver.tar.gz") md5sums=('SKIP') build() { - cd crab + cd "$srcdir" cargo build --release } package() { - cd crab + cd "$srcdir" install -D --mode=4755 --owner=root --group=root ./target/release/crab ${pkgdir}/usr/bin/crab - install -D --mode=600 --owner=root --group=root pam ${pkgdir}/etc/pam.d/crab - install -D --mode=600 --owner=root --group=root conf ${pkgdir}/usr/share/crab/crab.conf + install -D --mode=600 --owner=root --group=root ./config/pam ${pkgdir}/etc/pam.d/crab + install -D --mode=644 --owner=root --group=root ./config/default ${pkgdir}/usr/share/crab/crab.conf } diff --git a/install.sh b/deployments/source/install.sh similarity index 84% rename from install.sh rename to deployments/source/install.sh index 6c7b019..5b95a83 100755 --- a/install.sh +++ b/deployments/source/install.sh @@ -15,7 +15,7 @@ cp pam /etc/pam.d/crab chmod 600 /etc/pam.d/crab mkdir /usr/share/crab -chmod 600 /usr/share/crab +chmod 644 /usr/share/crab cp conf /usr/share/crab/crab.conf -chmod 600 /usr/share/crab/crab.conf +chmod 644 /usr/share/crab/crab.conf diff --git a/uninstall.sh b/deployments/source/uninstall.sh similarity index 100% rename from uninstall.sh rename to deployments/source/uninstall.sh diff --git a/readme.md b/readme.md index 7d6566d..e191759 100644 --- a/readme.md +++ b/readme.md @@ -3,15 +3,34 @@ # Installation ### From Source -First run `cargo build --release` to compile the binary. -Then run `install.sh` as root to install crab. +To be able to build the package, you need cargo wich you can get either though rust or rust up. -Run `uninstall.sh` as root to uninstall crab. +To build, run the following commands below in the root directory of the repo. Make sure to run the shell script as root. +```bash +cargo build --release +./deployments/source/install.sh +``` + +To uninstall, just run the following script as root. +```bash +./deployments/source/uninstall.sh +``` ### Arch Based Systems If you are on an arch based distro, crab is avaliable on the [AUR](https://aur.archlinux.org/packages/crab) as `crab`. +``` +paru -S crab +``` # Configuration + +The default configuration file is stored in `/usr/share/crab/crab.conf` and must be coppied to `/etc/crab.conf`. +```bash +cp /usr/share/crab/crab.conf /etc/crab.conf +chown root:root /etc/crab.conf +chmod 600 /etc/crab.conf +``` + Each line in the configuration specifies a different rule. Each rule is applied from top to bottom, so the first onethat matches a user is what is used. The first word is either `permit` or `deny` to allow or deny a certain group. Then the tags `persist` and `nopass` can be added to allow authoriziation @@ -23,8 +42,8 @@ For Example ``` deny :docker permit nopass persist linus as root -permit :wheel persist #deny stallman +permit :wheel persist permit nvidia as fu ``` -The default configuration file is stored in `/usr/share/crab/crab.conf` and must be coppied to `/etc/crab.conf`. +Please make sure when editing your config that not normal users can edit the file, but only root. If normal users can edit the config, they can add themselvs as permitted and get elevated privilages. From 34e676eaad04683a8ed927c4c84d000e870c6907 Mon Sep 17 00:00:00 2001 From: Tyler Murphy Date: Fri, 11 Nov 2022 16:29:57 -0500 Subject: [PATCH 09/10] fix install file --- deployments/source/install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployments/source/install.sh b/deployments/source/install.sh index 5b95a83..a3c4e4e 100755 --- a/deployments/source/install.sh +++ b/deployments/source/install.sh @@ -7,8 +7,8 @@ fi # Copy executable to bin cp ./target/release/crab /usr/bin/crab -chown root:root /bin/crab -chmod 4755 /bin/crab +chown root:root /usr/bin/crab +chmod 4755 /usr/bin/crab # Set up config files cp pam /etc/pam.d/crab From bf85fe72bf51020cc6fd46117d8009438c5f6973 Mon Sep 17 00:00:00 2001 From: Tyler Murphy Date: Fri, 11 Nov 2022 16:31:28 -0500 Subject: [PATCH 10/10] fix install file part 2 --- deployments/source/install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployments/source/install.sh b/deployments/source/install.sh index a3c4e4e..b488693 100755 --- a/deployments/source/install.sh +++ b/deployments/source/install.sh @@ -11,11 +11,11 @@ chown root:root /usr/bin/crab chmod 4755 /usr/bin/crab # Set up config files -cp pam /etc/pam.d/crab +cp ./config/pam /etc/pam.d/crab chmod 600 /etc/pam.d/crab mkdir /usr/share/crab chmod 644 /usr/share/crab -cp conf /usr/share/crab/crab.conf +cp ./config/default /usr/share/crab/crab.conf chmod 644 /usr/share/crab/crab.conf