From bf66312e8ae9c74d2c5dcb6394ae4da9fa213370 Mon Sep 17 00:00:00 2001 From: elfeiin Date: Sun, 24 Dec 2023 18:38:31 -0800 Subject: [PATCH] made rudo lol --- .gitignore | 1 + Cargo.lock | 519 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 19 ++ src/app.rs | 53 +++++ src/authentication.rs | 20 ++ src/error.rs | 35 +++ src/main.rs | 162 +++++++++++++ src/rudoers.rs | 202 ++++++++++++++++ src/user_info.rs | 54 +++++ 9 files changed, 1065 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/app.rs create mode 100644 src/authentication.rs create mode 100644 src/error.rs create mode 100644 src/main.rs create mode 100644 src/rudoers.rs create mode 100644 src/user_info.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9e0f17e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,519 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "clap" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fork" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf2ca97a59201425e7ee4d197c9c4fea282fe87a97d666a580bda889b95b8e88" +dependencies = [ + "libc", +] + +[[package]] +name = "hashbrown" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "linux-raw-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "pam" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa2bdc959c201c047004a1420a92aaa1dd1a6b64d5ef333aa3a4ac764fb93097" +dependencies = [ + "libc", + "pam-sys", + "users 0.8.1", +] + +[[package]] +name = "pam-sys" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cce697055a5e48d8907841682cd14ccc01494dfb39453335941cca6dde524b39" +dependencies = [ + "libc", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rpassword" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" +dependencies = [ + "libc", + "rtoolbox", + "winapi", +] + +[[package]] +name = "rtoolbox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "rudo" +version = "0.1.0" +dependencies = [ + "bincode", + "clap", + "fork", + "libc", + "pam", + "rpassword", + "serde", + "toml", + "unix_mode", + "users 0.11.0", + "which", +] + +[[package]] +name = "rustix" +version = "0.38.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "serde" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unix_mode" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55eedc365f81a3c32aea49baf23fa965e3cd85bcc28fb8045708c7388d124ef" + +[[package]] +name = "users" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fed7d0912567d35f88010c23dbaf865e9da8b5227295e8dc0f2fdd109155ab7" +dependencies = [ + "libc", +] + +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[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" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fd37b7e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "rudo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bincode = "1.3.3" +clap = { version = "4.4.6", features = ["derive"] } +fork = "0.1.22" +libc = "0.2.149" +pam = "0.7.0" +rpassword = "7.2.0" +serde = { version = "1.0.189", features = ["derive"] } +toml = "0.8.2" +unix_mode = "0.1.4" +users = "0.11.0" +which = "4.4.2" diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..093d9c6 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,53 @@ +use clap::Parser; +use std::path::PathBuf; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None, disable_help_flag = true, disable_version_flag = true)] +pub struct App { + /// Run the given command in background mode. Caution: backgrounded processes are not subject to shell job control. Interactive commands may misbehave. + #[arg(short, long)] + pub background: bool, + /// run as the specified user + #[arg(short, long)] + pub user: Option, + #[arg(short, long)] + pub validate: bool, + pub cmd: Option>, + // /// Run the command from the specified directory. The security policy may return an error if the user does not have permission to specify the working directory. + // #[arg(short = 'D', long)] + // pub chdir: Option, + // /// Indicates to the security policy that the user wishes to preserve existing environment variables. Subject to security policy. + // #[arg(short, long)] + // pub preserve_env: bool, + // #[arg(long)] + // help: bool, + // /// Specify group via name or `#` + // #[arg(short, long)] + // pub group: Option, + // #[arg(short = 'H', long)] + // pub set_home: bool, + // #[arg(short = 'h', long)] + // pub host: Option, + // #[arg(short = 'i', long)] + // pub login: bool, + // /// Remove all cached credentials for user. + // #[arg(short = 'K', long)] + // pub remove_timestamp: bool, + // /// Remove current shell's cached credentials. + // #[arg(short, long)] + // pub reset_timestamp: bool, + // /// Don't update cached credentials. + // #[arg(short = 'N', long)] + // pub no_update: bool, + // /// List privileges for user. + // #[arg(short, long)] + // pub list: Option, + // #[arg(short = 'R', long)] + // pub chroot: Option, + // /// Write prompt to stderr and read from stdin instead of terminal input. + // #[arg(short = 'S', long)] + // pub stdin: bool, + // /// Run shell specified in `SHELL`. + // #[arg(short, long)] + // pub shell: bool, +} diff --git a/src/authentication.rs b/src/authentication.rs new file mode 100644 index 0000000..8a5b9a0 --- /dev/null +++ b/src/authentication.rs @@ -0,0 +1,20 @@ +use crate::{error::RudoError, user_info::get_username, AUTH_SERVICE, RETRIES}; + +pub fn check_auth() -> Result<(), RudoError> { + let login = get_username().unwrap(); + let mut retries = RETRIES; + for i in 0..retries { + let password = rpassword::prompt_password(format!["password for {login}: "]).unwrap(); + let mut auth = pam::Authenticator::with_password(AUTH_SERVICE).unwrap(); + auth.get_handler().set_credentials(&login, password); + match auth.authenticate() { + Ok(()) => return Ok(()), + Err(_) => { + if i < retries - 1 { + eprintln!["incorrect password, try again"] + } + } + } + } + Err(RudoError::AuthenticationError) +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..6ec6aa1 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,35 @@ +use std::fmt::Display; + +#[derive(Debug)] +pub enum RudoError { + AuthenticationError, + NoCommandSpecified, + CommandNotFound, + PermissionDenied, + MalformedRudoersToml, + NotInRudoers, + NoSwitchEntry, + UnknownError, + BadHostname, + BadUsername, + BadGroupname, +} + +impl Display for RudoError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let text: &str = match self { + RudoError::AuthenticationError => "incorrect password".into(), + RudoError::NoCommandSpecified => "no command specified".into(), + RudoError::PermissionDenied => "permission denied :)".into(), + RudoError::MalformedRudoersToml => "malformed rudoers TODO: display error span".into(), + RudoError::NotInRudoers => "user is not in the rudoers file! >:C".into(), + RudoError::NoSwitchEntry => "not allowed to run as that user! >:C".into(), + RudoError::UnknownError => "unknown error".into(), + RudoError::BadHostname => "bad hostname".into(), + RudoError::BadUsername => "bad username".into(), + RudoError::BadGroupname => "bad groupname".into(), + RudoError::CommandNotFound => "command not found".into(), + }; + writeln![f, "{text}"] + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..556f93d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,162 @@ +use std::{ + fs::{DirBuilder, File, OpenOptions}, + io::{Error, Read, Write}, + os::unix::process::CommandExt, + path::PathBuf, + process::Command, + thread, + time::{Duration, Instant}, +}; + +mod app; +use app::App; +use clap::Parser; +use error::RudoError; +mod error; +mod rudoers; +mod user_info; +use fork::{daemon, Fork}; +use libc::kill; +use rudoers::check_rudoers; +mod authentication; +use authentication::check_auth; +use user_info::get_command_path; + +/// RUDOLPH THE RED NOSE REINDEER +/// HAD A VERY SHINY NOSE +/// AND IF YOU EVER SAW IT +/// YOU MIGHT EVEN SAY IT GLOWS + +const DEFAULT_RUDOERS: &'static str = "/etc/rudoers.toml"; +const AUTH_SERVICE: &'static str = "system-auth"; +const RETRIES: u32 = 3; +const DEFAULT_AUTH_CACHE_DURATION: u32 = 15 * 60; +const AUTH_CACHE_PATH: &str = "/var/db/rudo/cache/auth/"; +const SESSION_CACHE_PATH: &str = "/var/db/rudo/cache/session/"; + +pub fn update_auth_cache(alias: &str, ppid: &str) -> Result<(), std::io::Error> { + let mut path = PathBuf::from(AUTH_CACHE_PATH); + path.push(ppid); + DirBuilder::new().recursive(true).create(&path)?; + path.push(alias); + let mut opts = OpenOptions::new(); + opts.write(true).create(true); + let mut file = opts.open(&path)?; + let time = Instant::now(); + let time = unsafe { std::mem::transmute::(time) }; + file.write(&bincode::serialize(&time).unwrap())?; + Ok(()) +} + +pub fn check_auth_cache(alias: &str, ppid: &str) -> bool { + let mut path = PathBuf::from(AUTH_CACHE_PATH); + path.push(ppid); + path.push(alias); + let mut file = if let Ok(file) = File::open(&path) { + file + } else { + return false; + }; + let mut buf = Vec::new(); + if let Err(_) = file.read_to_end(&mut buf) { + return false; + }; + match bincode::deserialize::(&buf) { + Ok(n) => { + let old_time = unsafe { std::mem::transmute::(n) }; + if old_time.elapsed() < Duration::from_secs(DEFAULT_AUTH_CACHE_DURATION as u64) { + true + } else { + false + } + } + Err(e) => { + dbg!["{}", e]; + false + } + } +} + +pub fn execute(app: &App) -> Result<(), RudoError> { + if let Some(ref cmds) = app.cmd { + let run_as_name = match &app.user { + Some(x) => &x, + None => "root", + }; + let run_as = users::get_user_by_name(run_as_name).unwrap(); + + let mut child = Command::new(get_command_path(cmds.first().unwrap())?) + .uid(run_as.uid()) + .args(&cmds[1..]) + .spawn() + .unwrap(); + if app.background { + std::process::exit(0); + } else { + child.wait().unwrap(); + } + } else { + return Err(RudoError::NoCommandSpecified); + } + Ok(()) +} + +pub fn run() -> Result<(), RudoError> { + let app = App::parse(); + let alias = match &app.user { + Some(x) => &x, + None => "root", + }; + + let parent_id = std::os::unix::process::parent_id(); + let parent_id_str = format!["{parent_id}"]; + + let needs_password = check_rudoers(&app)? && !check_auth_cache(alias, &parent_id_str) || app.validate; + if needs_password { + check_auth()?; + } + + if let Err(e) = update_auth_cache(alias, &parent_id_str) { + eprintln!["Failed to update cache: {e}"]; + std::process::exit(1); + } + + let mut path = PathBuf::from(SESSION_CACHE_PATH); + path.push(&parent_id_str); + match OpenOptions::new().create_new(true).write(true).open(&path) { + Ok(_) => { + if let Ok(Fork::Child) = daemon(false, false) { + loop { + if unsafe { kill(parent_id as _, 0) } == -1 { + if let Err(e) = std::fs::remove_file(&path) { + println!["Failed to remove session file: {e}"]; + std::process::exit(1); + } + let mut path = PathBuf::from(AUTH_CACHE_PATH); + path.push(parent_id_str); + if let Err(e) = std::fs::remove_dir_all(&path) { + println!["Failed to remove session dir: {e}"]; + std::process::exit(1); + } + break; + } + thread::sleep(Duration::from_millis(1000)) + } + } + } + Err(_) => (), + }; + + // Run the command + execute(&app)?; + + Ok(()) +} + +fn main() -> Result<(), Error> { + match run() { + Ok(()) => (), + Err(e) => eprintln!["{e}"], + } + Ok(()) +} diff --git a/src/rudoers.rs b/src/rudoers.rs new file mode 100644 index 0000000..540335c --- /dev/null +++ b/src/rudoers.rs @@ -0,0 +1,202 @@ +use serde::{Deserialize, Serialize}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fs::File, + io::{Read, Write}, + path::PathBuf, +}; +use users::Group; + +use crate::{app::App, user_info::*}; +use crate::{error::RudoError, DEFAULT_RUDOERS}; + +const ALL: &'static str = "ALL"; + +fn default_password_required() -> bool { + true +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct HostPerms { + #[serde(default = "default_password_required")] + password_required: bool, + aliases: BTreeSet, + #[serde(default)] + commands: BTreeSet, +} + +#[derive(Deserialize, Serialize)] +#[serde(untagged)] +pub enum Entry { + CommandPaths(BTreeSet), + Hosts(BTreeMap), +} + +#[derive(Default, Deserialize, Serialize)] +pub struct Rudoers { + users: BTreeMap, + #[serde(default)] + groups: BTreeMap, +} + +fn parse_rudoers() -> Rudoers { + let mut rudoers = match File::open(DEFAULT_RUDOERS) { + Ok(file) => file, + Err(e) => { + eprintln!["Cannot open {DEFAULT_RUDOERS}: {e}"]; + std::process::exit(1); + } + }; + let mut contents = String::new(); + match rudoers.read_to_string(&mut contents) { + Ok(_) => (), + Err(e) => { + eprintln!["Could not read {DEFAULT_RUDOERS}: {e}"]; + std::process::exit(1); + } + }; + match toml::from_str(&contents) { + Ok(rudoers) => rudoers, + Err(e) => { + eprintln!["Failed to parse {DEFAULT_RUDOERS}: {e}"]; + std::process::exit(1); + } + } +} + +fn check_entry( + entry: &Entry, + hostname: &str, + alias: &str, + command_path: &str, +) -> Result { + match entry { + Entry::CommandPaths(cmds) => { + if cmds.contains(command_path) || cmds.contains(ALL) { + Ok(true) + } else { + Err(RudoError::PermissionDenied) + } + } + Entry::Hosts(hosts) => { + if let Some(ref host) = hosts.get(hostname).or_else(|| hosts.get(ALL)) { + if host.aliases.contains(alias) || host.aliases.contains(ALL) { + if host.commands.contains(command_path) || host.commands.contains(ALL) { + Ok(host.password_required) + } else { + Err(RudoError::PermissionDenied) + } + } else { + Err(if alias == "root" { + RudoError::PermissionDenied + } else { + RudoError::NoSwitchEntry + }) + } + } else { + Err(RudoError::PermissionDenied) + } + } + } +} + +pub fn check_rudoers(args: &App) -> Result { + let command_path: PathBuf = if let Some(ref cmd) = args.cmd { + get_command_path(cmd.first().unwrap())? + } else { + return Err(RudoError::NoCommandSpecified); + }; + let alias: String = args.user.clone().unwrap_or("root".into()); + let hostname: String = get_hostname()?; + let username: String = get_username()?; + let groups: Vec = get_groups(); + let rudoers = parse_rudoers(); + Ok(if let Some(entry) = rudoers.users.get(&username) { + check_entry(&entry, &hostname, &alias, &command_path.to_string_lossy())? + } else { + let mut result = Err(RudoError::NotInRudoers); + for group in groups { + if let Some(entry) = rudoers.groups.get(group.name().to_str().unwrap()) { + let output = + check_entry(&entry, &hostname, &alias, &command_path.to_string_lossy()); + if output.is_ok() { + result = output; + break; + } + } + } + result? + }) +} + +#[test] +fn rudoers_toml() { + let mut rudoers = Rudoers::default(); + rudoers.users.insert( + "alice".to_string(), + Entry::Hosts({ + let mut hosts = BTreeMap::new(); + hosts.insert( + "northpole".to_string(), + HostPerms { + password_required: false, + aliases: { + let mut set = BTreeSet::new(); + set.insert("root".to_string()); + set + }, + commands: { + let mut commands = BTreeSet::new(); + commands.insert("/bin/cat".into()); + commands.insert("/bin/grep".into()); + commands.insert("/bin/xargs".into()); + commands + }, + }, + ); + hosts + }), + ); + rudoers.users.insert( + "bob".to_string(), + Entry::Hosts({ + let mut hosts = BTreeMap::new(); + hosts.insert( + "northpole".to_string(), + HostPerms { + password_required: false, + aliases: { + let mut set = BTreeSet::new(); + set.insert("root".to_string()); + set + }, + commands: { + let mut commands = BTreeSet::new(); + commands.insert("/bin/cat".into()); + commands.insert("/bin/grep".into()); + commands.insert("/bin/xargs".into()); + commands + }, + }, + ); + hosts + }), + ); + let rudoers = toml::to_string(&rudoers).unwrap(); + let mut text = String::new(); + for (n, s) in rudoers.lines().enumerate() { + if n > 0 { + text.push('\n'); + } + if s.starts_with(|c: char| !c.is_whitespace()) { + text.push_str(&format!["# {s}"]); + } else { + text.push_str(&s); + } + } + text.push('\n'); + File::create("test_config") + .unwrap() + .write_all(&text.into_bytes()) + .unwrap(); +} diff --git a/src/user_info.rs b/src/user_info.rs new file mode 100644 index 0000000..4663128 --- /dev/null +++ b/src/user_info.rs @@ -0,0 +1,54 @@ +use std::path::PathBuf; + +use libc::gethostname; +use users::{get_current_gid, get_current_username, Group}; +use which::which; + +use crate::error::RudoError; + +pub fn get_command_path(command: &str) -> Result { + if let Ok(path) = which(command) { + Ok(path) + } else { + let mut path = PathBuf::new(); + path.push(command.to_owned()); + Ok(path) + } +} + +pub fn get_username() -> Result { + if let Some(username) = get_current_username() { + if let Ok(username) = username.into_string() { + Ok(username) + } else { + Err(RudoError::BadUsername) + } + } else { + Err(RudoError::BadUsername) + } +} + +pub fn get_groups() -> Vec { + if let Some(groups) = + users::get_user_groups(&get_username().unwrap_or_default(), get_current_gid()) + { + groups + } else { + vec![] + } +} + +pub fn get_hostname() -> Result { + let mut hostname = [0u8; 255]; + unsafe { + if gethostname(hostname.as_mut_ptr() as *mut i8, hostname.len()) != 0 { + return Err(RudoError::BadHostname); + } + }; + if let Ok(s) = String::from_utf8(hostname.to_vec()) { + let s = s.trim_matches(char::from_u32(0).unwrap()).to_owned(); + Ok(s) + } else { + Err(RudoError::BadHostname) + } +}