alpha hehehehehe woohooo
parent
bf66312e8a
commit
2a4d6c9f30
|
@ -2,54 +2,6 @@
|
|||
# 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"
|
||||
|
@ -61,55 +13,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.1"
|
||||
version = "2.4.2"
|
||||
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"
|
||||
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
|
@ -125,9 +31,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
|||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.5"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
|
@ -156,9 +62,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
|||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.5"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
|
||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
@ -175,15 +81,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.149"
|
||||
version = "0.2.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.10"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
|
||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
|
@ -199,9 +105,9 @@ checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
|||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "pam"
|
||||
|
@ -225,9 +131,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.69"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -264,15 +170,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rudo"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0-alpha"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"clap",
|
||||
"fork",
|
||||
"libc",
|
||||
"pam",
|
||||
"rpassword",
|
||||
"serde",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"toml",
|
||||
"unix_mode",
|
||||
"users 0.11.0",
|
||||
|
@ -281,9 +188,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.19"
|
||||
version = "0.38.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed"
|
||||
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
|
@ -292,6 +199,12 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.189"
|
||||
|
@ -322,16 +235,29 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
name = "strum"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.38"
|
||||
version = "2.0.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
|
||||
checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -403,22 +329,17 @@ dependencies = [
|
|||
"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"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
||||
checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c"
|
||||
dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -445,18 +366,18 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
|
@ -469,45 +390,45 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
[package]
|
||||
name = "rudo"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0-alpha"
|
||||
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"] }
|
||||
strum = "0.26.1"
|
||||
strum_macros = "0.26.1"
|
||||
toml = "0.8.2"
|
||||
unix_mode = "0.1.4"
|
||||
users = "0.11.0"
|
||||
which = "4.4.2"
|
||||
which = "6.0.0"
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
## Installation
|
||||
```
|
||||
cargo build --release
|
||||
mv target/release/rudo /usr/local/bin/rudo
|
||||
chown root:root /usr/local/bin/rudo
|
||||
chmod +xs /usr/local/bin/rudo
|
||||
```
|
357
src/app.rs
357
src/app.rs
|
@ -1,53 +1,308 @@
|
|||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
use std::{env::args, path::PathBuf};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
#[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,
|
||||
#[derive(Clone, Copy, Debug, EnumIter)]
|
||||
pub enum AppOption {
|
||||
Background,
|
||||
Chdir,
|
||||
CloseFrom,
|
||||
GenerateConfig,
|
||||
Group,
|
||||
Help,
|
||||
// Host,
|
||||
// List,
|
||||
// Login,
|
||||
NoUpdate,
|
||||
// OtherUser,
|
||||
PreserveEnv,
|
||||
// PreserveGroups,
|
||||
RemoveTimestamp,
|
||||
ResetTimestamp,
|
||||
// SetHome,
|
||||
// Shell,
|
||||
Stdin,
|
||||
User,
|
||||
Validate,
|
||||
Version,
|
||||
Z,
|
||||
}
|
||||
|
||||
impl AppOption {
|
||||
pub fn from_str(s: &str, _has_arg: bool) -> Option<Self> {
|
||||
match s {
|
||||
"--background" | "-b" => Self::Background,
|
||||
"--close-from" | "-C" => Self::CloseFrom,
|
||||
"--chdir" | "-D" => Self::Chdir,
|
||||
"--group" | "-g" => Self::Group,
|
||||
"--gen-conf" => Self::GenerateConfig,
|
||||
"--help" | "-h" => {
|
||||
// if has_arg {
|
||||
Self::Help
|
||||
// } else {
|
||||
// Self::Host
|
||||
// }
|
||||
}
|
||||
// "host" => Self::Host,
|
||||
// "list" | "l" => Self::List,
|
||||
// "--login" | "-i" => Self::Login,
|
||||
"--no-update" | "-N" => Self::NoUpdate,
|
||||
// "other-user" | "U" => Self::OtherUser,
|
||||
"--preserve-env" | "-E" => Self::PreserveEnv,
|
||||
// "--preserve-groups" | "-P" => Self::PreserveGroups,
|
||||
"--remove-timestamp" | "-K" => Self::RemoveTimestamp,
|
||||
"--reset_timestamp" | "-k" => Self::ResetTimestamp,
|
||||
// "--set-home" | "-H" => Self::SetHome,
|
||||
// "--shell" | "-s" => Self::Shell,
|
||||
"--stdin" | "-S" => Self::Stdin,
|
||||
"--user" | "-u" => Self::User,
|
||||
"--validate" | "-v" => Self::Validate,
|
||||
"--version" | "-V" => Self::Version,
|
||||
"--" => Self::Z,
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
pub fn from_flags(s: &str) -> Result<Vec<Self>, String> {
|
||||
let mut output = vec![];
|
||||
for ch in s.chars().skip(1) {
|
||||
if let Some(opt) = Self::from_str(&format!["-{ch}"], false) {
|
||||
if opt.is_flag() {
|
||||
output.push(opt)
|
||||
} else {
|
||||
return Err(format!["{ch}"]);
|
||||
}
|
||||
} else {
|
||||
return Err(format!["{ch}"]);
|
||||
}
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
pub fn usage(self) -> (&'static str, &'static str) {
|
||||
match self {
|
||||
AppOption::Background => (
|
||||
"--background | -b",
|
||||
"Run the given command in the background. Caution: backgrounded processes are not subject to shell job control. Interactive commands may misbehave.",
|
||||
),
|
||||
AppOption::Chdir => (
|
||||
"--chdir dir | -D dir",
|
||||
"Change to the specified directory before running the command."
|
||||
),
|
||||
AppOption::CloseFrom => (
|
||||
"--close-from n | -C n",
|
||||
"Close all file descriptors greater than or equal to n.",
|
||||
),
|
||||
AppOption::GenerateConfig => ("--gen-config", "Print example config to standard output."),
|
||||
AppOption::Group => (
|
||||
"--group | -g",
|
||||
"Run the command with the primary group set to group.",
|
||||
),
|
||||
AppOption::Help => ("--help | -h", "Display program help."),
|
||||
// AppOption::Host => ("", ""),
|
||||
// AppOption::List => ("--list (command) | -l (command)", "If command is specified, display the fully-qualified path as dictated by the security policy including command-line arguments. Otherwise, display allowed commands for the invoking user."),
|
||||
// AppOption::Login => (
|
||||
// "--login | -i",
|
||||
// "Runs the target user's login shell and passes command to the shell, if any.",
|
||||
// ),
|
||||
AppOption::NoUpdate => ("--no-update | -N", "Do not update user's cache credentials."),
|
||||
// AppOption::OtherUser => ("--other-user user | -U user", ""),
|
||||
AppOption::PreserveEnv => (
|
||||
"--preserve-env var, ... | -E var, ...",
|
||||
"Preserve environment variables.",
|
||||
),
|
||||
// AppOption::PreserveGroups => ("--preserve-groups | -P", "Preserve invoking user's groups."),
|
||||
AppOption::RemoveTimestamp => ("--remove-timestamp | -K", "Remove all cached credentials for invoking user."),
|
||||
AppOption::ResetTimestamp => ("--reset-timestamp | -k", "Without command, clears session credentials. With a command, runs the command without updating cached credentials."),
|
||||
// AppOption::SetHome => ("--set-home | -H", "Set HOME environment variable to target user's home."),
|
||||
// AppOption::Shell => ("--shell | -s", "Run the shell specified by the SHELL environment variable or as specific to invoking user. Passes command to shell."),
|
||||
AppOption::Stdin => ("--stdin | -S", "Write the password prompt to standard error and read the password from standard input."),
|
||||
AppOption::User => ("--user user| -u user", "Run command as user instead of default."),
|
||||
AppOption::Validate => ("--validate | -v", "Re-authenticate the user and update cached credentials."),
|
||||
AppOption::Version => ("--version | -V", "Print the version."),
|
||||
AppOption::Z => ("--", "Indicates end of options. Subsequent options are passed to the command."),
|
||||
}
|
||||
}
|
||||
pub fn is_flag(self) -> bool {
|
||||
match self {
|
||||
Self::Background
|
||||
| Self::Help
|
||||
// | AppOption::Login
|
||||
// | AppOption::PreserveGroups
|
||||
| Self::GenerateConfig
|
||||
| Self::NoUpdate
|
||||
| Self::RemoveTimestamp
|
||||
| Self::ResetTimestamp
|
||||
// | AppOption::SetHome
|
||||
// | AppOption::Shell
|
||||
| Self::Stdin
|
||||
| Self::Validate
|
||||
| Self::Version => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub fn format_all() -> String {
|
||||
let mut output = String::from("Usage: rudo (options) (--) (command)\n");
|
||||
let max_len = Self::iter().fold(0, |n, v| n.max(v.usage().0.len()));
|
||||
for (dashing, details) in Self::iter().map(|v| v.usage()) {
|
||||
let mut spacing = String::from(" ");
|
||||
for _ in dashing.len()..max_len {
|
||||
spacing.push(' ');
|
||||
}
|
||||
let s = format!["\n {dashing}{spacing}{details}"];
|
||||
let chunks = s
|
||||
.chars()
|
||||
.collect::<Vec<char>>()
|
||||
.chunks(80)
|
||||
.map(|chunk| chunk.into_iter().collect::<String>())
|
||||
.collect::<Vec<String>>();
|
||||
let f = chunks.join("\n");
|
||||
output.push_str(&f);
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct App {
|
||||
pub background: bool,
|
||||
pub chdir: Option<PathBuf>,
|
||||
pub chroot: Option<PathBuf>,
|
||||
pub close_from: Option<i32>,
|
||||
pub cmd: Vec<String>,
|
||||
pub gen_config: bool,
|
||||
pub group: Option<String>,
|
||||
pub help: bool,
|
||||
pub host: Option<String>,
|
||||
pub list: (bool, Option<String>),
|
||||
pub login: bool,
|
||||
pub no_update: bool,
|
||||
pub preserve_env: Vec<String>,
|
||||
pub preserve_groups: bool,
|
||||
pub remove_timestamp: bool,
|
||||
pub reset_timestamp: bool,
|
||||
pub set_home: bool,
|
||||
pub shell: bool,
|
||||
pub stdin: bool,
|
||||
pub user: Option<String>,
|
||||
pub validate: bool,
|
||||
pub version: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn parse() -> Result<Self, String> {
|
||||
let args: Vec<String> = args().collect();
|
||||
let mut app = App::default();
|
||||
let mut args_iter = args.iter().skip(1).peekable();
|
||||
'sm: loop {
|
||||
if let Some(arg) = args_iter.next() {
|
||||
let app_opts = if arg.starts_with("--") {
|
||||
if let Some(o) = AppOption::from_str(arg, args_iter.peek().is_some()) {
|
||||
vec![o]
|
||||
} else {
|
||||
return Err(format!["Error: Unknown option {arg}"]);
|
||||
}
|
||||
} else if arg.starts_with("-") {
|
||||
match AppOption::from_flags(arg) {
|
||||
Ok(v) => v,
|
||||
Err(s) => return Err(format!["Unknown flag {s}"]),
|
||||
}
|
||||
} else {
|
||||
app.cmd.push(arg.to_owned());
|
||||
break;
|
||||
};
|
||||
for app_opt in app_opts {
|
||||
match app_opt {
|
||||
AppOption::Background => app.background = true,
|
||||
AppOption::Chdir => {
|
||||
let next = args_iter.next();
|
||||
if let Some(s) = next {
|
||||
app.chdir = Some(s.into());
|
||||
} else {
|
||||
return Err(format![
|
||||
"Error: Missing argument for {arg}\nUsage: {}\n{}",
|
||||
AppOption::Chdir.usage().0,
|
||||
AppOption::Chdir.usage().1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
AppOption::CloseFrom => {
|
||||
let next = args_iter.next();
|
||||
if let Some(s) = next {
|
||||
if let Ok(n) = s.parse() {
|
||||
app.close_from = Some(n);
|
||||
} else {
|
||||
return Err(format![
|
||||
"Error: Invalid argument for {arg}: Expected number, got {s}\nUsage: {}\n{}",
|
||||
AppOption::CloseFrom.usage().0,
|
||||
AppOption::CloseFrom.usage().1,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
return Err(format![
|
||||
"Error: Missing argument for {arg}\nUsage: {}\n{}",
|
||||
AppOption::CloseFrom.usage().0,
|
||||
AppOption::CloseFrom.usage().1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
AppOption::GenerateConfig => app.gen_config = true,
|
||||
AppOption::Group => {
|
||||
let next = args_iter.next();
|
||||
if let Some(s) = next {
|
||||
app.group = Some(s.to_owned());
|
||||
} else {
|
||||
return Err(format![
|
||||
"Error: Missing argument for {arg}\nUsage: {}\n{}",
|
||||
AppOption::Group.usage().0,
|
||||
AppOption::Group.usage().1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
AppOption::Help => app.help = true,
|
||||
// AppOption::Login => app.login = true,
|
||||
AppOption::NoUpdate => app.no_update = true,
|
||||
AppOption::PreserveEnv => loop {
|
||||
if let Some(arg) = args_iter.next() {
|
||||
app.preserve_env.push(arg.to_owned());
|
||||
if !arg.ends_with(",") {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break 'sm;
|
||||
}
|
||||
},
|
||||
// AppOption::PreserveGroups => app.preserve_groups = true,
|
||||
AppOption::RemoveTimestamp => app.remove_timestamp = true,
|
||||
AppOption::ResetTimestamp => app.reset_timestamp = true,
|
||||
// AppOption::SetHome => app.set_home = true,
|
||||
// AppOption::Shell => app.shell = true,
|
||||
AppOption::Stdin => app.stdin = true,
|
||||
AppOption::User => {
|
||||
if let Some(arg) = args_iter.next() {
|
||||
app.user = Some(arg.to_owned());
|
||||
} else {
|
||||
return Err(format![
|
||||
"Error: Missing argument for {arg}\nUsage: {}\n{}",
|
||||
AppOption::User.usage().0,
|
||||
AppOption::User.usage().1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
AppOption::Validate => app.validate = true,
|
||||
AppOption::Version => app.version = true,
|
||||
AppOption::Z => break 'sm,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
app.cmd.extend(args_iter.map(|s| s.to_owned()));
|
||||
Ok(app)
|
||||
}
|
||||
pub fn usage() -> String {
|
||||
AppOption::format_all()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,33 @@
|
|||
use crate::{error::RudoError, user_info::get_username, AUTH_SERVICE, RETRIES};
|
||||
use std::io::stdin;
|
||||
|
||||
pub fn check_auth() -> Result<(), RudoError> {
|
||||
use crate::user_info::get_username;
|
||||
|
||||
const AUTH_SERVICE: &'static str = "system-auth";
|
||||
const RETRIES: u32 = 3;
|
||||
|
||||
pub fn check_auth(from_stdin: bool) -> Result<(), String> {
|
||||
let login = get_username().unwrap();
|
||||
let mut retries = RETRIES;
|
||||
for i in 0..retries {
|
||||
let password = rpassword::prompt_password(format!["password for {login}: "]).unwrap();
|
||||
for i in 0..RETRIES {
|
||||
let password = if from_stdin {
|
||||
eprintln!["password for {login}: "];
|
||||
let mut buf = String::new();
|
||||
if let Err(e) = stdin().read_line(&mut buf) {
|
||||
return Err(format!["Error: Rudo: Failed to read stdin: {e}"]);
|
||||
}
|
||||
buf
|
||||
} else {
|
||||
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 {
|
||||
if i < RETRIES - 1 {
|
||||
eprintln!["incorrect password, try again"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(RudoError::AuthenticationError)
|
||||
Err(String::from("Error: Authentication error."))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
use std::{
|
||||
fs::{DirBuilder, File, OpenOptions},
|
||||
io::{Read, Write},
|
||||
path::PathBuf,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use fork::{daemon, Fork};
|
||||
use libc::kill;
|
||||
|
||||
const DEFAULT_AUTH_CACHE_DURATION: u64 = 5 * 60;
|
||||
const AUTH_CACHE_PATH: &str = "/tmp/rudo/cache/auth/";
|
||||
const SESSION_CACHE_PATH: &str = "/tmp/rudo/cache/session/";
|
||||
|
||||
pub fn clear_auth_cache(alias: &str, ppid: u32) -> Result<(), std::io::Error> {
|
||||
let ppid_str = format!["{ppid}"];
|
||||
let mut path = PathBuf::from(SESSION_CACHE_PATH);
|
||||
path.push(&ppid_str);
|
||||
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(ppid_str);
|
||||
path.push(alias);
|
||||
if let Err(e) = std::fs::remove_dir_all(&path) {
|
||||
println!["Failed to remove session dir: {e}"];
|
||||
std::process::exit(1);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_auth_cache(alias: &str, ppid: u32) -> Result<(), std::io::Error> {
|
||||
let ppid_str = format!["{ppid}"];
|
||||
let mut session_path = PathBuf::from(SESSION_CACHE_PATH);
|
||||
DirBuilder::new().recursive(true).create(&session_path)?;
|
||||
session_path.push(&ppid_str);
|
||||
let mut timestamp_path = PathBuf::from(AUTH_CACHE_PATH);
|
||||
timestamp_path.push(ppid_str);
|
||||
DirBuilder::new().recursive(true).create(×tamp_path)?;
|
||||
timestamp_path.push(alias);
|
||||
let mut opts = OpenOptions::new();
|
||||
opts.write(true).create(true);
|
||||
let mut timestamp_file = opts.open(×tamp_path)?;
|
||||
let time = SystemTime::now() + Duration::from_secs(DEFAULT_AUTH_CACHE_DURATION);
|
||||
timestamp_file.write_all(&bincode::serialize(&time).unwrap_or_default())?;
|
||||
match OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(&session_path)
|
||||
{
|
||||
Ok(_) => {
|
||||
if let Ok(Fork::Child) = daemon(false, false) {
|
||||
std::thread::sleep(Duration::from_secs(DEFAULT_AUTH_CACHE_DURATION));
|
||||
if unsafe { kill(ppid as _, 0) } == -1 {
|
||||
clear_auth_cache(alias, ppid)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => (),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_auth_cache(alias: &str, ppid: u32) -> bool {
|
||||
let mut path = PathBuf::from(AUTH_CACHE_PATH);
|
||||
path.push(format!["{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::<SystemTime>(&buf) {
|
||||
Ok(old_time) => {
|
||||
if old_time.elapsed().unwrap_or_default()
|
||||
< Duration::from_secs(DEFAULT_AUTH_CACHE_DURATION as u64)
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
dbg!["{}", e];
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
239
src/main.rs
239
src/main.rs
|
@ -1,162 +1,165 @@
|
|||
use std::{
|
||||
fs::{DirBuilder, File, OpenOptions},
|
||||
io::{Error, Read, Write},
|
||||
os::unix::process::CommandExt,
|
||||
path::PathBuf,
|
||||
process::Command,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use std::{env, os::unix::process::CommandExt, process::Command};
|
||||
mod app;
|
||||
use app::App;
|
||||
use clap::Parser;
|
||||
use error::RudoError;
|
||||
mod error;
|
||||
use cache::*;
|
||||
mod cache;
|
||||
mod rudoers;
|
||||
mod user_info;
|
||||
use fork::{daemon, Fork};
|
||||
use libc::kill;
|
||||
use libc::{close, getpwuid};
|
||||
use rudoers::check_rudoers;
|
||||
use users::{get_group_by_name, get_user_by_name};
|
||||
mod authentication;
|
||||
use authentication::check_auth;
|
||||
use std::ffi::CStr;
|
||||
use user_info::get_command_path;
|
||||
|
||||
use crate::rudoers::make_example_rudoers_file;
|
||||
|
||||
/// 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/";
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
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
|
||||
pub fn execute(app: App) -> Result<(), String> {
|
||||
if !app.cmd.is_empty() && !app.shell {
|
||||
if let Some(close_from) = app.close_from {
|
||||
for i in close_from.. {
|
||||
let res = unsafe { close(i) };
|
||||
if res == -1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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_name = if let Some(nym) = app.user {
|
||||
nym
|
||||
} else {
|
||||
String::from("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();
|
||||
let run_as = get_user_by_name(&run_as_name).unwrap();
|
||||
let command_path = get_command_path(app.cmd.first().unwrap())?;
|
||||
let args = app.cmd;
|
||||
let args = &args[1..];
|
||||
let mut cmd = Command::new(command_path);
|
||||
cmd.uid(run_as.uid()).args(args);
|
||||
if let Some(chdir) = app.chdir {
|
||||
cmd.current_dir(chdir);
|
||||
}
|
||||
if let Some(group) = app.group {
|
||||
let group_id: u32 = if group.starts_with("#") {
|
||||
if let Ok(id) = group[1..].parse() {
|
||||
id
|
||||
} else {
|
||||
return Err(format!["Error: Failed to parse group ID '{}'", &group[1..]]);
|
||||
}
|
||||
} else {
|
||||
if let Some(group) = get_group_by_name(&group) {
|
||||
group.gid()
|
||||
} else {
|
||||
return Err(format!["Error: Group '{}' not found", group]);
|
||||
}
|
||||
};
|
||||
cmd.gid(group_id);
|
||||
}
|
||||
for var in app.preserve_env {
|
||||
let value = env::var(&var).unwrap_or_default();
|
||||
cmd.env(var, value);
|
||||
}
|
||||
// if app.preserve_groups {
|
||||
// if let Some(user) = get_user_by_name(&get_username()?) {}
|
||||
// }
|
||||
if app.set_home {
|
||||
let ptr = unsafe { (*getpwuid(run_as.uid())).pw_dir };
|
||||
let home_dir = match unsafe { CStr::from_ptr(ptr).to_str() } {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return Err(format![
|
||||
"Error: Failed to read home dir for {run_as_name}: {e}"
|
||||
]);
|
||||
}
|
||||
};
|
||||
cmd.env("HOME", home_dir);
|
||||
}
|
||||
// execute the child
|
||||
let mut child = cmd.spawn().unwrap();
|
||||
if app.background {
|
||||
std::process::exit(0);
|
||||
} else {
|
||||
child.wait().unwrap();
|
||||
}
|
||||
} else {
|
||||
return Err(RudoError::NoCommandSpecified);
|
||||
return Err(String::from("Error: No command specified."));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run() -> Result<(), RudoError> {
|
||||
let app = App::parse();
|
||||
pub fn run() -> Result<(), String> {
|
||||
let app = App::parse()?;
|
||||
if app.help {
|
||||
println!["{}", App::usage()];
|
||||
return Ok(());
|
||||
}
|
||||
if app.version {
|
||||
println!["Rudo v{VERSION}"];
|
||||
return Ok(());
|
||||
}
|
||||
if app.gen_config {
|
||||
println!["{}", make_example_rudoers_file()];
|
||||
}
|
||||
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;
|
||||
let needs_password =
|
||||
check_rudoers(&app)? && !check_auth_cache(alias, parent_id) || app.validate;
|
||||
if needs_password {
|
||||
check_auth()?;
|
||||
check_auth(app.stdin)?;
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
let failed_to_update = String::from("Error: Rudo: Failed to update auth cache: ");
|
||||
if !app.no_update && !app.remove_timestamp {
|
||||
if let Err(e) = update_auth_cache(alias, parent_id) {
|
||||
eprintln!["{failed_to_update}{e}"];
|
||||
std::process::exit(1);
|
||||
}
|
||||
Err(_) => (),
|
||||
};
|
||||
|
||||
}
|
||||
if app.validate {
|
||||
return Ok(());
|
||||
}
|
||||
if app.remove_timestamp {
|
||||
if let Err(e) = clear_auth_cache(alias, parent_id) {
|
||||
eprintln!["{failed_to_update}{e}"];
|
||||
std::process::exit(1);
|
||||
}
|
||||
if app.cmd.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if app.reset_timestamp {
|
||||
if let Err(e) = clear_auth_cache(alias, parent_id) {
|
||||
eprintln!["{failed_to_update}{e}"];
|
||||
std::process::exit(1);
|
||||
}
|
||||
if let Err(e) = update_auth_cache(alias, parent_id) {
|
||||
eprintln!["{failed_to_update}{e}"];
|
||||
std::process::exit(1);
|
||||
}
|
||||
if app.cmd.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
// Run the command
|
||||
execute(&app)?;
|
||||
|
||||
execute(app)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
fn main() {
|
||||
match run() {
|
||||
Ok(()) => (),
|
||||
Err(e) => eprintln!["{e}"],
|
||||
Err(e) => {
|
||||
eprintln!["{e}"];
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use crate::{app::App, user_info::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
io::Read,
|
||||
path::PathBuf,
|
||||
};
|
||||
use users::Group;
|
||||
|
||||
use crate::{app::App, user_info::*};
|
||||
use crate::{error::RudoError, DEFAULT_RUDOERS};
|
||||
|
||||
const ALL: &'static str = "ALL";
|
||||
const DEFAULT_RUDOERS: &'static str = "/etc/rudoers.toml";
|
||||
|
||||
fn default_password_required() -> bool {
|
||||
true
|
||||
|
@ -69,13 +68,15 @@ fn check_entry(
|
|||
hostname: &str,
|
||||
alias: &str,
|
||||
command_path: &str,
|
||||
) -> Result<bool, RudoError> {
|
||||
) -> Result<bool, String> {
|
||||
let permission_denied = String::from("Error: Permission denied.");
|
||||
let operation_not_permitted = String::from("Error: Operation not permitted.");
|
||||
match entry {
|
||||
Entry::CommandPaths(cmds) => {
|
||||
if cmds.contains(command_path) || cmds.contains(ALL) {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(RudoError::PermissionDenied)
|
||||
Err(permission_denied)
|
||||
}
|
||||
}
|
||||
Entry::Hosts(hosts) => {
|
||||
|
@ -84,27 +85,27 @@ fn check_entry(
|
|||
if host.commands.contains(command_path) || host.commands.contains(ALL) {
|
||||
Ok(host.password_required)
|
||||
} else {
|
||||
Err(RudoError::PermissionDenied)
|
||||
Err(permission_denied)
|
||||
}
|
||||
} else {
|
||||
Err(if alias == "root" {
|
||||
RudoError::PermissionDenied
|
||||
permission_denied
|
||||
} else {
|
||||
RudoError::NoSwitchEntry
|
||||
operation_not_permitted
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Err(RudoError::PermissionDenied)
|
||||
Err(permission_denied)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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())?
|
||||
pub fn check_rudoers(args: &App) -> Result<bool, String> {
|
||||
let command_path: PathBuf = if let Some(first) = args.cmd.first() {
|
||||
get_command_path(first)?
|
||||
} else {
|
||||
return Err(RudoError::NoCommandSpecified);
|
||||
return Err(String::from("Error: No command specified."));
|
||||
};
|
||||
let alias: String = args.user.clone().unwrap_or("root".into());
|
||||
let hostname: String = get_hostname()?;
|
||||
|
@ -114,7 +115,7 @@ pub fn check_rudoers(args: &App) -> Result<bool, RudoError> {
|
|||
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);
|
||||
let mut result = Err(String::from("Error: User is not in the rudoers file."));
|
||||
for group in groups {
|
||||
if let Some(entry) = rudoers.groups.get(group.name().to_str().unwrap()) {
|
||||
let output =
|
||||
|
@ -129,8 +130,7 @@ pub fn check_rudoers(args: &App) -> Result<bool, RudoError> {
|
|||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rudoers_toml() {
|
||||
pub fn make_example_rudoers_file() -> String {
|
||||
let mut rudoers = Rudoers::default();
|
||||
rudoers.users.insert(
|
||||
"alice".to_string(),
|
||||
|
@ -194,9 +194,5 @@ fn rudoers_toml() {
|
|||
text.push_str(&s);
|
||||
}
|
||||
}
|
||||
text.push('\n');
|
||||
File::create("test_config")
|
||||
.unwrap()
|
||||
.write_all(&text.into_bytes())
|
||||
.unwrap();
|
||||
text
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ use libc::gethostname;
|
|||
use users::{get_current_gid, get_current_username, Group};
|
||||
use which::which;
|
||||
|
||||
use crate::error::RudoError;
|
||||
// use crate::error::RudoError;
|
||||
|
||||
pub fn get_command_path(command: &str) -> Result<PathBuf, RudoError> {
|
||||
pub fn get_command_path(command: &str) -> Result<PathBuf, String> {
|
||||
if let Ok(path) = which(command) {
|
||||
Ok(path)
|
||||
} else {
|
||||
|
@ -16,15 +16,16 @@ pub fn get_command_path(command: &str) -> Result<PathBuf, RudoError> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_username() -> Result<String, RudoError> {
|
||||
pub fn get_username() -> Result<String, String> {
|
||||
let bad_username = String::from("Error: only UTF-8 usernames are supported.");
|
||||
if let Some(username) = get_current_username() {
|
||||
if let Ok(username) = username.into_string() {
|
||||
Ok(username)
|
||||
} else {
|
||||
Err(RudoError::BadUsername)
|
||||
Err(bad_username)
|
||||
}
|
||||
} else {
|
||||
Err(RudoError::BadUsername)
|
||||
Err(bad_username)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,17 +39,18 @@ pub fn get_groups() -> Vec<Group> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_hostname() -> Result<String, RudoError> {
|
||||
pub fn get_hostname() -> Result<String, String> {
|
||||
let bad_hostname = String::from("Error: only UTF-8 hostnames are supported.");
|
||||
let mut hostname = [0u8; 255];
|
||||
unsafe {
|
||||
if gethostname(hostname.as_mut_ptr() as *mut i8, hostname.len()) != 0 {
|
||||
return Err(RudoError::BadHostname);
|
||||
return Err(bad_hostname);
|
||||
}
|
||||
};
|
||||
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)
|
||||
Err(bad_hostname)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue