commit c98b78f4c17a0d997de2cf7aef1f8a22e1a92616 Author: Tyler Murphy Date: Tue Nov 8 19:35:01 2022 -0500 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e401bd5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,326 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ca34107f97baef6cfb231b32f36115781856b8f8208e8c580e0bcaea374842" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crab" +version = "4.2.0" +dependencies = [ + "exec", + "nix", + "pam", + "procinfo", + "pwd", + "rpassword", + "serde_json", + "time", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "exec" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "886b70328cba8871bfc025858e1de4be16b1d5088f2ba50b57816f4210672615" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nix" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + +[[package]] +name = "nom" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff" + +[[package]] +name = "pam" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa2bdc959c201c047004a1420a92aaa1dd1a6b64d5ef333aa3a4ac764fb93097" +dependencies = [ + "libc", + "pam-sys", + "users", +] + +[[package]] +name = "pam-sys" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd4858311a097f01a0006ef7d0cd50bca81ec430c949d7bf95cbefd202282434" +dependencies = [ + "libc", +] + +[[package]] +name = "pin-utils" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab1427f3d2635891f842892dda177883dca0639e05fe66796a62c9d2f23b49c" +dependencies = [ + "byteorder", + "libc", + "nom", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c9f5d2a0c3e2ea729ab3706d22217177770654c3ef5056b68b69d07332d3f5" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" + +[[package]] +name = "serde_json" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +dependencies = [ + "itoa", + "ryu", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +dependencies = [ + "serde", + "time-core", +] + +[[package]] +name = "time-core" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fed7d0912567d35f88010c23dbaf865e9da8b5227295e8dc0f2fdd109155ab7" +dependencies = [ + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d063d87 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "crab" +version = "4.2.0" +edition = "2021" + +[dependencies] +pam = "0.7.0" +pwd = "1.4.0" +nix = "0.25.0" +exec = "0.3.1" +rpassword = "7.1.0" +serde_json = "1.0.87" +procinfo = "0.4.2" +time = "0.3.17" \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..b9e3c57 --- /dev/null +++ b/build.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +if [[ $(/usr/bin/id -u) -ne 0 ]]; then + echo "Please run this as root" + exit +fi + +# Make sure rust is able to build as root +rustup default stable + +# Build crab +cargo build --release + +# Copy executable to bin +cp ./target/release/crab /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 \ No newline at end of file diff --git a/conf b/conf new file mode 100644 index 0000000..bebb505 --- /dev/null +++ b/conf @@ -0,0 +1 @@ +root true \ No newline at end of file diff --git a/pam b/pam new file mode 100644 index 0000000..b98216b --- /dev/null +++ b/pam @@ -0,0 +1,4 @@ +#%PAM-1.0 +auth include system-auth +account include system-auth +session include system-auth diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..dd8eeb3 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,188 @@ +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); +} \ No newline at end of file