made rudo lol

main
elfeiin 2023-12-24 18:38:31 -08:00
commit bf66312e8a
Signed by: elfein
GPG Key ID: A53FDD4FD091A276
9 changed files with 1065 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

519
Cargo.lock generated Normal file
View File

@ -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",
]

19
Cargo.toml Normal file
View File

@ -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"

53
src/app.rs Normal file
View File

@ -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<String>,
#[arg(short, long)]
pub validate: bool,
pub cmd: Option<Vec<String>>,
// /// 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<PathBuf>,
// /// 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 `#<group number>`
// #[arg(short, long)]
// pub group: Option<String>,
// #[arg(short = 'H', long)]
// pub set_home: bool,
// #[arg(short = 'h', long)]
// pub host: Option<String>,
// #[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<String>,
// #[arg(short = 'R', long)]
// pub chroot: Option<PathBuf>,
// /// 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,
}

20
src/authentication.rs Normal file
View File

@ -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)
}

35
src/error.rs Normal file
View File

@ -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}"]
}
}

162
src/main.rs Normal file
View File

@ -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::<Instant, u128>(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::<u128>(&buf) {
Ok(n) => {
let old_time = unsafe { std::mem::transmute::<u128, Instant>(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(())
}

202
src/rudoers.rs Normal file
View File

@ -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<String>,
#[serde(default)]
commands: BTreeSet<String>,
}
#[derive(Deserialize, Serialize)]
#[serde(untagged)]
pub enum Entry {
CommandPaths(BTreeSet<String>),
Hosts(BTreeMap<String, HostPerms>),
}
#[derive(Default, Deserialize, Serialize)]
pub struct Rudoers {
users: BTreeMap<String, Entry>,
#[serde(default)]
groups: BTreeMap<String, Entry>,
}
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<bool, RudoError> {
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<bool, RudoError> {
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<Group> = 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();
}

54
src/user_info.rs Normal file
View File

@ -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<PathBuf, RudoError> {
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<String, RudoError> {
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<Group> {
if let Some(groups) =
users::get_user_groups(&get_username().unwrap_or_default(), get_current_gid())
{
groups
} else {
vec![]
}
}
pub fn get_hostname() -> Result<String, RudoError> {
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)
}
}